diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 00000000000..6d85638ba17 --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# .asf.yaml documentation: +# https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features +notifications: + commits: commits@logging.apache.org + # issues and PRs send their own emails to devs + issues: notifications@logging.apache.org + pullrequests: notifications@logging.apache.org + jira_options: link label +github: + description: "Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback's architecture." + homepage: https://logging.apache.org/log4j/2.x/ + labels: + - apache + - api + - java + - library + - log4j + - log4j2 + - logging + protected_branches: + # Prevent force pushes to primary branches + master: {} + release-2.x: {} diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 627e878c53f..00000000000 --- a/.dockerignore +++ /dev/null @@ -1,27 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -.project -.idea -**/*.iml -**/target -target/ -.settings -.classpath -.cache-main -.cache-tests -velocity.log -felix-cache/ -bin/ diff --git a/.gitattributes b/.gitattributes index c425e6f31e1..776b2e16ae1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,15 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. mvnw.cmd eol=crlf diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..7a437062e36 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +version: 2 +updates: +- package-ecosystem: maven + directory: "/" + schedule: + interval: monthly + open-pull-requests-limit: 50 +- package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 5 +- package-ecosystem: "docker" + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000000..455f29b6910 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,234 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +name: benchmark + +on: [ workflow_dispatch ] + +jobs: + + build: + + if: github.repository == 'apache/logging-log4j2' + + runs-on: ubuntu-latest + + steps: + + - name: Checkout repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + + - name: Set up JDK 11 + uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # 3.6.0 + with: + distribution: temurin + java-version: 11 + java-package: jdk + architecture: x64 + cache: maven + + - name: Build with Maven + shell: bash + run: | + ./mvnw \ + --show-version --batch-mode --errors --no-transfer-progress \ + -DskipTests=true \ + --projects log4j-perf \ + --also-make \ + package + + - name: Upload built sources + uses: actions/upload-artifact@v2 + with: + name: benchmarks.jar + path: log4j-perf/target/benchmarks.jar + + run: + + needs: build + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ macos-latest, ubuntu-latest, windows-latest ] + jdk: [ 11, 17 ] + concurrency: [ 1, 8 ] + jmhCommand: + - "-t $CONCURRENCY -f 3 -wi 3 -w 10s -i 4 -r 20s -prof gc -prof perfnorm -rf json -rff results-layout-jtl.json '.*JsonTemplateLayoutBenchmark.*Jtl4EcsLayout'" + - "-t $CONCURRENCY -f 3 -wi 3 -w 10s -i 4 -r 20s -prof gc -prof perfnorm -rf json -rff results-util-instant-format.json '.*InstantFormatBenchmark.*'" + + steps: + + - name: Checkout repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + + - name: Download built sources + uses: actions/download-artifact@v2 + with: + name: benchmarks.jar + path: log4j-perf/target + + - name: Set up JDK ${{ matrix.jdk }} + if: matrix.jdk != 11 + uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # 3.6.0 + with: + distribution: temurin + java-version: ${{ matrix.jdk }} + java-package: jdk + architecture: x64 + cache: maven + + - name: Run benchmarks + timeout-minutes: 120 + shell: bash + run: | + export CONCURRENCY=${{ matrix.concurrency }} + java \ + -jar log4j-perf/target/benchmarks.jar \ + ${{ matrix.jmhCommand }} + + - name: Stage benchmark results for commit + shell: bash + run: | + + # Determine the artifact version. + set -x + ./mvnw \ + --batch-mode --quiet \ + -DforceStdout=true \ + -Dexpression=project.version \ + help:evaluate \ + | tee mvnw-project-version.out + echo + + # Determine certain file path properties. + export REVISION=$(&1 | tee git-push.out + if [ $? -eq 0 ]; then + exit 0 + else + set -e + let RETRY+=1 + echo "retry #$RETRY" + git pull -r origin gh-pages + fi + done + + index: + + runs-on: ubuntu-latest + needs: run + + steps: + + - name: Checkout repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + with: + ref: gh-pages + + - name: Setup Python 3 + uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # 4.3.0 + with: + python-version: 3.x + + - name: Index benchmark results + timeout-minutes: 1 + shell: bash + run: | + + # Configure the git user. + git config user.name github-actions + git config user.email github-actions@github.com + + # Push changes in a loop to allow concurrent repository modifications. + export RETRY=0 + export INDEX_FILEPATH=benchmark/results/index.json + while [ 1 ]; do + + # Generate the index file. + python -c '\ + import json, os, re;\ + filepaths=[re.sub("^benchmark/results/", "", os.path.join(root,filename)) \ + for (root, dirs, filenames) in os.walk("benchmark/results") \ + for filename in filenames]; \ + filepaths.remove("index.json"); \ + print(json.dumps(filepaths))' \ + >"$INDEX_FILEPATH" + + # Exit if there are no changes, that is, a concurrent job has indexed all results. + git diff --exit-code "$INDEX_FILEPATH" && exit 0 + + # Commit the index file. + git add "$INDEX_FILEPATH" + git commit "$INDEX_FILEPATH" -m "Update benchmark results index." + + # Push the index file. + set +e + git push origin gh-pages 2>&1 | tee git-push.out + if [ $? -eq 0 ]; then + exit 0 + else + set -e + let RETRY+=1 + echo "retry #$RETRY" + git pull -r origin gh-pages + fi + + done diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..172bbe0b415 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,128 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +name: build + +on: + push: + branches: + - master + pull_request: + +permissions: read-all + +jobs: + + build: + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + + steps: + + - name: Checkout repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + with: + fetch-depth: 32 + + - name: Setup JDK 11 + uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # 3.6.0 + with: + distribution: temurin + java-version: 11 + java-package: jdk + architecture: x64 + cache: maven + + - name: Inspect environment (Linux) + if: runner.os == 'Linux' + run: env | grep '^JAVA' + + - name: Inspect environment (Windows) + if: runner.os == 'Windows' + run: set java + + - name: Inspect environment (MacOS) + if: runner.os == 'macOS' + run: env | grep '^JAVA' + + - name: Maven "verify" + timeout-minutes: 60 + shell: bash + run: | + ./mvnw \ + --show-version --batch-mode --errors --no-transfer-progress --fail-at-end \ + -DtrimStackTrace=false \ + -Dsurefire.rerunFailingTestsCount=2 \ + -Dlog4j2.junit.fileCleanerSleepPeriodMillis=1000 \ + verify + + # Maven `site` goal takes ~1 hour and mostly irrelevant for typical PRs. + # Hence, we only execute that for `dependabot` PRs, which tend to break `site` occasionally. + - name: Maven "site" + if: ${{ github.actor == 'dependabot[bot]' }} + timeout-minutes: 60 + shell: bash + run: | + ./mvnw \ + --show-version --batch-mode --errors --no-transfer-progress --fail-at-end \ + --non-recursive \ + -Dmaven.doap.skip \ + -DskipTests \ + site + + deploy: + + runs-on: ubuntu-latest + needs: build + if: github.repository == 'apache/logging-log4j2' && github.ref == 'refs/heads/master' + + steps: + + - name: Checkout repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + with: + fetch-depth: 32 + + - name: Setup JDK 11 + uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # 3.6.0 + with: + distribution: temurin + java-version: 11 + java-package: jdk + architecture: x64 + cache: maven + + - name: Inspect environment + run: env | grep '^JAVA' + + - name: Maven "deploy" + timeout-minutes: 15 + shell: bash + # `package install:install deploy:deploy` goal is needed to deploy without configuring the plugin in the POM. + # For details see: https://maven.apache.org/plugins/maven-gpg-plugin/examples/deploy-signed-artifacts.html + run: | + ./mvnw \ + --show-version --batch-mode --errors --no-transfer-progress \ + -Dgpg.skip=true \ + -DskipTests=true \ + --settings .github/workflows/maven-settings.xml \ + package install:install deploy:deploy + env: + NEXUS_USER: ${{ secrets.NEXUS_USER }} + NEXUS_PW: ${{ secrets.NEXUS_PW }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000000..1bab4c774d7 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,81 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '32 12 * * 5' + +jobs: + + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + + - name: Checkout repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + with: + fetch-depth: 32 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@cc7986c02bac29104a72998e67239bb5ee2ee110 # 2.1.28 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # JDK 11 is needed for the build. + # Search `maven-toolchains-plugin` usages for details. + - name: Setup JDK 11 + uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # 3.6.0 + with: + distribution: temurin + java-version: 11 + java-package: jdk + architecture: x64 + cache: maven + + - name: Build with Maven + timeout-minutes: 60 + shell: bash + run: | + ./mvnw \ + --show-version --batch-mode --errors --no-transfer-progress \ + -DskipTests + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@cc7986c02bac29104a72998e67239bb5ee2ee110 # 2.1.28 diff --git a/.github/workflows/log4j-kafka-test.yml b/.github/workflows/log4j-kafka-test.yml new file mode 100644 index 00000000000..b5843042aa8 --- /dev/null +++ b/.github/workflows/log4j-kafka-test.yml @@ -0,0 +1,59 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +name: log4j-kafka tests +on: [workflow_dispatch] + +permissions: read-all + +jobs: + + log4j-kafka-test: + + runs-on: ubuntu-latest + strategy: + matrix: + version: [ 1.1.1, 2.8.2, 3.3.1 ] + + steps: + + - name: Checkout repository + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # 3.1.0 + with: + fetch-depth: 32 + + - name: Setup JDK 11 + uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # 3.6.0 + with: + distribution: temurin + java-version: 11 + java-package: jdk + architecture: x64 + cache: maven + + - name: Inspect environment (Linux) + if: runner.os == 'Linux' + run: env | grep '^JAVA' + + - name: Maven "test" + timeout-minutes: 60 + shell: bash + run: | + ./mvnw \ + --show-version --batch-mode --errors --no-transfer-progress --fail-at-end \ + -Dkafka.version=${{ matrix.version }} \ + -pl log4j-kafka \ + test + diff --git a/.github/workflows/maven-settings.xml b/.github/workflows/maven-settings.xml new file mode 100644 index 00000000000..c7453df3962 --- /dev/null +++ b/.github/workflows/maven-settings.xml @@ -0,0 +1,28 @@ + + + + + + apache.snapshots.https + ${env.NEXUS_USER} + ${env.NEXUS_PW} + + + diff --git a/.gitignore b/.gitignore index fce0e91bb57..3faf4b45e32 100644 --- a/.gitignore +++ b/.gitignore @@ -12,18 +12,20 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -.project +bin/ +.cache-main +.cache-tests +.classpath +.factorypath +felix-cache/ .idea **/*.iml +/.metadata/ +.mvn/wrapper/MavenWrapperDownloader.java +.project +.settings +.surefire-* **/target target/ -.settings -.classpath -.cache-main -.cache-tests .toDelete -.factorypath velocity.log -felix-cache/ -bin/ diff --git a/.java-version b/.java-version new file mode 100644 index 00000000000..b4de3947675 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +11 diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index c6feb8bb6f7..c1dd12f1764 100644 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index d183696ac15..8c79a83ae43 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,16 +1,18 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip \ No newline at end of file +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/.travis-toolchains.xml b/.travis-toolchains.xml deleted file mode 100644 index f1702234504..00000000000 --- a/.travis-toolchains.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - jdk - - java7 - 1.7 - oracle - - - /usr/lib/jvm/java-7-oracle - - - - jdk - - java8 - 1.8 - oracle - - - /usr/lib/jvm/java-8-oracle - - - - jdk - - java9 - 9 - oracle - - - /usr/lib/jvm/java-9-oracle - - - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d3441ea5532..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -language: java -# sudo enabled because sudo vms have more memory: https://docs.travis-ci.com/user/ci-environment/ -sudo: true -# trusty required for oraclejdk9 -dist: trusty - -jdk: - - oraclejdk7 - -env: - global: - JAVA_OPTS=-Xmx4g - -install: - - ./mvnw --toolchains=./.travis-toolchains.xml install -DskipTests=true -Dmaven.javadoc.skip=true -B -V - -script: - - ./mvnw --toolchains=./.travis-toolchains.xml test -B - -after_success: - - ./mvnw --show-version -pl !log4j-bom jacoco:prepare-agent test jacoco:report coveralls:report \ No newline at end of file diff --git a/BUILDING.md b/BUILDING.md index d9734588124..839913f23f9 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -14,64 +14,40 @@ See the License for the specific language governing permissions and limitations under the License. --> -# Building Log4j 2 - -To build Log4j 2, you need a JDK implementation version 1.7 or greater, JDK -version 9, and Apache Maven 3.x. -Log4j 2.x uses the Java 9 compiler in addition to -the Java version installed in the path. This is accomplished by using Maven's toolchains support. -Log4j 2 provides sample toolchains XML files in the root folder. This may be used by -modifying it and installing the file as toolchains.xml in the .m2 folder or by using the -following when invoking Maven. +# Requirements -``` -[Macintosh] -t ./toolchains-sample-mac.xml -[Windows] -t ./toolchains-sample-win.xml -[Linux] -t ./toolchains-sample-linux.xml -``` +* JDK 11 +* Apache Maven 3.x +* A modern Linux, OSX, or Windows host -To perform the license release audit, a.k.a. "RAT check", run. + +# Building the sources - mvn apache-rat:check +You can build and verify the sources as follows: -To build the site with Java 7, make sure you give Maven enough memory using -`MAVEN_OPTS` with options appropriate for your JVM. Alternatively, you can -build with Java 8 and not deal with `MAVEN_OPTS`. + ./mvnw verify -To install the jars in your local Maven repository, from a command line, run: +`verify` goal runs validation and test steps next to building (i.e., compiling) the sources. +To speed up the build, you can skip verification: - mvn clean install + ./mvnw -DskipTests package -Once install is run, you can run the Clirr check on the API and 1.2 API modules: +If you want to install generated artifacts to your local Maven repository, replace above `verify` and/or `package` goals with `install`. - mvn clirr:check -pl log4j-api + +## DNS lookups in tests - mvn clirr:check -pl log4j-1.2-api +Note that if your `/etc/hosts` file does not include an entry for your computer's hostname, then +many unit tests may execute slow due to DNS lookups to translate your hostname to an IP address in +[`InetAddress.getLocalHost()`](http://docs.oracle.com/javase/7/docs/api/java/net/InetAddress.html#getLocalHost()). +To remedy this, you can execute the following: -Next, to build the site: + printf '127.0.0.1 %s\n::1 %s\n' `hostname` `hostname` | sudo tee -a /etc/hosts -If Java 7 runs out of memory building the site, you will need: + +# Building the website and manual - set MAVEN_OPTS=-Xmx2000m -XX:MaxPermSize=384m +You can build the website and manual as follows: - mvn site - -On Windows, use a local staging directory, for example: - - mvn site:stage-deploy -DstagingSiteURL=file:///%HOMEDRIVE%%HOMEPATH%/log4j - -On UNIX, use a local staging directory, for example: - - mvn site:stage-deploy -DstagingSiteURL=file:///$HOME/log4j - -To test, run: - - mvn clean install - -## Testing in Docker - -In order to run a clean test using the minimum version of the JDK along with a -proper Linux environment, run: - - docker build . + ./mvnw --non-recursive -Dmaven.doap.skip -DskipTests site diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..3ed501501d3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,17 @@ + +The Apache code of conduct page is [https://www.apache.org/foundation/policies/conduct.html](https://www.apache.org/foundation/policies/conduct.html). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 526d871feaf..d0dd1135c4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ follow so that we can have a chance of keeping on top of things. ## Getting Started + Make sure you have a [JIRA account](https://issues.apache.org/jira/). -+ Make sure you have a [GitHub account](https://github.com/signup/free). ++ Make sure you have a [GitHub account](https://github.com/join). + If you're planning to implement a new feature it makes sense to discuss your changes on the [dev list](https://logging.apache.org/log4j/2.x/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Log4j's scope. + Submit a ticket for your issue, assuming one does not already exist. + Clearly describe the issue including steps to reproduce when it is a bug. @@ -62,9 +62,9 @@ In this case, it is appropriate to start the first line of a commit with '(doc)' + [Project Guidelines](https://logging.apache.org/log4j/2.x/guidelines.html) + [Code Style Guide](https://logging.apache.org/log4j/2.x/javastyle.html) -+ [Apache Log4j 2 JIRA project page](https://issues.apache.org/jira/browse/LOG4J2) ++ [Apache Log4j 2 JIRA project page](https://issues.apache.org/jira/projects/LOG4J2/issues) + [Contributor License Agreement][cla] -+ [General GitHub documentation](https://help.github.com/) -+ [GitHub pull request documentation](https://help.github.com/send-pull-requests/) ++ [General GitHub documentation](https://docs.github.com/) ++ [GitHub pull request documentation](https://docs.github.com/en/pull-requests) [cla]:https://www.apache.org/licenses/#clas diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 07b0b59d756..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# FROM openjdk:7-alpine -# Reverted to debian yet alpine does not include jdk9 -FROM openjdk:7-jdk - -# Require while jdk9 is unstable on debian -RUN echo 'deb http://deb.debian.org/debian unstable main' >> /etc/apt/sources.list - -RUN set -ex \ - && mkdir /src \ - && apt-get update \ - && apt-get install -y \ - curl \ - openjdk-9-jdk-headless \ - && ln -svT "/usr/lib/jvm/java-9-openjdk-$(dpkg --print-architecture)" /docker-java-9-home \ - && cd /opt \ - && curl -fsSL http://www-us.apache.org/dist/maven/maven-3/3.5.0/binaries/apache-maven-3.5.0-bin.tar.gz -o maven.tar.gz \ - && tar -xzf maven.tar.gz \ - && rm -f maven.tar.gz - -COPY . /src - -RUN set -ex \ - && cd /src \ - && /opt/apache-maven-3.5.0/bin/mvn verify --global-toolchains toolchains-docker.xml diff --git a/NOTICE.txt b/NOTICE.txt index bd95322f254..bbb5fb3f66e 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache Log4j -Copyright 1999-2017 Apache Software Foundation +Copyright 1999-2023 Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). @@ -15,3 +15,6 @@ Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams picocli (http://picocli.info) Copyright 2017 Remko Popma + +TimeoutBlockingWaitStrategy.java and parts of Util.java +Copyright 2011 LMAX Ltd. diff --git a/README.md b/README.md index e5ade8a1c62..4aa65ab6bf0 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,28 @@ -# [Apache Log4j 2](https://logging.apache.org/log4j/2.x/) - -Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, + + +# [Apache Log4j 3.x](https://logging.apache.org/log4j/2.x/) + +Apache Log4j 3.x is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback's architecture. -[![Jenkins Status](https://img.shields.io/jenkins/s/https/builds.apache.org/job/Log4j%202.x.svg)](https://builds.apache.org/job/Log4j%202.x/) -[![Travis Status](https://travis-ci.org/apache/logging-log4j2.svg?branch=master)](https://travis-ci.org/apache/logging-log4j2) -[![Maven Central](https://img.shields.io/maven-central/v/org.apache.logging.log4j/log4j-api.svg)](http://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api) +[![GitHub build (3.x)](https://img.shields.io/github/workflow/status/apache/logging-log4j2/build/master?label=3.x&logo=github)](https://github.com/apache/logging-log4j2/actions?query=workflow%3AMaven+branch%3Amaster) +[![GitHub build (2.x)](https://img.shields.io/github/workflow/status/apache/logging-log4j2/build/release-2.x?label=2.x&logo=github)](https://github.com/apache/logging-log4j2/actions?query=workflow%3AMaven+branch%3Arelease-2.x) +[![Latest Maven Central release](https://img.shields.io/maven-central/v/org.apache.logging.log4j/log4j-api.svg?logo=java)](http://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api) ## Pull Requests on Github @@ -13,7 +30,7 @@ and provides many of the improvements available in Logback while fixing some inh By sending a pull request you grant the Apache Software Foundation sufficient rights to use and release the submitted work under the Apache license. You grant the same rights (copyright license, patent license, etc.) to the Apache Software Foundation as if you have signed a Contributor License Agreement. For contributions that are -judged to be non-trivial, you will be asked to actually signing a Contributor License Agreement. +judged to be non-trivial, you will be asked to actually sign a [Contributor License Agreement](https://www.apache.org/licenses/icla.pdf). ## Usage @@ -64,42 +81,38 @@ And an example `log4j2.xml` configuration file: ## Documentation -The Log4j 2 User's Guide is available [here](https://logging.apache.org/log4j/2.x/manual/index.html) or as a downloadable +The Log4j 3.x User's Guide is available [here](https://logging.apache.org/log4j/2.x/manual/index.html) or as a downloadable [PDF](https://logging.apache.org/log4j/2.x/log4j-users-guide.pdf). ## Requirements -Log4j 2.4 and greater requires Java 7, versions 2.0-alpha1 to 2.3 required Java 6. +Log4j 3.0 and greater requires Java 11. Some features require optional dependencies; the documentation for these features specifies the dependencies. ## License -Apache Log4j 2 is distributed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). +Apache Log4j 3.x is distributed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). ## Download [How to download Log4j](http://logging.apache.org/log4j/2.x/download.html), and [how to use it from Maven, Ivy and Gradle](http://logging.apache.org/log4j/2.x/maven-artifacts.html). -You can access the latest development snapshot by using the Maven repository `https://repository.apache.org/snapshots`, +You can access the latest development snapshot by using the Maven repository `https://repository.apache.org/snapshots`, see [Snapshot builds](https://logging.apache.org/log4j/2.x/maven-artifacts.html#Snapshot_builds). ## Issue Tracking -Issues, bugs, and feature requests should be submitted to the +Issues, bugs, and feature requests should be submitted to the [JIRA issue tracking system for this project](https://issues.apache.org/jira/browse/LOG4J2). -Pull request on GitHub are welcome, but please open a ticket in the JIRA issue tracker first, and mention the +Pull requests on GitHub are welcome, but please open a ticket in the JIRA issue tracker first, and mention the JIRA issue in the Pull Request. ## Building From Source -Log4j requires Apache Maven 3.x. To build from source and install to your local Maven repository, execute the following: - -```sh -mvn install -``` +See [the detailed build instructions](BUILDING.md) on how to build to the project and website from sources. ## Contributing -We love contributions! Take a look at -[our contributing page](https://github.com/apache/logging-log4j2/blob/master/CONTRIBUTING.md). +We love contributions! +Take a look at [our contributing page](CONTRIBUTING.md). diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index fe5f8001863..9914baf7441 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,91 @@ +# Apache Log4j 2.11.0 Release Notes + +## List of Binary Compatibility Breaking Changes in log4j-core + +Log4j 2.11.0 moves code from `log4j-core` to several new Maven modules. Dependencies to other jars that used to be +optional in `log4j-core` are now required in the new modules. The code in these modules have been repackaged. + +These changes do not affect your configuration files. + +The new modules are: + +### log4j-cvs + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-cvs` + +* Old package: `org.apache.logging.log4j.core.layout` +* New package: `org.apache.logging.log4j.csv.layout` + +### log4j-jdbc + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-jdbc` + +* Old package: `org.apache.logging.log4j.core.appender.db.jdbc` +* New package: `org.apache.logging.log4j.jdbc.appender` + +### log4j-jeromq + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-jeromq` + +* Old package: `org.apache.logging.log4j.core.appender.mom.jeromq` +* New package: `org.apache.logging.log4j.jeromq.appender` + +### log4j-jms + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-jms` + +* Old package 1: `org.apache.logging.log4j.core.appender.mom` +* New package 1: `org.apache.logging.log4j.jms.appender` + + + +### log4j-jpa + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-jpa` + +* Old package 1: `org.apache.logging.log4j.core.appender.db.jpa` +* New package 1: `org.apache.logging.log4j.jpa.appender` + +* Old package 2: `org.apache.logging.log4j.core.appender.db.jpa.converter` +* New package 2: `org.apache.logging.log4j.jpa.converter` + +### log4j-kafka + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-kafka` + +* Old package: `org.apache.logging.log4j.core.appender.mom.kafka` +* New package: `org.apache.logging.log4j.kafka.appender` + +### log4j-mongodb2 + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-mongodb2` + +* Old package: `org.apache.logging.log4j.mongodb` +* New package: `org.apache.logging.log4j.mongodb2` + +### log4j-mongodb3 + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-mongodb3` + +* Old package: `org.apache.logging.log4j.mongodb` +* New package: `org.apache.logging.log4j.mongodb3` + +### log4j-smtp + +* Group ID: `org.apache.logging.log4j` +* Artifact ID: `log4j-smtp` + +* Old package: `org.apache.logging.log4j.core.appender` +* New package: `org.apache.logging.log4j.smtp.appender` + # Apache Log4j 2.10.0 Release Notes The Apache Log4j 2 team is pleased to announce the Log4j 2.10.0 release! diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..f6dbd02b1b4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Only the most recent release of Apache Log4j 2 is supported. + +## Reporting a Vulnerability + +If you have encountered an unlisted security vulnerability or other unexpected behaviour that has security impact, please report them privately to the [Log4j Security Team](mailto:private@logging.apache.org). + +## Past Vulnerabilities + +See [Apache Log4j Security Vulnerabilities](https://logging.apache.org/log4j/2.x/security.html). diff --git a/checkstyle.xml b/checkstyle.xml index 80ad14d34e0..4ac1c24c957 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -24,6 +24,8 @@ + + @@ -62,33 +64,24 @@ - - - + - --> - - - - - - + + - - - + + + + @@ -154,11 +147,6 @@ - - - - - diff --git a/doap_log4j2.rdf b/doap_log4j2.rdf deleted file mode 100644 index fc2328e7ae5..00000000000 --- a/doap_log4j2.rdf +++ /dev/null @@ -1,447 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - Apache Log4j 2 - - - - - - Apache Log4j 2 - Apache Log4j 2 - - - - - - Apache Software Foundation - - - - - - 1999-01-01 - - - - - - - - - - - - - - - - - - Java - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Latest stable release - 2.7 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.7/log4j-2.7.pom - 2016-10-06 - - - - - Apache Log4j 2 - 2.6.2 - 2.6.2 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.6.2/log4j-2.6.2.pom - - - - - Apache Log4j 2 - 2.6.1 - 2.6.1 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.6.1/log4j-2.6.1.pom - - - - - Apache Log4j 2 - 2.6 - 2.6 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.6/log4j-2.6.pom - - - - - Apache Log4j 2 - 2.5 - 2.5 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.5/log4j-2.5.pom - - - - - Apache Log4j 2 - 2.4.1 - 2.4.1 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.4.1/log4j-2.4.1.pom - - - - - Apache Log4j 2 - 2.4 - 2.4 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.4/log4j-2.4.pom - - - - - Apache Log4j 2 - 2.3 - 2.3 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.3/log4j-2.3.pom - - - - - Apache Log4j 2 - 2.2 - 2.2 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.2/log4j-2.2.pom - - - - - Apache Log4j 2 - 2.1 - 2.1 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.1/log4j-2.1.pom - - - - - Apache Log4j 2 - 2.0.2 - 2.0.2 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0.2/log4j-2.0.2.pom - - - - - Apache Log4j 2 - 2.0.1 - 2.0.1 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0.1/log4j-2.0.1.pom - - - - - Apache Log4j 2 - 2.0 - 2.0 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0/log4j-2.0.pom - - - - - Apache Log4j 2 - 2.0-rc2 - 2.0-rc2 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-rc2/log4j-2.0-rc2.pom - - - - - Apache Log4j 2 - 2.0-rc1 - 2.0-rc1 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-rc1/log4j-2.0-rc1.pom - - - - - Apache Log4j 2 - 2.0-beta9 - 2.0-beta9 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta9/log4j-2.0-beta9.pom - - - - - Apache Log4j 2 - 2.0-beta8 - 2.0-beta8 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta8/log4j-2.0-beta8.pom - - - - - Apache Log4j 2 - 2.0-beta7 - 2.0-beta7 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta7/log4j-2.0-beta7.pom - - - - - Apache Log4j 2 - 2.0-beta6 - 2.0-beta6 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta6/log4j-2.0-beta6.pom - - - - - Apache Log4j 2 - 2.0-beta5 - 2.0-beta5 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta5/log4j-2.0-beta5.pom - - - - - Apache Log4j 2 - 2.0-beta4 - 2.0-beta4 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta4/log4j-2.0-beta4.pom - - - - - Apache Log4j 2 - 2.0-beta3 - 2.0-beta3 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta3/log4j-2.0-beta3.pom - - - - - Apache Log4j 2 - 2.0-beta2 - 2.0-beta2 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta2/log4j-2.0-beta2.pom - - - - - Apache Log4j 2 - 2.0-beta1 - 2.0-beta1 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-beta1/log4j-2.0-beta1.pom - - - - - Apache Log4j 2 - 2.0-alpha2 - 2.0-alpha2 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-alpha2/log4j-2.0-alpha2.pom - - - - - Apache Log4j 2 - 2.0-alpha1 - 2.0-alpha1 - https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j/2.0-alpha1/log4j-2.0-alpha1.pom - - - - - - - - - - Bruce Brouwer - - - - - - Gary Gregory - - - - - - Matt Sicker - - - - - - Mikael Ståldal - - - - - - Nick Williams - - - - - - Ralph Goers - - - - - - Remko Popma - - - - - - Scott Deboy - - - - - - - - - - - Murad Ersoy - - - - - - - - - - - Apache Log4j 2 - - The Apache Logging Services Project creates and maintains open-source software related to the logging of - application behavior and released at no charge to the public. - - - Ralph Goers - - - - - - Gary Gregory - - - - - - Matt Sicker - - - - - - Mikael Ståldal - - - - - - Nick Williams - - - - - - Remko Popma - - - - - - Scott Deboy - - - - - - Nextiva - - - - - Spotify - - - - - Rocket Software - - - - - SPR Consulting - - - - \ No newline at end of file diff --git a/docs/2.17.0-interpolation.md b/docs/2.17.0-interpolation.md new file mode 100644 index 00000000000..76c496da3f8 --- /dev/null +++ b/docs/2.17.0-interpolation.md @@ -0,0 +1,156 @@ +I'd like to go into detail on some of the changes in 2.17.0, why they're so important, and how they relate to both [CVE-2021-45046](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45046) and [CVE-2021-45105](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-45105). + +The substitution of untrusted log data allowed access to code that was never meant to be exposed. Lookups should be triggered only by configuration and the logging framework (including custom layout/appender/etc plugins). Not by user-provided inputs. + +## 1. PatternLayout rendered message substitution + +Substitution within the contents of a rendered log message provided the largest opportunity for untrusted inputs to be evaluated by the string replacement system. This was [removed (by default) in 2.15.0](https://github.com/apache/logging-log4j2/commit/001aaada7dab82c3c09cde5f8e14245dc9d8b454), and [removed entirely in 2.16.0](https://github.com/apache/logging-log4j2/commit/27972043b76c9645476f561c5adc483dec6d3f5d) after public disclosure. + +## 2. Recursive Substitution Within Lookups + +Despite message replacements being removed in 2.15.0, other pathways still exist (based on the configuration) which can evaluate untrusted user-provided values. For example, a `PatternLayout` using the pattern `%p %t %c $${ctx:userAgent} %m%n`[1][2] would re-evaluate `config.getStrSubstitutor().replace(event, " ${ctx:userAgent} ")` for each log-event due to the [LiteralPatternConverter](https://github.com/apache/logging-log4j2/blob/cffe58f6a433ea1ab60ceb129d4c9b3377acda1d/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverter.java#L62-L65). The substitutor would evaluate recursively, and invoke unexpected lookups. At worst triggering something like jndi or an exception preventing logging, but even resulting in logging that doesn't match the literal data input is a very serious bug. For example if `MDC.put("userAgent", "${lower:FOO}")` then `${ctx:userAgent}` would evaluate to literal `foo` instead of literal `${lower:FOO}` + +This is not isolated to the PatternLayout, rather anywhere lookups in which the result contained user-provided data. e.g. `${ctx:USER_PROVIDED}`, `${event:Message}`. For example `` would be impacted as well. + +In 2.17.0 we resolved this class of issues with a simple idea: +Recursive evaluation is allowed while parsing the configuration (no user-input/LogEvent data is present, and configuration breaks are to be avoided) however when log-events themselves are being evaluated we _never_ recursively evaluate substitutions. [That's it](https://github.com/apache/logging-log4j2/commit/806023265f8c905b2dd1d81fd2458f64b2ea0b5e). That means when `${lower:${event:Message}}` is evaluated for message `Hello, World!` the result is `hello, world!`, and when evaluated for `${java:version}` the result is the string literal `${java:version}` which itself is not evaluated. + +----- + +1. Note that I used `$${ctx:userAgent}` so the value itself is not evaluated, however even `${ctx:userAgent}` may be vulnerable because it's not likely that a thread-context value is set when the configuration itself is evaluated, in which case no replacement occurs and the value is equivalent to the `$${ctx:userAgent}` example. +2. Please use `%X{userAgent}` in the PatternLayout instead of `${ctx:userAgent}`. It's safer, more obvious, and more efficient. LogEventPatternConverters are pluggable, don't rely on stringly typed data, and are created when the pattern is parsed, not re-scanned on a per-LogEvent basis. + +## 3. Impact Upon Configuration + +There are certainly cases in which this change impacts existing configurations, however I believe that the safety is well worth the trade-off. +For example, [this test configuration](https://github.com/apache/logging-log4j2/commit/806023265f8c905b2dd1d81fd2458f64b2ea0b5e#diff-f13a31d919bf2e7169ca936948aeef1cda6089f295be684d71f2bd5709248475) had to be updated because routes are created based on a log-event, thus we cannot allow recursive evaluation. An argument can be made that the inputs must already be sanitized to avoid path traversal, however the filename isn't the only place these lookups can be evaluated, and the type of sanitization differs dramatically between validating against string replacement data, and validating against unexpected path-based attacks. + +In a perfect world I'd love to swap the types used for substitution to avoid stringly matching anything, using the type system to differentiate between trusted lookups and user-input data, however in such a large, widely-used framework, that's not something we can do, certainly not quickly or easily. + +### Configuration Impact: Examples + +**Complex Lookups :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:** + +This example uses complex lookups based on the [ThreadContext](https://logging.apache.org/log4j/2.x/manual/thread-context.html) (aka MDC), however it continues to work as expected **and** prevents user-provided data from `key1` or `key2` from being substituted. + +```xml +<-- +Note that lookups are all escaped to prevent them from being evaluated +immediately when the Pattern is constructed +--> + +``` + + +**Pattern Reuse Via Lookups :heavy_check_mark: :heavy_check_mark: :heavy_check_mark:** + +This is not impacted either: + +```xml + + %d %p %t %c %m%n + + + + + + +``` + +**Lazy Pattern Lookups Which include Lazy Lookups :x::x::x:** + +However, in this case, evaluation depends on a lazily-replaced property which itself is lazily evaluated: + +```xml + + $${event:Message} + + + + + + +``` + +The upgrade to 2.17.0 would change the output for the following log line: + +```java +LogManager.getLogger(com.example.Main.class).info("example"); +``` + +```diff +- com.example.Main example ++ com.example.Main ${event:Message} +``` + +This can be resolved by unescaping the property reference in the pattern, allowing the pattern itself to be fully evaluated when the configuration is loaded, but preventing message contents from being further interpolated due to the runtime-substitution protections. + +Note that this example is contrived, and in practice one should use the `%m` pattern here rather than a lookup, however this example applies to a general shape of usage and some extrapolation may be required for your configuration. + + +```diff + + $${event:Message} + + + +- ++ + + +``` + +**RoutingAppender with lazy property references in routes :x::x::x:** + +RoutingAppender routes are lazily evaluated when they're needed, which means a LogEvent (thus potentially user-provided data) is available. Everything that could be represented before can still be done, however it's no longer possible to extract some pieces to shared properties. + +Let's take the [example from the test liked above](https://github.com/apache/logging-log4j2/commit/806023265f8c905b2dd1d81fd2458f64b2ea0b5e#diff-f13a31d919bf2e7169ca936948aeef1cda6089f295be684d71f2bd5709248475) + +```xml + + + target/routing1/routingtest-$${sd:type}.log + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + +``` + +The reference to `fileName="${filename}"` is no longer evaluated recursively to `fileName="target/routing1/routingtest-${sd:type}.log"` however the `${sd:type}` is left as a string literal, not expanded as it was before. + +The fix in this case is to inline the `fileName` property value into the `RollingFile` `filename` attribute, removing the extra`$` escaping the `${sd:type}` lookup like so: + +```diff + + + +- + + %d %p %C{1.} [%t] %m%n + + + + + + +``` \ No newline at end of file diff --git a/findbugs-exclude-filter.xml b/findbugs-exclude-filter.xml deleted file mode 100644 index 327be31718b..00000000000 --- a/findbugs-exclude-filter.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/jenkins-toolchains.xml b/jenkins-toolchains.xml deleted file mode 100644 index 2ee96063cfc..00000000000 --- a/jenkins-toolchains.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - jdk - - 1.7 - sun - - - /home/jenkins/tools/java/latest1.7 - - - - jdk - - 1.8 - sun - - - /home/jenkins/tools/java/latest1.8 - - - - jdk - - 9 - sun - - - /home/jenkins/tools/java/jdk-9-b181 - - - - - diff --git a/log4j-1.2-api/pom.xml b/log4j-1.2-api/pom.xml index 3ddf8055ac7..21e454f1523 100644 --- a/log4j-1.2-api/pom.xml +++ b/log4j-1.2-api/pom.xml @@ -20,11 +20,10 @@ org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + 3.0.0-SNAPSHOT log4j-1.2-api - bundle + jar Apache Log4j 1.x Compatibility API The Apache Log4j 1.x Compatibility API @@ -32,78 +31,100 @@ Log4j 1.2 Documentation /log4j12-api org.apache.log4j + true - junit - junit + javax.jms + javax.jms-api + provided + + + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-core + true + + + org.apache.logging.log4j + log4j-api-test + test + + + org.apache.logging.log4j + log4j-core-test + test + + + commons-io + commons-io + test + + + org.apache.commons + commons-lang3 test - org.apache.felix - org.apache.felix.framework + org.hamcrest + hamcrest test - org.eclipse.tycho - org.eclipse.osgi + com.fasterxml.jackson.dataformat + jackson-dataformat-xml test + - org.apache.logging.log4j - log4j-api + javax.activation + javax.activation-api + test + - org.apache.logging.log4j - log4j-api - test-jar + javax.xml.bind + jaxb-api test - org.apache.logging.log4j - log4j-core + org.junit.jupiter + junit-jupiter-engine + test - org.apache.logging.log4j - log4j-core - test-jar + org.junit.jupiter + junit-jupiter-params test - org.apache.velocity - velocity - 1.7 + org.junit.vintage + junit-vintage-engine test - commons-io - commons-io + org.mockito + mockito-core test + - com.fasterxml.jackson.dataformat - jackson-dataformat-xml + oro + oro + test + + + org.apache.velocity + velocity test - - - org.apache.maven.plugins - maven-remote-resources-plugin - - - - process - - - false - - - - org.apache.felix maven-bundle-plugin @@ -111,6 +132,12 @@ org.apache.logging.log4j.core org.apache.log4j.* + + javax.jms;version="[1.1,2)";resolution:=optional, + + com.sun.jdmk.comm;resolution:=optional, + * + @@ -121,7 +148,6 @@ org.apache.maven.plugins maven-changes-plugin - ${changes.plugin.version} @@ -137,7 +163,6 @@ org.apache.maven.plugins maven-checkstyle-plugin - ${checkstyle.plugin.version} ${log4jParentDir}/checkstyle.xml @@ -150,15 +175,15 @@ org.apache.maven.plugins maven-javadoc-plugin - ${javadoc.plugin.version} - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
+ <p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, - and the Apache Log4j logo are trademarks of The Apache Software Foundation.

]]>
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>
false true + 8
@@ -169,22 +194,9 @@
- - org.codehaus.mojo - findbugs-maven-plugin - ${findbugs.plugin.version} - - true - -Duser.language=en - Normal - Default - ${log4jParentDir}/findbugs-exclude-filter.xml - - org.apache.maven.plugins maven-jxr-plugin - ${jxr.plugin.version} non-aggregate @@ -203,12 +215,14 @@ org.apache.maven.plugins maven-pmd-plugin - ${pmd.plugin.version} ${maven.compiler.target} + + com.github.spotbugs + spotbugs-maven-plugin +
- diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java b/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java index a4c52310c2e..2800107687f 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/AppenderSkeleton.java @@ -44,11 +44,9 @@ public abstract class AppenderSkeleton implements Appender, OptionHandler { * Create new instance. */ public AppenderSkeleton() { - super(); } protected AppenderSkeleton(final boolean isActive) { - super(); } @Override @@ -56,7 +54,7 @@ public void activateOptions() { } @Override - public void addFilter(Filter newFilter) { + public void addFilter(final Filter newFilter) { if(headFilter == null) { headFilter = tailFilter = newFilter; } else { @@ -104,72 +102,70 @@ public Priority getThreshold() { return threshold; } - public boolean isAsSevereAsThreshold(Priority priority) { + public boolean isAsSevereAsThreshold(final Priority priority) { return ((threshold == null) || priority.isGreaterOrEqual(threshold)); } - /** - * This method is never going to be called in Log4j 2 so there isn't much point in having any code in it. - * @param event The LoggingEvent. - */ @Override - public void doAppend(LoggingEvent event) { + public synchronized void doAppend(final LoggingEvent event) { + // Threshold checks and filtering is performed by the AppenderWrapper. + append(event); } /** - * Set the {@link ErrorHandler} for this Appender. + * Sets the {@link ErrorHandler} for this Appender. * * @since 0.9.0 */ @Override - public synchronized void setErrorHandler(ErrorHandler eh) { + public synchronized void setErrorHandler(final ErrorHandler eh) { if (eh != null) { this.errorHandler = eh; } } @Override - public void setLayout(Layout layout) { + public void setLayout(final Layout layout) { this.layout = layout; } @Override - public void setName(String name) { + public void setName(final String name) { this.name = name; } - public void setThreshold(Priority threshold) { + public void setThreshold(final Priority threshold) { this.threshold = threshold; } public static class NoOpErrorHandler implements ErrorHandler { @Override - public void setLogger(Logger logger) { + public void setLogger(final Logger logger) { } @Override - public void error(String message, Exception e, int errorCode) { + public void error(final String message, final Exception e, final int errorCode) { } @Override - public void error(String message) { + public void error(final String message) { } @Override - public void error(String message, Exception e, int errorCode, LoggingEvent event) { + public void error(final String message, final Exception e, final int errorCode, final LoggingEvent event) { } @Override - public void setAppender(Appender appender) { + public void setAppender(final Appender appender) { } @Override - public void setBackupAppender(Appender appender) { + public void setBackupAppender(final Appender appender) { } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java index 2b7ec7fa57b..de422174213 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java @@ -16,30 +16,50 @@ */ package org.apache.log4j; +import org.apache.logging.log4j.util.StackLocatorUtil; + /** - * Provided for compatibility with Log4j 1.x. + * Configures the package. + * + *

+ * For file based configuration, see {@link PropertyConfigurator}. For XML based configuration, see + * {@link org.apache.log4j.xml.DOMConfigurator DOMConfigurator}. + *

+ * + * @since 0.8.1 */ public class BasicConfigurator { - protected BasicConfigurator() { - } - + /** + * Adds a {@link ConsoleAppender} that uses {@link PatternLayout} using the + * {@link PatternLayout#TTCC_CONVERSION_PATTERN} and prints to System.out to the root category. + */ public static void configure() { - LogManager.reconfigure(); + LogManager.reconfigure(StackLocatorUtil.getCallerClassLoader(2)); } /** - * No-op implementation. - * @param appender The appender. + * Adds appender to the root category. + * + * @param appender The appender to add to the root category. */ public static void configure(final Appender appender) { - // no-op + LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2)).addAppender(appender); } /** - * No-op implementation. + * Resets the default hierarchy to its default. It is equivalent to calling + * Category.getDefaultHierarchy().resetConfiguration(). + * + * See {@link Hierarchy#resetConfiguration()} for more details. */ public static void resetConfiguration() { - // no-op + LogManager.resetConfiguration(StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * Constructs a new instance. + */ + protected BasicConfigurator() { } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java index 873aa0e3c1a..c35a2cc74e0 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java @@ -16,157 +16,397 @@ */ package org.apache.log4j; +import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; -import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Vector; import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LogEventWrapper; +import org.apache.log4j.helpers.AppenderAttachableImpl; import org.apache.log4j.helpers.NullEnumeration; -import org.apache.log4j.spi.LoggerFactory; +import org.apache.log4j.legacy.core.CategoryUtil; +import org.apache.log4j.or.ObjectRenderer; +import org.apache.log4j.or.RendererMap; +import org.apache.log4j.spi.AppenderAttachable; +import org.apache.log4j.spi.HierarchyEventListener; +import org.apache.log4j.spi.LoggerRepository; import org.apache.log4j.spi.LoggingEvent; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.util.NameUtil; import org.apache.logging.log4j.message.LocalizedMessage; +import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.spi.AbstractLoggerAdapter; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; - /** * Implementation of the Category class for compatibility, despite it having been deprecated a long, long time ago. */ -public class Category { +public class Category implements AppenderAttachable { - private static PrivateAdapter adapter = new PrivateAdapter(); + private static final String FQCN = Category.class.getName(); - private static final Map> CONTEXT_MAP = - new WeakHashMap<>(); + /** + * Tests if the named category exists (in the default hierarchy). + * + * @param name The name to test. + * @return Whether the name exists. + * + * @deprecated Please use {@link LogManager#exists(String)} instead. + * @since 0.8.5 + */ + @Deprecated + public static Logger exists(final String name) { + return LogManager.exists(name, StackLocatorUtil.getCallerClassLoader(2)); + } - private static final String FQCN = Category.class.getName(); + /** + * Returns all the currently defined categories in the default hierarchy as an {@link java.util.Enumeration + * Enumeration}. + * + *

+ * The root category is not included in the returned {@link Enumeration}. + *

+ * + * @return and Enumeration of the Categories. + * + * @deprecated Please use {@link LogManager#getCurrentLoggers()} instead. + */ + @SuppressWarnings("rawtypes") + @Deprecated + public static Enumeration getCurrentCategories() { + return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * Gets the default LoggerRepository instance. + * + * @return the default LoggerRepository instance. + * @deprecated Please use {@link LogManager#getLoggerRepository()} instead. + * @since 1.0 + */ + @Deprecated + public static LoggerRepository getDefaultHierarchy() { + return LogManager.getLoggerRepository(); + } + + public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) { + return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2)); + } + + public static Category getInstance(final String name) { + return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)); + } + + public static Category getRoot() { + return LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2)); + } + + private static String getSubName(final String name) { + if (Strings.isEmpty(name)) { + return null; + } + final int i = name.lastIndexOf('.'); + return i > 0 ? name.substring(0, i) : Strings.EMPTY; + } + + /** + * Shuts down the current configuration. + */ + public static void shutdown() { + // Depth 2 gets the call site of this method. + LogManager.shutdown(StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * The name of this category. + */ + protected String name; + + /** + * Additivity is set to true by default, that is children inherit the appenders of their ancestors by default. If this + * variable is set to false then the appenders found in the ancestors of this category are not used. + * However, the children of this category will inherit its appenders, unless the children have their additivity flag set + * to false too. See the user manual for more details. + */ + protected boolean additive = true; + + /** + * The assigned level of this category. The level variable need not be assigned a value in which case it is + * inherited form the hierarchy. + */ + volatile protected Level level; + + private RendererMap rendererMap; + + /** + * The parent of this category. All categories have at least one ancestor which is the root category. + */ + volatile protected Category parent; /** * Resource bundle for localized messages. */ - protected ResourceBundle bundle = null; + protected ResourceBundle bundle; - private final org.apache.logging.log4j.core.Logger logger; + private final org.apache.logging.log4j.Logger logger; + + /** Categories need to know what Hierarchy they are in. */ + protected LoggerRepository repository; + + AppenderAttachableImpl aai; /** * Constructor used by Logger to specify a LoggerContext. + * * @param context The LoggerContext. * @param name The name of the Logger. */ protected Category(final LoggerContext context, final String name) { + this.name = name; this.logger = context.getLogger(name); + this.repository = LogManager.getLoggerRepository(); + // this.rendererMap = ((RendererSupport) repository).getRendererMap(); + } + + Category(final org.apache.logging.log4j.Logger logger) { + this.logger = logger; + // rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap(); } /** * Constructor exposed by Log4j 1.2. + * * @param name The name of the Logger. */ protected Category(final String name) { - this(PrivateManager.getContext(), name); + this(Hierarchy.getContext(), name); } - private Category(final org.apache.logging.log4j.core.Logger logger) { - this.logger = logger; + /** + * Add newAppender to the list of appenders of this Category instance. + *

+ * If newAppender is already in the list of appenders, then it won't be added again. + *

+ */ + @Override + public void addAppender(final Appender appender) { + if (appender != null) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.addAppender(logger, AppenderAdapter.adapt(appender)); + } else { + synchronized (this) { + if (aai == null) { + aai = new AppenderAttachableImpl(); + } + aai.addAppender(appender); + } + } + repository.fireAddAppenderEvent(this, appender); + } } - public static Category getInstance(final String name) { - return getInstance(PrivateManager.getContext(), name, adapter); + /** + * If assertion parameter is {@code false}, then logs msg as an {@link #error(Object) error} + * statement. + * + *

+ * The assert method has been renamed to assertLog because assert is a language + * reserved word in JDK 1.4. + *

+ * + * @param assertion The assertion. + * @param msg The message to print if assertion is false. + * + * @since 1.2 + */ + public void assertLog(final boolean assertion, final String msg) { + if (!assertion) { + this.error(msg); + } } - static Logger getInstance(final LoggerContext context, final String name) { - return getInstance(context, name, adapter); + /** + * Call the appenders in the hierrachy starting at this. If no appenders could be found, emit a warning. + *

+ * This method calls all the appenders inherited from the hierarchy circumventing any evaluation of whether to log or + * not to log the particular log request. + *

+ * + * @param event the event to log. + */ + public void callAppenders(final LoggingEvent event) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.log(logger, new LogEventWrapper(event)); + return; + } + int writes = 0; + for (Category c = this; c != null; c = c.parent) { + // Protected against simultaneous call to addAppender, removeAppender,... + synchronized (c) { + if (c.aai != null) { + writes += c.aai.appendLoopOnAppenders(event); + } + if (!c.additive) { + break; + } + } + } + if (writes == 0) { + repository.emitNoAppenderWarning(this); + } } - static Logger getInstance(final LoggerContext context, final String name, final LoggerFactory factory) { - final ConcurrentMap loggers = getLoggersMap(context); - Logger logger = loggers.get(name); - if (logger != null) { - return logger; + /** + * Closes all attached appenders implementing the AppenderAttachable interface. + * + * @since 1.0 + */ + synchronized void closeNestedAppenders() { + final Enumeration enumeration = this.getAllAppenders(); + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + final Appender a = (Appender) enumeration.nextElement(); + if (a instanceof AppenderAttachable) { + a.close(); + } + } } - logger = factory.makeNewLoggerInstance(name); - final Logger prev = loggers.putIfAbsent(name, logger); - return prev == null ? logger : prev; } - static Logger getInstance(final LoggerContext context, final String name, final PrivateAdapter factory) { - final ConcurrentMap loggers = getLoggersMap(context); - Logger logger = loggers.get(name); - if (logger != null) { - return logger; - } - logger = factory.newLogger(name, context); - final Logger prev = loggers.putIfAbsent(name, logger); - return prev == null ? logger : prev; + public void debug(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, null); } - public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) { - return getInstance(clazz.getName()); + public void debug(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, t); } - static Logger getInstance(final LoggerContext context, @SuppressWarnings("rawtypes") final Class clazz) { - return getInstance(context, clazz.getName()); + public void error(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, null); } - public final String getName() { - return logger.getName(); + public void error(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, t); } - org.apache.logging.log4j.core.Logger getLogger() { - return logger; + public void fatal(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, null); } - public final Category getParent() { - final org.apache.logging.log4j.core.Logger parent = logger.getParent(); - if (parent == null) { - return null; + public void fatal(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, t); + } + + /** + * LoggerRepository forgot the fireRemoveAppenderEvent method, if using the stock Hierarchy implementation, then call + * its fireRemove. Custom repositories can implement HierarchyEventListener if they want remove notifications. + * + * @param appender appender, may be null. + */ + private void fireRemoveAppenderEvent(final Appender appender) { + if (appender != null) { + if (repository instanceof Hierarchy) { + ((Hierarchy) repository).fireRemoveAppenderEvent(this, appender); + } else if (repository instanceof HierarchyEventListener) { + ((HierarchyEventListener) repository).removeAppenderEvent(this, appender); + } } - final ConcurrentMap loggers = getLoggersMap(logger.getContext()); - final Logger l = loggers.get(parent.getName()); - return l == null ? new Category(parent) : l; } - public static Category getRoot() { - return getInstance(Strings.EMPTY); + private static Message createMessage(Object message) { + if (message instanceof String) { + return new SimpleMessage((String) message); + } + if (message instanceof CharSequence) { + return new SimpleMessage((CharSequence) message); + } + if (message instanceof Map) { + return new MapMessage<>((Map) message); + } + if (message instanceof Message) { + return (Message) message; + } + return new ObjectMessage(message); } - static Logger getRoot(final LoggerContext context) { - return getInstance(context, Strings.EMPTY); + public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) { + final org.apache.logging.log4j.Level lvl = level.getVersion2Level(); + final Message msg = createMessage(message); + if (logger instanceof ExtendedLogger) { + ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t); + } else { + logger.log(lvl, msg, t); + } } - private static ConcurrentMap getLoggersMap(final LoggerContext context) { - synchronized (CONTEXT_MAP) { - ConcurrentMap map = CONTEXT_MAP.get(context); - if (map == null) { - map = new ConcurrentHashMap<>(); - CONTEXT_MAP.put(context, map); + private ObjectRenderer get(final Class clazz) { + ObjectRenderer renderer = null; + for (Class c = clazz; c != null; c = c.getSuperclass()) { + renderer = rendererMap.get(c); + if (renderer != null) { + return renderer; + } + renderer = searchInterfaces(c); + if (renderer != null) { + return renderer; } - return map; } + return null; } - /** - Returns all the currently defined categories in the default - hierarchy as an {@link java.util.Enumeration Enumeration}. + public boolean getAdditivity() { + return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false; + } -

The root category is not included in the returned - {@link Enumeration}. - @return and Enumeration of the Categories. + /** + * Get all the Log4j 1.x appenders contained in this category as an + * {@link Enumeration}. Log4j 2.x appenders are omitted. + * + * @return Enumeration An enumeration of the appenders in this category. + */ + @Override + @SuppressWarnings("unchecked") + public Enumeration getAllAppenders() { + if (LogManager.isLog4jCorePresent()) { + final Collection appenders = CategoryUtil.getAppenders(logger) + .values(); + return Collections.enumeration(appenders.stream() + // omit native Log4j 2.x appenders + .filter(AppenderAdapter.Adapter.class::isInstance) + .map(AppenderWrapper::adapt) + .collect(Collectors.toSet())); + } + return aai == null ? NullEnumeration.getInstance() : aai.getAllAppenders(); + } - @deprecated Please use {@link LogManager#getCurrentLoggers()} instead. + /** + * Look for the appender named as name. + *

+ * Return the appender with that name if in the list. Return null otherwise. + *

*/ - @SuppressWarnings("rawtypes") - @Deprecated - public static Enumeration getCurrentCategories() { - return LogManager.getCurrentLoggers(); + @Override + public Appender getAppender(final String name) { + if (LogManager.isLog4jCorePresent()) { + return AppenderWrapper.adapt(CategoryUtil.getAppenders(logger).get(name)); + } + return aai != null ? aai.getAppender(name) : null; } - public final Level getEffectiveLevel() { + public Priority getChainedPriority() { + return getEffectiveLevel(); + } + + public Level getEffectiveLevel() { switch (logger.getLevel().getStandardLevel()) { case ALL: return Level.ALL; @@ -182,307 +422,288 @@ public final Level getEffectiveLevel() { return Level.ERROR; case FATAL: return Level.FATAL; - case OFF: - return Level.OFF; default: // TODO Should this be an IllegalStateException? return Level.OFF; } } - public final Priority getChainedPriority() { - return getEffectiveLevel(); + /** + * Gets the {@link LoggerRepository} where this Category instance is attached. + * + * @deprecated Please use {@link #getLoggerRepository()} instead. + * @since 1.1 + */ + @Deprecated + public LoggerRepository getHierarchy() { + return repository; } public final Level getLevel() { return getEffectiveLevel(); } - public void setLevel(final Level level) { - logger.setLevel(org.apache.logging.log4j.Level.toLevel(level.levelStr)); + private String getLevelStr(final Priority priority) { + return priority == null ? null : priority.levelStr; } - public final Level getPriority() { - return getEffectiveLevel(); + org.apache.logging.log4j.Logger getLogger() { + return logger; } - public void setPriority(final Priority priority) { - logger.setLevel(org.apache.logging.log4j.Level.toLevel(priority.levelStr)); + /** + * Gets the {@link LoggerRepository} where this Category is attached. + * + * @since 1.2 + */ + public LoggerRepository getLoggerRepository() { + return repository; } - public void debug(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, null); + public final String getName() { + return logger.getName(); } - public void debug(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, t); + public final Category getParent() { + if (!LogManager.isLog4jCorePresent()) { + return null; + } + final org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger); + final LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger); + if (parent == null || loggerContext == null) { + return null; + } + final ConcurrentMap loggers = Hierarchy.getLoggersMap(loggerContext); + final Logger parentLogger = loggers.get(parent.getName()); + return parentLogger == null ? new Category(parent) : parentLogger; } - public boolean isDebugEnabled() { - return logger.isDebugEnabled(); + public final Level getPriority() { + return getEffectiveLevel(); } - public void error(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, null); + public ResourceBundle getResourceBundle() { + if (bundle != null) { + return bundle; + } + String name = logger.getName(); + if (LogManager.isLog4jCorePresent()) { + final LoggerContext ctx = CategoryUtil.getLoggerContext(logger); + if (ctx != null) { + final ConcurrentMap loggers = Hierarchy.getLoggersMap(ctx); + while ((name = getSubName(name)) != null) { + final Logger subLogger = loggers.get(name); + if (subLogger != null) { + final ResourceBundle rb = subLogger.bundle; + if (rb != null) { + return rb; + } + } + } + } + } + return null; } - public void error(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, t); + public void info(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null); } - public boolean isErrorEnabled() { - return logger.isErrorEnabled(); + public void info(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t); } - public void warn(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null); + /** + * Is the appender passed as parameter attached to this category? + * + * @param appender The Appender to add. + * @return true if the appender is attached. + */ + @Override + public boolean isAttached(final Appender appender) { + return aai == null ? false : aai.isAttached(appender); } - public void warn(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t); + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); } - public boolean isWarnEnabled() { - return logger.isWarnEnabled(); + private boolean isEnabledFor(final org.apache.logging.log4j.Level level) { + return logger.isEnabled(level); } - public void fatal(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, null); + public boolean isEnabledFor(final Priority level) { + return isEnabledFor(level.getVersion2Level()); } - public void fatal(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, t); + public boolean isErrorEnabled() { + return logger.isErrorEnabled(); } public boolean isFatalEnabled() { return logger.isFatalEnabled(); } - public void info(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null); - } - - public void info(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t); - } - public boolean isInfoEnabled() { return logger.isInfoEnabled(); } - public void trace(final Object message) { - maybeLog(FQCN, org.apache.logging.log4j.Level.TRACE, message, null); - } - - public void trace(final Object message, final Throwable t) { - maybeLog(FQCN, org.apache.logging.log4j.Level.TRACE, message, t); - } - - public boolean isTraceEnabled() { - return logger.isTraceEnabled(); + public boolean isWarnEnabled() { + return logger.isWarnEnabled(); } - public boolean isEnabledFor(final Priority level) { - final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString()); - return isEnabledFor(lvl); + public void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) { + if (isEnabledFor(priority)) { + final Message msg = new LocalizedMessage(bundle, key, params); + forcedLog(FQCN, priority, msg, t); + } } - /** - * No-op implementation. - * @param appender The Appender to add. - */ - public void addAppender(final Appender appender) { + public void l7dlog(final Priority priority, final String key, final Throwable t) { + if (isEnabledFor(priority)) { + final Message msg = new LocalizedMessage(bundle, key, null); + forcedLog(FQCN, priority, msg, t); + } } - /** - * No-op implementation. - * @param event The logging event. - */ - public void callAppenders(final LoggingEvent event) { + public void log(final Priority priority, final Object message) { + if (isEnabledFor(priority)) { + forcedLog(FQCN, priority, message, null); + } } - @SuppressWarnings("rawtypes") - public Enumeration getAllAppenders() { - return NullEnumeration.getInstance(); + public void log(final Priority priority, final Object message, final Throwable t) { + if (isEnabledFor(priority)) { + forcedLog(FQCN, priority, message, t); + } } - /** - * No-op implementation. - * @param name The name of the Appender. - * @return null. - */ - public Appender getAppender(final String name) { - return null; + public void log(final String fqcn, final Priority priority, final Object message, final Throwable t) { + if (isEnabledFor(priority)) { + forcedLog(fqcn, priority, message, t); + } } - /** - Is the appender passed as parameter attached to this category? - * @param appender The Appender to add. - * @return true if the appender is attached. - */ - public boolean isAttached(final Appender appender) { - return false; + void maybeLog(final String fqcn, final org.apache.logging.log4j.Level level, final Object message, final Throwable throwable) { + if (logger.isEnabled(level)) { + final Message msg = createMessage(message); + if (logger instanceof ExtendedLogger) { + ((ExtendedLogger) logger).logMessage(fqcn, level, null, msg, throwable); + } else { + logger.log(level, msg, throwable); + } + } } /** - * No-op implementation. + * Removes all previously added appenders from this Category instance. + *

+ * This is useful when re-reading configuration information. + *

*/ + @Override public void removeAllAppenders() { + if (aai != null) { + final Vector appenders = new Vector(); + for (final Enumeration iter = aai.getAllAppenders(); iter != null && iter.hasMoreElements();) { + appenders.add(iter.nextElement()); + } + aai.removeAllAppenders(); + for (final Object appender : appenders) { + fireRemoveAppenderEvent((Appender) appender); + } + aai = null; + } } /** - * No-op implementation. + * Removes the appender passed as parameter form the list of appenders. + * * @param appender The Appender to remove. + * @since 0.8.2 */ + @Override public void removeAppender(final Appender appender) { + if (appender == null || aai == null) { + return; + } + final boolean wasAttached = aai.isAttached(appender); + aai.removeAppender(appender); + if (wasAttached) { + fireRemoveAppenderEvent(appender); + } } /** - * No-op implementation. + * Removes the appender with the name passed as parameter form the list of appenders. + * * @param name The Appender to remove. + * @since 0.8.2 */ + @Override public void removeAppender(final String name) { - } - - /** - * No-op implementation. - */ - public static void shutdown() { - } - - - public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) { - final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString()); - final Message msg = message instanceof Message ? (Message) message : new ObjectMessage(message); - logger.logMessage(fqcn, lvl, null, msg, t); - } - - public boolean exists(final String name) { - return PrivateManager.getContext().hasLogger(name); - } - - public boolean getAdditivity() { - return logger.isAdditive(); - } - - public void setAdditivity(final boolean additivity) { - logger.setAdditive(additivity); - } - - public void setResourceBundle(final ResourceBundle bundle) { - this.bundle = bundle; - } - - public ResourceBundle getResourceBundle() { - if (bundle != null) { - return bundle; + if (name == null || aai == null) { + return; } - String name = logger.getName(); - final ConcurrentMap loggers = getLoggersMap(logger.getContext()); - while ((name = NameUtil.getSubName(name)) != null) { - final Logger subLogger = loggers.get(name); - if (subLogger != null) { - final ResourceBundle rb = subLogger.bundle; - if (rb != null) { - return rb; - } - } + final Appender appender = aai.getAppender(name); + aai.removeAppender(name); + if (appender != null) { + fireRemoveAppenderEvent(appender); } - return null; } - /** - If assertion parameter is {@code false}, then - logs msg as an {@link #error(Object) error} statement. - -

The assert method has been renamed to - assertLog because assert is a language - reserved word in JDK 1.4. - - @param assertion The assertion. - @param msg The message to print if assertion is - false. - - @since 1.2 - */ - public void assertLog(final boolean assertion, final String msg) { - if (!assertion) { - this.error(msg); + ObjectRenderer searchInterfaces(final Class c) { + ObjectRenderer renderer = rendererMap.get(c); + if (renderer != null) { + return renderer; } - } - - public void l7dlog(final Priority priority, final String key, final Throwable t) { - if (isEnabledFor(priority)) { - final Message msg = new LocalizedMessage(bundle, key, null); - forcedLog(FQCN, priority, msg, t); + final Class[] ia = c.getInterfaces(); + for (final Class clazz : ia) { + renderer = searchInterfaces(clazz); + if (renderer != null) { + return renderer; + } } + return null; } - public void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) { - if (isEnabledFor(priority)) { - final Message msg = new LocalizedMessage(bundle, key, params); - forcedLog(FQCN, priority, msg, t); + public void setAdditivity(final boolean additivity) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.setAdditivity(logger, additivity); } } - public void log(final Priority priority, final Object message, final Throwable t) { - if (isEnabledFor(priority)) { - final Message msg = new ObjectMessage(message); - forcedLog(FQCN, priority, msg, t); - } + /** + * Only the Hiearchy class can set the hiearchy of a category. Default package access is MANDATORY here. + */ + final void setHierarchy(final LoggerRepository repository) { + this.repository = repository; } - public void log(final Priority priority, final Object message) { - if (isEnabledFor(priority)) { - final Message msg = new ObjectMessage(message); - forcedLog(FQCN, priority, msg, null); - } + public void setLevel(final Level level) { + setLevel(level != null ? level.getVersion2Level() : null); } - public void log(final String fqcn, final Priority priority, final Object message, final Throwable t) { - if (isEnabledFor(priority)) { - final Message msg = new ObjectMessage(message); - forcedLog(fqcn, priority, msg, t); + private void setLevel(final org.apache.logging.log4j.Level level) { + if (LogManager.isLog4jCorePresent()) { + CategoryUtil.setLevel(logger, level); } } - private void maybeLog(final String fqcn, final org.apache.logging.log4j.Level level, - final Object message, final Throwable throwable) { - if (logger.isEnabled(level, null, message, throwable)) { - logger.logMessage(FQCN, level, null, new ObjectMessage(message), throwable); - } + public void setPriority(final Priority priority) { + setLevel(priority != null ? priority.getVersion2Level() : null); } - private static class PrivateAdapter extends AbstractLoggerAdapter { - - @Override - protected Logger newLogger(final String name, final org.apache.logging.log4j.spi.LoggerContext context) { - return new Logger((LoggerContext) context, name); - } - - @Override - protected org.apache.logging.log4j.spi.LoggerContext getContext() { - return PrivateManager.getContext(); - } + public void setResourceBundle(final ResourceBundle bundle) { + this.bundle = bundle; } - /** - * Private LogManager. - */ - private static class PrivateManager extends org.apache.logging.log4j.LogManager { - private static final String FQCN = Category.class.getName(); - - public static LoggerContext getContext() { - return (LoggerContext) getContext(FQCN, false); - } - - public static org.apache.logging.log4j.Logger getLogger(final String name) { - return getLogger(FQCN, name); - } + public void warn(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null); } - private boolean isEnabledFor(final org.apache.logging.log4j.Level level) { - return logger.isEnabled(level, null, null); + public void warn(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t); } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/CategoryKey.java b/log4j-1.2-api/src/main/java/org/apache/log4j/CategoryKey.java new file mode 100644 index 00000000000..048c6b1ca9d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/CategoryKey.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +/** + * CategoryKey is a wrapper for String that apparently accelerated hash table lookup in early JVM's. + */ +class CategoryKey { + + String name; + int hashCache; + + CategoryKey(final String name) { + this.name = name; + this.hashCache = name.hashCode(); + } + + @Override + final public int hashCode() { + return hashCache; + } + + @Override + final public boolean equals(final Object rArg) { + if (this == rArg) { + return true; + } + + if (rArg != null && CategoryKey.class == rArg.getClass()) { + return name.equals(((CategoryKey) rArg).name); + } else { + return false; + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/ConsoleAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/ConsoleAppender.java new file mode 100644 index 00000000000..fb9c48ce938 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/ConsoleAppender.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Placeholder for Log4j 1.2 Console Appender. + */ +public class ConsoleAppender extends WriterAppender { + + public static final String SYSTEM_OUT = "System.out"; + public static final String SYSTEM_ERR = "System.err"; + + protected String target = SYSTEM_OUT; + + /** + * Determines if the appender honors reassignments of System.out or System.err made after configuration. + */ + private boolean follow; + + /** + * Constructs a non-configured appender. + */ + public ConsoleAppender() { + } + + /** + * Constructs a configured appender. + * + * @param layout layout, may not be null. + */ + public ConsoleAppender(final Layout layout) { + this(layout, SYSTEM_OUT); + } + + /** + * Constructs a configured appender. + * + * @param layout layout, may not be null. + * @param target target, either "System.err" or "System.out". + */ + public ConsoleAppender(final Layout layout, final String target) { + setLayout(layout); + setTarget(target); + activateOptions(); + } + + /** + * {@inheritDoc} + */ + @Override + public void append(final LoggingEvent theEvent) { + // NOOP + } + + /** + * {@inheritDoc} + */ + @Override + public void close() { + // NOOP + } + + /** + * Gets whether the appender honors reassignments of System.out or System.err made after configuration. + * + * @return true if appender will use value of System.out or System.err in force at the time when logging events are + * appended. + * @since 1.2.13 + */ + public boolean getFollow() { + return follow; + } + + /** + * Gets the current value of the Target property. The default value of the option is "System.out". + * + * See also {@link #setTarget}. + */ + public String getTarget() { + return target; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean requiresLayout() { + return false; + } + + /** + * Sets whether the appender honors reassignments of System.out or System.err made after configuration. + * + * @param follow if true, appender will use value of System.out or System.err in force at the time when logging events + * are appended. + * @since 1.2.13 + */ + public void setFollow(boolean follow) { + this.follow = follow; + } + + /** + * Sets the value of the Target option. Recognized values are "System.out" and "System.err". Any other value will + * be ignored. + */ + public void setTarget(final String value) { + final String v = value.trim(); + + if (SYSTEM_OUT.equalsIgnoreCase(v)) { + target = SYSTEM_OUT; + } else if (SYSTEM_ERR.equalsIgnoreCase(v)) { + target = SYSTEM_ERR; + } else { + targetWarn(value); + } + } + + void targetWarn(final String val) { + StatusLogger.getLogger().warn("[" + val + "] should be System.out or System.err."); + StatusLogger.getLogger().warn("Using previously set target, System.out by default."); + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultCategoryFactory.java b/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultCategoryFactory.java new file mode 100644 index 00000000000..cb443eadbd5 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultCategoryFactory.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import org.apache.log4j.spi.LoggerFactory; + +class DefaultCategoryFactory implements LoggerFactory { + + DefaultCategoryFactory() { + } + + @Override + public Logger makeNewLoggerInstance(final String name) { + return new Logger(name); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultThrowableRenderer.java b/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultThrowableRenderer.java new file mode 100644 index 00000000000..1913f9c34db --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/DefaultThrowableRenderer.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; + +import org.apache.log4j.spi.ThrowableRenderer; + +/** + * Default implementation of {@link ThrowableRenderer} using {@link Throwable#printStackTrace(PrintWriter)}. + * + * @since 1.2.16 + */ +public final class DefaultThrowableRenderer implements ThrowableRenderer { + + /** + * Render throwable using Throwable.printStackTrace. + * + * @param throwable throwable, may not be null. + * @return string representation. + */ + public static String[] render(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + try { + throwable.printStackTrace(pw); + } catch (final RuntimeException ex) { + // ignore + } + pw.flush(); + final LineNumberReader reader = new LineNumberReader(new StringReader(sw.toString())); + final ArrayList lines = new ArrayList<>(); + try { + String line = reader.readLine(); + while (line != null) { + lines.add(line); + line = reader.readLine(); + } + } catch (final IOException ex) { + if (ex instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + lines.add(ex.toString()); + } + final String[] tempRep = new String[lines.size()]; + lines.toArray(tempRep); + return tempRep; + } + + /** + * Construct new instance. + */ + public DefaultThrowableRenderer() { + // empty + } + + /** + * {@inheritDoc} + */ + @Override + public String[] doRender(final Throwable throwable) { + return render(throwable); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java new file mode 100644 index 00000000000..40f46a9b779 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/FileAppender.java @@ -0,0 +1,306 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.Writer; + +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.QuietWriter; +import org.apache.log4j.spi.ErrorCode; + +/** + * FileAppender appends log events to a file. + *

+ * Support for java.io.Writer and console appending has been deprecated and then removed. See the + * replacement solutions: {@link WriterAppender} and {@link ConsoleAppender}. + *

+ */ +public class FileAppender extends WriterAppender { + + /** + * Controls file truncatation. The default value for this variable is true, meaning that by default a + * FileAppender will append to an existing file and not truncate it. + *

+ * This option is meaningful only if the FileAppender opens the file. + *

+ */ + protected boolean fileAppend = true; + + /** + * The name of the log file. + */ + protected String fileName = null; + + /** + * Do we do bufferedIO? + */ + protected boolean bufferedIO = false; + + /** + * Determines the size of IO buffer be. Default is 8K. + */ + protected int bufferSize = 8 * 1024; + + /** + * The default constructor does not do anything. + */ + public FileAppender() { + } + + /** + * Constructs a FileAppender and open the file designated by filename. The opened filename will become the + * output destination for this appender. + *

+ * The file will be appended to. + *

+ */ + public FileAppender(Layout layout, String filename) throws IOException { + this(layout, filename, true); + } + + /** + * Constructs a FileAppender and open the file designated by filename. The opened filename will become the + * output destination for this appender. + *

+ * If the append parameter is true, the file will be appended to. Otherwise, the file designated by + * filename will be truncated before being opened. + *

+ */ + public FileAppender(Layout layout, String filename, boolean append) throws IOException { + this.layout = layout; + this.setFile(filename, append, false, bufferSize); + } + + /** + * Constructs a FileAppender and open the file designated by filename. The opened filename + * will become the output destination for this appender. + *

+ * If the append parameter is true, the file will be appended to. Otherwise, the file designated by + * filename will be truncated before being opened. + *

+ *

+ * If the bufferedIO parameter is true, then buffered IO will be used to write to the output + * file. + *

+ */ + public FileAppender(Layout layout, String filename, boolean append, boolean bufferedIO, int bufferSize) throws IOException { + this.layout = layout; + this.setFile(filename, append, bufferedIO, bufferSize); + } + + /** + * If the value of File is not null, then {@link #setFile} is called with the values of File + * and Append properties. + * + * @since 0.8.1 + */ + public void activateOptions() { + if (fileName != null) { + try { + setFile(fileName, fileAppend, bufferedIO, bufferSize); + } catch (java.io.IOException e) { + errorHandler.error("setFile(" + fileName + "," + fileAppend + ") call failed.", e, ErrorCode.FILE_OPEN_FAILURE); + } + } else { + // LogLog.error("File option not set for appender ["+name+"]."); + LogLog.warn("File option not set for appender [" + name + "]."); + LogLog.warn("Are you using FileAppender instead of ConsoleAppender?"); + } + } + + /** + * Closes the previously opened file. + */ + protected void closeFile() { + if (this.qw != null) { + try { + this.qw.close(); + } catch (java.io.IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + // Exceptionally, it does not make sense to delegate to an + // ErrorHandler. Since a closed appender is basically dead. + LogLog.error("Could not close " + qw, e); + } + } + } + + /** + * Returns the value of the Append option. + */ + public boolean getAppend() { + return fileAppend; + } + + /** + * Get the value of the BufferedIO option. + * + *

+ * BufferedIO will significatnly increase performance on heavily loaded systems. + *

+ */ + public boolean getBufferedIO() { + return this.bufferedIO; + } + + /** + * Get the size of the IO buffer. + */ + public int getBufferSize() { + return this.bufferSize; + } + + /** Returns the value of the File option. */ + public String getFile() { + return fileName; + } + + /** + * Close any previously opened file and call the parent's reset. + */ + protected void reset() { + closeFile(); + this.fileName = null; + super.reset(); + } + + /** + * The Append option takes a boolean value. It is set to true by default. If true, then + * File will be opened in append mode by {@link #setFile setFile} (see above). Otherwise, {@link #setFile + * setFile} will open File in truncate mode. + * + *

+ * Note: Actual opening of the file is made when {@link #activateOptions} is called, not when the options are set. + *

+ */ + public void setAppend(boolean flag) { + fileAppend = flag; + } + + /** + * The BufferedIO option takes a boolean value. It is set to false by default. If true, then + * File will be opened and the resulting {@link java.io.Writer} wrapped around a {@link BufferedWriter}. + * + * BufferedIO will significatnly increase performance on heavily loaded systems. + * + */ + public void setBufferedIO(boolean bufferedIO) { + this.bufferedIO = bufferedIO; + if (bufferedIO) { + immediateFlush = false; + } + } + + /** + * Set the size of the IO buffer. + */ + public void setBufferSize(int bufferSize) { + this.bufferSize = bufferSize; + } + + /** + * The File property takes a string value which should be the name of the file to append to. + *

+ * Note that the special values "System.out" or "System.err" are no longer honored. + *

+ *

+ * Note: Actual opening of the file is made when {@link #activateOptions} is called, not when the options are set. + *

+ */ + public void setFile(String file) { + // Trim spaces from both ends. The users probably does not want + // trailing spaces in file names. + String val = file.trim(); + fileName = val; + } + + /** + * Sets and opens the file where the log output will go. The specified file must be writable. + *

+ * If there was already an opened file, then the previous file is closed first. + *

+ *

+ * Do not use this method directly. To configure a FileAppender or one of its subclasses, set its properties one by + * one and then call activateOptions. + *

+ * + * @param fileName The path to the log file. + * @param append If true will append to fileName. Otherwise will truncate fileName. + */ + public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) throws IOException { + LogLog.debug("setFile called: " + fileName + ", " + append); + + // It does not make sense to have immediate flush and bufferedIO. + if (bufferedIO) { + setImmediateFlush(false); + } + + reset(); + FileOutputStream ostream = null; + try { + // + // attempt to create file + // + ostream = new FileOutputStream(fileName, append); + } catch (FileNotFoundException ex) { + // + // if parent directory does not exist then + // attempt to create it and try to create file + // see bug 9150 + // + String parentName = new File(fileName).getParent(); + if (parentName != null) { + File parentDir = new File(parentName); + if (!parentDir.exists() && parentDir.mkdirs()) { + ostream = new FileOutputStream(fileName, append); + } else { + throw ex; + } + } else { + throw ex; + } + } + Writer fw = createWriter(ostream); + if (bufferedIO) { + fw = new BufferedWriter(fw, bufferSize); + } + this.setQWForFiles(fw); + this.fileName = fileName; + this.fileAppend = append; + this.bufferedIO = bufferedIO; + this.bufferSize = bufferSize; + writeHeader(); + LogLog.debug("setFile ended"); + } + + /** + * Sets the quiet writer being used. + * + * This method is overriden by {@link RollingFileAppender}. + */ + protected void setQWForFiles(Writer writer) { + this.qw = new QuietWriter(writer, errorHandler); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java new file mode 100644 index 00000000000..a40b33bd539 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java @@ -0,0 +1,606 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.legacy.core.ContextUtil; +import org.apache.log4j.or.ObjectRenderer; +import org.apache.log4j.or.RendererMap; +import org.apache.log4j.spi.HierarchyEventListener; +import org.apache.log4j.spi.LoggerFactory; +import org.apache.log4j.spi.LoggerRepository; +import org.apache.log4j.spi.RendererSupport; +import org.apache.log4j.spi.ThrowableRenderer; +import org.apache.log4j.spi.ThrowableRendererSupport; +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.spi.AbstractLoggerAdapter; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.util.StackLocatorUtil; + +/** + * This class is specialized in retrieving loggers by name and also maintaining the logger hierarchy. + * + *

+ * The casual user does not have to deal with this class directly. + *

+ *

+ * The structure of the logger hierarchy is maintained by the {@link #getLogger} method. The hierarchy is such that + * children link to their parent but parents do not have any pointers to their children. Moreover, loggers can be + * instantiated in any order, in particular descendant before ancestor. + *

+ *

+ * In case a descendant is created before a particular ancestor, then it creates a provision node for the ancestor and + * adds itself to the provision node. Other descendants of the same ancestor add themselves to the previously created + * provision node. + *

+ */ +public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport { + + private static class PrivateLoggerAdapter extends AbstractLoggerAdapter { + + @Override + protected org.apache.logging.log4j.spi.LoggerContext getContext() { + return PrivateLogManager.getContext(); + } + + @Override + protected Logger newLogger(final String name, final org.apache.logging.log4j.spi.LoggerContext context) { + return new Logger(context, name); + } + } + + /** + * Private LogManager. + */ + private static class PrivateLogManager extends org.apache.logging.log4j.LogManager { + private static final String FQCN = Hierarchy.class.getName(); + + public static LoggerContext getContext() { + return getContext(FQCN, false); + } + + public static org.apache.logging.log4j.Logger getLogger(final String name) { + return getLogger(FQCN, name); + } + } + + private static final PrivateLoggerAdapter LOGGER_ADAPTER = new PrivateLoggerAdapter(); + + private static final WeakHashMap> CONTEXT_MAP = new WeakHashMap<>(); + + static LoggerContext getContext() { + return PrivateLogManager.getContext(); + } + + static Logger getInstance(final LoggerContext context, final String name) { + return getInstance(context, name, LOGGER_ADAPTER); + } + + static Logger getInstance(final LoggerContext context, final String name, final LoggerFactory factory) { + return getLoggersMap(context).computeIfAbsent(name, k -> factory.makeNewLoggerInstance(name)); + } + + static Logger getInstance(final LoggerContext context, final String name, final PrivateLoggerAdapter factory) { + return getLoggersMap(context).computeIfAbsent(name, k -> factory.newLogger(name, context)); + } + + static ConcurrentMap getLoggersMap(final LoggerContext context) { + synchronized (CONTEXT_MAP) { + return CONTEXT_MAP.computeIfAbsent(context, k -> new ConcurrentHashMap<>()); + } + } + + static Logger getRootLogger(final LoggerContext context) { + return getInstance(context, org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); + } + + private final LoggerFactory defaultFactory; + private final Vector listeners; + Hashtable ht; + Logger root; + RendererMap rendererMap; + int thresholdInt; + Level threshold; + boolean emittedNoAppenderWarning; + + boolean emittedNoResourceBundleWarning; + + private ThrowableRenderer throwableRenderer; + + /** + * Creates a new logger hierarchy. + * + * @param root The root of the new hierarchy. + * + */ + public Hierarchy(final Logger root) { + ht = new Hashtable(); + listeners = new Vector(1); + this.root = root; + // Enable all level levels by default. + setThreshold(Level.ALL); + this.root.setHierarchy(this); + rendererMap = new RendererMap(); + defaultFactory = new DefaultCategoryFactory(); + } + + @Override + public void addHierarchyEventListener(final HierarchyEventListener listener) { + if (listeners.contains(listener)) { + LogLog.warn("Ignoring attempt to add an existent listener."); + } else { + listeners.addElement(listener); + } + } + + /** + * Adds an object renderer for a specific class. + */ + public void addRenderer(final Class classToRender, final ObjectRenderer or) { + rendererMap.put(classToRender, or); + } + + /** + * This call will clear all logger definitions from the internal hashtable. Invoking this method will irrevocably mess + * up the logger hierarchy. + * + *

+ * You should really know what you are doing before invoking this method. + *

+ * + * @since 0.9.0 + */ + public void clear() { + // System.out.println("\n\nAbout to clear internal hash table."); + ht.clear(); + getLoggersMap(getContext()).clear(); + } + + @Override + public void emitNoAppenderWarning(final Category cat) { + // No appenders in hierarchy, warn user only once. + if (!this.emittedNoAppenderWarning) { + LogLog.warn("No appenders could be found for logger (" + cat.getName() + ")."); + LogLog.warn("Please initialize the log4j system properly."); + LogLog.warn("See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info."); + this.emittedNoAppenderWarning = true; + } + } + + /** + * Tests if the named logger exists in the hierarchy. If so return its reference, otherwise returns null. + * + * @param name The name of the logger to search for. + * + */ + @Override + public Logger exists(final String name) { + return exists(name, getContext()); + } + + Logger exists(final String name, final ClassLoader classLoader) { + return exists(name, getContext(classLoader)); + } + + Logger exists(final String name, final LoggerContext loggerContext) { + if (!loggerContext.hasLogger(name)) { + return null; + } + return Logger.getLogger(name); + } + + @Override + public void fireAddAppenderEvent(final Category logger, final Appender appender) { + if (listeners != null) { + final int size = listeners.size(); + HierarchyEventListener listener; + for (int i = 0; i < size; i++) { + listener = (HierarchyEventListener) listeners.elementAt(i); + listener.addAppenderEvent(logger, appender); + } + } + } + + void fireRemoveAppenderEvent(final Category logger, final Appender appender) { + if (listeners != null) { + final int size = listeners.size(); + HierarchyEventListener listener; + for (int i = 0; i < size; i++) { + listener = (HierarchyEventListener) listeners.elementAt(i); + listener.removeAppenderEvent(logger, appender); + } + } + } + + LoggerContext getContext(final ClassLoader classLoader) { + return LogManager.getContext(classLoader); + } + + /** + * @deprecated Please use {@link #getCurrentLoggers} instead. + */ + @Deprecated + @Override + public Enumeration getCurrentCategories() { + return getCurrentLoggers(); + } + + /** + * Gets all the currently defined categories in this hierarchy as an {@link java.util.Enumeration Enumeration}. + * + *

+ * The root logger is not included in the returned {@link Enumeration}. + *

+ */ + @Override + public Enumeration getCurrentLoggers() { + // The accumlation in v is necessary because not all elements in + // ht are Logger objects as there might be some ProvisionNodes + // as well. +// final Vector v = new Vector(ht.size()); +// +// final Enumeration elems = ht.elements(); +// while (elems.hasMoreElements()) { +// final Object o = elems.nextElement(); +// if (o instanceof Logger) { +// v.addElement(o); +// } +// } +// return v.elements(); + + return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * Gets a new logger instance named as the first parameter using the default factory. + * + *

+ * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated and + * then linked with its existing ancestors as well as children. + *

+ * + * @param name The name of the logger to retrieve. + * + */ + @Override + public Logger getLogger(final String name) { + return getInstance(getContext(), name); + } + + Logger getLogger(final String name, final ClassLoader classLoader) { + return getInstance(getContext(classLoader), name); + } + + /** + * Gets a new logger instance named as the first parameter using factory. + * + *

+ * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated by + * the factory parameter and linked with its existing ancestors as well as children. + *

+ * + * @param name The name of the logger to retrieve. + * @param factory The factory that will make the new logger instance. + * + */ + @Override + public Logger getLogger(final String name, final LoggerFactory factory) { + return getInstance(getContext(), name, factory); + } + + Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) { + return getInstance(getContext(classLoader), name, factory); + } + + /** + * Gets the renderer map for this hierarchy. + */ + @Override + public RendererMap getRendererMap() { + return rendererMap; + } + + /** + * Gets the root of this hierarchy. + * + * @since 0.9.0 + */ + @Override + public Logger getRootLogger() { + return getInstance(getContext(), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); + } + + Logger getRootLogger(final ClassLoader classLoader) { + return getInstance(getContext(classLoader), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); + } + + /** + * Gets a {@link Level} representation of the enable state. + * + * @since 1.2 + */ + @Override + public Level getThreshold() { + return threshold; + } + + /** + * {@inheritDoc} + */ + @Override + public ThrowableRenderer getThrowableRenderer() { + return throwableRenderer; + } + + /** + * This method will return true if this repository is disabled for level object passed as + * parameter and false otherwise. See also the {@link #setThreshold(Level) threshold} emthod. + */ + @Override + public boolean isDisabled(final int level) { + return thresholdInt > level; + } + + /** + * @deprecated Deprecated with no replacement. + */ + @Deprecated + public void overrideAsNeeded(final String override) { + LogLog.warn("The Hiearchy.overrideAsNeeded method has been deprecated."); + } + + /** + * Resets all values contained in this hierarchy instance to their default. This removes all appenders from all + * categories, sets the level of all non-root categories to null, sets their additivity flag to + * true and sets the level of the root logger to {@link Level#DEBUG DEBUG}. Moreover, message disabling is + * set its default "off" value. + * + *

+ * Existing categories are not removed. They are just reset. + *

+ * + *

+ * This method should be used sparingly and with care as it will block all logging until it is completed. + *

+ * + * @since 0.8.5 + */ + @Override + public void resetConfiguration() { + resetConfiguration(getContext()); + } + + void resetConfiguration(final ClassLoader classLoader) { + resetConfiguration(getContext(classLoader)); + } + + void resetConfiguration(final LoggerContext loggerContext) { + getLoggersMap(loggerContext).clear(); + + getRootLogger().setLevel(Level.DEBUG); + root.setResourceBundle(null); + setThreshold(Level.ALL); + + // the synchronization is needed to prevent JDK 1.2.x hashtable + // surprises + synchronized (ht) { + shutdown(); // nested locks are OK + + final Enumeration cats = getCurrentLoggers(); + while (cats.hasMoreElements()) { + final Logger c = (Logger) cats.nextElement(); + c.setLevel(null); + c.setAdditivity(true); + c.setResourceBundle(null); + } + } + rendererMap.clear(); + throwableRenderer = null; + } + + /** + * Does nothing. + * + * @deprecated Deprecated with no replacement. + */ + @Deprecated + public void setDisableOverride(final String override) { + LogLog.warn("The Hiearchy.setDisableOverride method has been deprecated."); + } + + /** + * Used by subclasses to add a renderer to the hierarchy passed as parameter. + */ + @Override + public void setRenderer(final Class renderedClass, final ObjectRenderer renderer) { + rendererMap.put(renderedClass, renderer); + } + + /** + * Enable logging for logging requests with level l or higher. By default all levels are enabled. + * + * @param level The minimum level for which logging requests are sent to their appenders. + */ + @Override + public void setThreshold(final Level level) { + if (level != null) { + thresholdInt = level.level; + threshold = level; + } + } + + /** + * The string form of {@link #setThreshold(Level)}. + */ + @Override + public void setThreshold(final String levelStr) { + final Level level = OptionConverter.toLevel(levelStr, null); + if (level != null) { + setThreshold(level); + } else { + LogLog.warn("Could not convert [" + levelStr + "] to Level."); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setThrowableRenderer(final ThrowableRenderer throwableRenderer) { + this.throwableRenderer = throwableRenderer; + } + + /** + * Shutting down a hierarchy will safely close and remove all appenders in all categories including the root + * logger. + * + *

+ * Some appenders such as {@link org.apache.log4j.net.SocketAppender} and {@link AsyncAppender} need to be closed before + * the application exists. Otherwise, pending logging events might be lost. + *

+ *

+ * The shutdown method is careful to close nested appenders before closing regular appenders. This is + * allows configurations where a regular appender is attached to a logger and again to a nested appender. + *

+ * + * @since 1.0 + */ + @Override + public void shutdown() { + shutdown(getContext()); + } + + public void shutdown(final ClassLoader classLoader) { + shutdown(org.apache.logging.log4j.LogManager.getContext(classLoader, false)); + } + + void shutdown(final LoggerContext context) { +// final Logger root = getRootLogger(); +// // begin by closing nested appenders +// root.closeNestedAppenders(); +// +// synchronized (ht) { +// Enumeration cats = this.getCurrentLoggers(); +// while (cats.hasMoreElements()) { +// final Logger c = (Logger) cats.nextElement(); +// c.closeNestedAppenders(); +// } +// +// // then, remove all appenders +// root.removeAllAppenders(); +// cats = this.getCurrentLoggers(); +// while (cats.hasMoreElements()) { +// final Logger c = (Logger) cats.nextElement(); +// c.removeAllAppenders(); +// } +// } + getLoggersMap(context).clear(); + if (LogManager.isLog4jCorePresent()) { + ContextUtil.shutdown(context); + } + } + + /** + * We update the links for all the children that placed themselves in the provision node 'pn'. The second argument 'cat' + * is a reference for the newly created Logger, parent of all the children in 'pn' + * + * We loop on all the children 'c' in 'pn': + * + * If the child 'c' has been already linked to a child of 'cat' then there is no need to update 'c'. + * + * Otherwise, we set cat's parent field to c's parent and set c's parent field to cat. + * + */ + final private void updateChildren(final ProvisionNode pn, final Logger logger) { + // System.out.println("updateChildren called for " + logger.name); + final int last = pn.size(); + + for (int i = 0; i < last; i++) { + final Logger l = (Logger) pn.elementAt(i); + // System.out.println("Updating child " +p.name); + + // Unless this child already points to a correct (lower) parent, + // make cat.parent point to l.parent and l.parent to cat. + if (!l.parent.name.startsWith(logger.name)) { + logger.parent = l.parent; + l.parent = logger; + } + } + } + + /** + * This method loops through all the *potential* parents of 'cat'. There 3 possible cases: + * + * 1) No entry for the potential parent of 'cat' exists + * + * We create a ProvisionNode for this potential parent and insert 'cat' in that provision node. + * + * 2) There entry is of type Logger for the potential parent. + * + * The entry is 'cat's nearest existing parent. We update cat's parent field with this entry. We also break from the + * loop because updating our parent's parent is our parent's responsibility. + * + * 3) There entry is of type ProvisionNode for this potential parent. + * + * We add 'cat' to the list of children for this potential parent. + */ + final private void updateParents(final Logger cat) { + final String name = cat.name; + final int length = name.length(); + boolean parentFound = false; + + // System.out.println("UpdateParents called for " + name); + + // if name = "w.x.y.z", loop thourgh "w.x.y", "w.x" and "w", but not "w.x.y.z" + for (int i = name.lastIndexOf('.', length - 1); i >= 0; i = name.lastIndexOf('.', i - 1)) { + final String substr = name.substring(0, i); + + // System.out.println("Updating parent : " + substr); + final CategoryKey key = new CategoryKey(substr); // simple constructor + final Object o = ht.get(key); + // Create a provision node for a future parent. + if (o == null) { + // System.out.println("No parent "+substr+" found. Creating ProvisionNode."); + final ProvisionNode pn = new ProvisionNode(cat); + ht.put(key, pn); + } else if (o instanceof Category) { + parentFound = true; + cat.parent = (Category) o; + // System.out.println("Linking " + cat.name + " -> " + ((Category) o).name); + break; // no need to update the ancestors of the closest ancestor + } else if (o instanceof ProvisionNode) { + ((ProvisionNode) o).addElement(cat); + } else { + final Exception e = new IllegalStateException("unexpected object type " + o.getClass() + " in ht."); + e.printStackTrace(); + } + } + // If we could not find any existing parents, then link with root. + if (!parentFound) { + cat.parent = root; + } + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Layout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Layout.java index 2ef4b1c7eaf..dbd22916a21 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Layout.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Layout.java @@ -24,6 +24,8 @@ */ public abstract class Layout { + public final static String LINE_SEP = Strings.LINE_SEPARATOR; + /** Note that the line.separator property can be looked up even by applets. */ public static final int LINE_SEP_LEN = Strings.LINE_SEPARATOR.length(); diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java index af5315417bd..2da318529b3 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Level.java @@ -23,6 +23,7 @@ import java.io.Serializable; import java.util.Locale; +import org.apache.log4j.helpers.OptionConverter; import org.apache.logging.log4j.util.Strings; /** @@ -48,50 +49,50 @@ public class Level extends Priority implements Serializable { * The OFF has the highest possible rank and is * intended to turn off logging. */ - public static final Level OFF = new Level(OFF_INT, "OFF", 0); + public static final Level OFF = new Level(OFF_INT, "OFF", 0, org.apache.logging.log4j.Level.OFF); /** * The FATAL level designates very severe error * events that will presumably lead the application to abort. */ - public static final Level FATAL = new Level(FATAL_INT, "FATAL", 0); + public static final Level FATAL = new Level(FATAL_INT, "FATAL", 0, org.apache.logging.log4j.Level.FATAL); /** * The ERROR level designates error events that * might still allow the application to continue running. */ - public static final Level ERROR = new Level(ERROR_INT, "ERROR", 3); + public static final Level ERROR = new Level(ERROR_INT, "ERROR", 3, org.apache.logging.log4j.Level.ERROR); /** * The WARN level designates potentially harmful situations. */ - public static final Level WARN = new Level(WARN_INT, "WARN", 4); + public static final Level WARN = new Level(WARN_INT, "WARN", 4, org.apache.logging.log4j.Level.WARN); /** * The INFO level designates informational messages * that highlight the progress of the application at coarse-grained * level. */ - public static final Level INFO = new Level(INFO_INT, "INFO", 6); + public static final Level INFO = new Level(INFO_INT, "INFO", 6, org.apache.logging.log4j.Level.INFO); /** * The DEBUG Level designates fine-grained * informational events that are most useful to debug an * application. */ - public static final Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7); + public static final Level DEBUG = new Level(DEBUG_INT, "DEBUG", 7, org.apache.logging.log4j.Level.DEBUG); /** * The TRACE Level designates finer-grained * informational events than the DEBUG level. */ - public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7); + public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7, org.apache.logging.log4j.Level.TRACE); /** * The ALL has the lowest possible rank and is intended to * turn on all logging. */ - public static final Level ALL = new Level(ALL_INT, "ALL", 7); + public static final Level ALL = new Level(ALL_INT, "ALL", 7, org.apache.logging.log4j.Level.ALL); /** * Serialization version id. @@ -99,16 +100,21 @@ public class Level extends Priority implements Serializable { private static final long serialVersionUID = 3491141966387921974L; /** - * Instantiate a Level object. + * Instantiate a Level object. A corresponding Log4j 2.x level is also created. * * @param level The logging level. * @param levelStr The level name. * @param syslogEquivalent The matching syslog level. */ protected Level(final int level, final String levelStr, final int syslogEquivalent) { - super(level, levelStr, syslogEquivalent); + this(level, levelStr, syslogEquivalent, null); } + protected Level(final int level, final String levelStr, final int syslogEquivalent, + final org.apache.logging.log4j.Level version2Equivalent) { + super(level, levelStr, syslogEquivalent); + this.version2Level = version2Equivalent != null ? version2Equivalent : OptionConverter.createLevel(this); + } /** * Convert the string passed as argument to a level. If the diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java index 751cb86972d..26c19bd3de7 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java @@ -1,222 +1,224 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.log4j; - -import java.util.Enumeration; - -import org.apache.log4j.helpers.NullEnumeration; -import org.apache.log4j.spi.HierarchyEventListener; -import org.apache.log4j.spi.LoggerFactory; -import org.apache.log4j.spi.LoggerRepository; -import org.apache.log4j.spi.RepositorySelector; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.util.Strings; - -/** - * - */ -public final class LogManager { - - /** - * @deprecated This variable is for internal use only. It will - * become package protected in future versions. - * */ - @Deprecated - public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties"; - - /** - * @deprecated This variable is for internal use only. It will - * become private in future versions. - * */ - @Deprecated - public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration"; - - /** - * @deprecated This variable is for internal use only. It will - * become private in future versions. - * */ - @Deprecated - public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass"; - - /** - * @deprecated This variable is for internal use only. It will - * become private in future versions. - */ - @Deprecated - public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride"; - - static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml"; - - private static final LoggerRepository REPOSITORY = new Repository(); - - private LogManager() { - } - - public static Logger getRootLogger() { - return Category.getInstance(PrivateManager.getContext(), Strings.EMPTY); - } - - public static Logger getLogger(final String name) { - return Category.getInstance(PrivateManager.getContext(), name); - } - - public static Logger getLogger(final Class clazz) { - return Category.getInstance(PrivateManager.getContext(), clazz.getName()); - } - - public static Logger getLogger(final String name, final LoggerFactory factory) { - return Category.getInstance(PrivateManager.getContext(), name); - } - - public static Logger exists(final String name) { - final LoggerContext ctx = PrivateManager.getContext(); - if (!ctx.hasLogger(name)) { - return null; - } - return Logger.getLogger(name); - } - - @SuppressWarnings("rawtypes") - public static Enumeration getCurrentLoggers() { - return NullEnumeration.getInstance(); - } - - static void reconfigure() { - final LoggerContext ctx = PrivateManager.getContext(); - ctx.reconfigure(); - } - - /** - * No-op implementation. - */ - public static void shutdown() { - } - - /** - * No-op implementation. - */ - public static void resetConfiguration() { - } - - /** - * No-op implementation. - * @param selector The RepositorySelector. - * @param guard prevents calls at the incorrect time. - * @throws IllegalArgumentException if a parameter is invalid. - */ - public static void setRepositorySelector(final RepositorySelector selector, final Object guard) - throws IllegalArgumentException { - } - - public static LoggerRepository getLoggerRepository() { - return REPOSITORY; - } - - /** - * The Repository. - */ - private static class Repository implements LoggerRepository { - @Override - public void addHierarchyEventListener(final HierarchyEventListener listener) { - - } - - @Override - public boolean isDisabled(final int level) { - return false; - } - - @Override - public void setThreshold(final Level level) { - - } - - @Override - public void setThreshold(final String val) { - - } - - @Override - public void emitNoAppenderWarning(final Category cat) { - - } - - @Override - public Level getThreshold() { - return Level.OFF; - } - - @Override - public Logger getLogger(final String name) { - return Category.getInstance(PrivateManager.getContext(), name); - } - - @Override - public Logger getLogger(final String name, final LoggerFactory factory) { - return Category.getInstance(PrivateManager.getContext(), name); - } - - @Override - public Logger getRootLogger() { - return Category.getRoot(PrivateManager.getContext()); - } - - @Override - public Logger exists(final String name) { - return LogManager.exists(name); - } - - @Override - public void shutdown() { - } - - @Override - @SuppressWarnings("rawtypes") - public Enumeration getCurrentLoggers() { - return NullEnumeration.getInstance(); - } - - @Override - @SuppressWarnings("rawtypes") - public Enumeration getCurrentCategories() { - return NullEnumeration.getInstance(); - } - - @Override - public void fireAddAppenderEvent(final Category logger, final Appender appender) { - } - - @Override - public void resetConfiguration() { - } - } - - /** - * Internal LogManager. - */ - private static class PrivateManager extends org.apache.logging.log4j.LogManager { - private static final String FQCN = LogManager.class.getName(); - - public static LoggerContext getContext() { - return (LoggerContext) getContext(FQCN, false); - } - - public static org.apache.logging.log4j.Logger getLogger(final String name) { - return getLogger(FQCN, name); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.stream.Collectors; + +import org.apache.log4j.legacy.core.ContextUtil; +import org.apache.log4j.spi.DefaultRepositorySelector; +import org.apache.log4j.spi.LoggerFactory; +import org.apache.log4j.spi.LoggerRepository; +import org.apache.log4j.spi.NOPLoggerRepository; +import org.apache.log4j.spi.RepositorySelector; +import org.apache.log4j.spi.RootLogger; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.StackLocatorUtil; + +/** + * The main entry point to Log4j 1. + */ +public final class LogManager { + + /** + * @deprecated This variable is for internal use only. It will become package protected in future versions. + */ + @Deprecated + public static final String DEFAULT_CONFIGURATION_FILE = "log4j.properties"; + + /** + * @deprecated This variable is for internal use only. It will become private in future versions. + */ + @Deprecated + public static final String DEFAULT_CONFIGURATION_KEY = "log4j.configuration"; + + /** + * @deprecated This variable is for internal use only. It will become private in future versions. + */ + @Deprecated + public static final String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass"; + + /** + * @deprecated This variable is for internal use only. It will become private in future versions. + */ + @Deprecated + public static final String DEFAULT_INIT_OVERRIDE_KEY = "log4j.defaultInitOverride"; + + static final String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml"; + + static private RepositorySelector repositorySelector; + + private static final boolean LOG4J_CORE_PRESENT; + + static { + LOG4J_CORE_PRESENT = LoaderUtil.isClassAvailable("org.apache.logging.log4j.core.LoggerContext"); + // By default, we use a DefaultRepositorySelector which always returns 'hierarchy'. + final Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG)); + repositorySelector = new DefaultRepositorySelector(hierarchy); + } + + /** + * Tests if a logger for the given name exists. + * + * @param name logger name to test. + * @return whether a logger for the given name exists. + */ + public static Logger exists(final String name) { + return exists(name, StackLocatorUtil.getCallerClassLoader(2)); + } + + static Logger exists(final String name, final ClassLoader classLoader) { + return getHierarchy().exists(name, classLoader); + } + + /** + * Gets a LoggerContext. + * + * @param classLoader The ClassLoader for the context. If null the context will attempt to determine the appropriate + * ClassLoader. + * @return a LoggerContext. + */ + static LoggerContext getContext(final ClassLoader classLoader) { + return org.apache.logging.log4j.LogManager.getContext(classLoader, false); + } + + /** + * Gets an enumeration of the current loggers. + * + * @return an enumeration of the current loggers. + */ + @SuppressWarnings("rawtypes") + public static Enumeration getCurrentLoggers() { + return getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2)); + } + + @SuppressWarnings("rawtypes") + static Enumeration getCurrentLoggers(final ClassLoader classLoader) { + // @formatter:off + return Collections.enumeration( + LogManager.getContext(classLoader).getLoggerRegistry() + .getLoggers().stream().map(e -> LogManager.getLogger(e.getName(), classLoader)) + .collect(Collectors.toList())); + // @formatter:on + } + + static Hierarchy getHierarchy() { + final LoggerRepository loggerRepository = getLoggerRepository(); + return loggerRepository instanceof Hierarchy ? (Hierarchy) loggerRepository : null; + } + + /** + * Gets the logger for the given class. + */ + public static Logger getLogger(final Class clazz) { + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2)) + : getLoggerRepository().getLogger(clazz.getName()); + } + + /** + * Gets the logger for the given name. + */ + public static Logger getLogger(final String name) { + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)) : getLoggerRepository().getLogger(name); + } + + static Logger getLogger(final String name, final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, classLoader) : getLoggerRepository().getLogger(name); + } + + public static Logger getLogger(final String name, final LoggerFactory factory) { + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2)) + : getLoggerRepository().getLogger(name, factory); + } + + static Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getLogger(name, factory, classLoader) : getLoggerRepository().getLogger(name, factory); + } + + public static LoggerRepository getLoggerRepository() { + if (repositorySelector == null) { + repositorySelector = new DefaultRepositorySelector(new NOPLoggerRepository()); + } + return repositorySelector.getLoggerRepository(); + } + + /** + * Gets the root logger. + */ + public static Logger getRootLogger() { + return getRootLogger(StackLocatorUtil.getCallerClassLoader(2)); + } + + static Logger getRootLogger(final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + return hierarchy != null ? hierarchy.getRootLogger(classLoader) : getLoggerRepository().getRootLogger(); + } + + static boolean isLog4jCorePresent() { + return LOG4J_CORE_PRESENT; + } + + static void reconfigure(final ClassLoader classLoader) { + if (isLog4jCorePresent()) { + ContextUtil.reconfigure(LogManager.getContext(classLoader)); + } + } + + public static void resetConfiguration() { + resetConfiguration(StackLocatorUtil.getCallerClassLoader(2)); + } + + static void resetConfiguration(final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + if (hierarchy != null) { + hierarchy.resetConfiguration(classLoader); + } else { + getLoggerRepository().resetConfiguration(); + } + } + + public static void setRepositorySelector(final RepositorySelector selector, final Object guard) throws IllegalArgumentException { + if (selector == null) { + throw new IllegalArgumentException("RepositorySelector must be non-null."); + } + LogManager.repositorySelector = selector; + } + + /** + * Shuts down the current configuration. + */ + public static void shutdown() { + shutdown(StackLocatorUtil.getCallerClassLoader(2)); + } + + static void shutdown(final ClassLoader classLoader) { + final Hierarchy hierarchy = getHierarchy(); + if (hierarchy != null) { + hierarchy.shutdown(classLoader); + } else { + getLoggerRepository().shutdown(); + } + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Logger.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Logger.java index fb7277b5012..b152e2b757f 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Logger.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Logger.java @@ -16,51 +16,58 @@ */ package org.apache.log4j; - import org.apache.log4j.spi.LoggerFactory; -import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.util.StackLocatorUtil; /** * */ public class Logger extends Category { - protected Logger(final String name) { - super(PrivateManager.getContext(), name); - } + /** + * The fully qualified name of the Logger class. + */ + private static final String FQCN = Logger.class.getName(); - Logger(final LoggerContext context, final String name) { - super(context, name); + public static Logger getLogger(final Class clazz) { + // Depth 2 gets the call site of this method. + return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2)); } public static Logger getLogger(final String name) { - return Category.getInstance(PrivateManager.getContext(), name); + // Depth 2 gets the call site of this method. + return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)); } - public static Logger getLogger(final Class clazz) { - return Category.getInstance(PrivateManager.getContext(), clazz); + public static Logger getLogger(final String name, final LoggerFactory factory) { + // Depth 2 gets the call site of this method. + return LogManager.getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2)); } public static Logger getRootLogger() { - return Category.getRoot(PrivateManager.getContext()); + return LogManager.getRootLogger(); } - public static Logger getLogger(final String name, final LoggerFactory factory) { - return Category.getInstance(PrivateManager.getContext(), name, factory); + Logger(final LoggerContext context, final String name) { + super(context, name); } - /** - * Internal Log Manager. - */ - private static class PrivateManager extends org.apache.logging.log4j.LogManager { - private static final String FQCN = Logger.class.getName(); + protected Logger(final String name) { + super(name); + } - public static LoggerContext getContext() { - return (LoggerContext) getContext(FQCN, false); - } + public boolean isTraceEnabled() { + return getLogger().isTraceEnabled(); + } + + public void trace(final Object message) { + maybeLog(FQCN, org.apache.logging.log4j.Level.TRACE, message, null); + } - public static org.apache.logging.log4j.Logger getLogger(final String name) { - return getLogger(FQCN, name); - } + public void trace(final Object message, final Throwable t) { + maybeLog(FQCN, org.apache.logging.log4j.Level.TRACE, message, t); } + + } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/MDC.java b/log4j-1.2-api/src/main/java/org/apache/log4j/MDC.java index ee7631a6994..ad902d21e69 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/MDC.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/MDC.java @@ -28,8 +28,7 @@ */ public final class MDC { - - private static ThreadLocal> localMap = + private static final ThreadLocal> localMap = new InheritableThreadLocal>() { @Override protected Map initialValue() { @@ -38,7 +37,7 @@ protected Map initialValue() { @Override protected Map childValue(final Map parentValue) { - return parentValue == null ? new HashMap() : new HashMap<>(parentValue); + return parentValue == null ? new HashMap<>() : new HashMap<>(parentValue); } }; diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/NDC.java b/log4j-1.2-api/src/main/java/org/apache/log4j/NDC.java index a4e23dcd7d1..72efe04a86d 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/NDC.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/NDC.java @@ -19,7 +19,7 @@ import java.util.Stack; /** - * + * This class does not use generics to provide better source compatibility. */ public final class NDC { @@ -52,7 +52,7 @@ public static void clear() { * The child thread uses the {@link #inherit inherit} method to * inherit the parent's diagnostic context. *

- * @return Stack A clone of the current thread's diagnostic context. + * @return Stack A clone of the current thread's diagnostic context, Stack of Strings. */ @SuppressWarnings("rawtypes") public static Stack cloneStack() { @@ -65,7 +65,7 @@ public static Stack cloneStack() { /** - * Inherit the diagnostic context of another thread. + * Inherit the diagnostic context of another thread, a Stack of Strings. *

* The parent thread can obtain a reference to its diagnostic * context using the {@link #cloneStack} method. It should @@ -83,9 +83,10 @@ public static Stack cloneStack() { * there is no client-transparent way of inheriting diagnostic * contexts. Do you know any solution to this problem? *

- * @param stack The diagnostic context of the parent thread. + * @param stack The diagnostic context of the parent thread, a Stack of Strings. */ - public static void inherit(final Stack stack) { + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void inherit(final Stack stack) { org.apache.logging.log4j.ThreadContext.setStack(stack); } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/PatternLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/PatternLayout.java index c2e1251ee8c..da6f87f6547 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/PatternLayout.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/PatternLayout.java @@ -16,25 +16,117 @@ */ package org.apache.log4j; +import org.apache.log4j.helpers.PatternConverter; +import org.apache.log4j.helpers.PatternParser; import org.apache.log4j.spi.LoggingEvent; -import org.apache.logging.log4j.util.Strings; /** * */ public class PatternLayout extends Layout { + /** + * Default pattern string for log output. Currently set to the string {@value #DEFAULT_CONVERSION_PATTERN} which + * just prints the application supplied message. + */ + public final static String DEFAULT_CONVERSION_PATTERN = "%m%n"; + + /** + * A conversion pattern equivalent to the TTCCCLayout. Current value is {@value #TTCC_CONVERSION_PATTERN} + */ + public final static String TTCC_CONVERSION_PATTERN = "%r [%t] %p %c %x - %m%n"; + + protected final int BUF_SIZE = 256; + + protected final int MAX_CAPACITY = 1024; + + // output buffer appended to when format() is invoked + private StringBuffer sbuf = new StringBuffer(BUF_SIZE); + + private String pattern; + + private PatternConverter head; + + /** + * Constructs a PatternLayout using the DEFAULT_LAYOUT_PATTERN. + * + * The default pattern just produces the application supplied message. + */ + public PatternLayout() { + this(DEFAULT_CONVERSION_PATTERN); + } + + /** + * Constructs a PatternLayout using the supplied conversion pattern. + */ public PatternLayout(final String pattern) { + this.pattern = pattern; + head = createPatternParser((pattern == null) ? DEFAULT_CONVERSION_PATTERN : pattern).parse(); + } + /** + * Does not do anything as options become effective + */ + public void activateOptions() { + // nothing to do. } + /** + * Returns PatternParser used to parse the conversion string. Subclasses may override this to return a subclass of + * PatternParser which recognize custom conversion characters. + * + * @since 0.9.0 + */ + protected PatternParser createPatternParser(final String pattern) { + return new PatternParser(pattern); + } + + /** + * Produces a formatted string as specified by the conversion pattern. + */ @Override public String format(final LoggingEvent event) { - return Strings.EMPTY; + // Reset working stringbuffer + if (sbuf.capacity() > MAX_CAPACITY) { + sbuf = new StringBuffer(BUF_SIZE); + } else { + sbuf.setLength(0); + } + + PatternConverter c = head; + + while (c != null) { + c.format(sbuf, event); + c = c.next; + } + return sbuf.toString(); } + /** + * Returns the value of the ConversionPattern option. + */ + public String getConversionPattern() { + return pattern; + } + + /** + * The PatternLayout does not handle the throwable contained within {@link LoggingEvent LoggingEvents}. Thus, it returns + * true. + * + * @since 0.8.4 + */ @Override public boolean ignoresThrowable() { return true; } + + /** + * Set the ConversionPattern option. This is the string which controls formatting and consists of a mix of + * literal content and conversion specifiers. + */ + public void setConversionPattern(final String conversionPattern) { + pattern = conversionPattern; + head = createPatternParser(conversionPattern).parse(); + } + } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java index 8f6eee9b519..33a78915ef2 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Priority.java @@ -96,6 +96,7 @@ public class Priority { transient int level; transient String levelStr; transient int syslogEquivalent; + transient org.apache.logging.log4j.Level version2Level; /** * Default constructor for deserialization. @@ -148,6 +149,14 @@ final int getSyslogEquivalent() { return syslogEquivalent; } + /** + * Gets the Log4j 2.x level associated with this priority + * + * @return a Log4j 2.x level. + */ + public org.apache.logging.log4j.Level getVersion2Level() { + return version2Level; + } /** * Returns {@code true} if this level has a higher or equal diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java index 0fe1fe0e60c..7069f5f8cec 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java @@ -16,111 +16,663 @@ */ package org.apache.log4j; +import java.io.IOException; import java.io.InputStream; +import java.io.InterruptedIOException; import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Map; import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.config.PropertySetter; +import org.apache.log4j.helpers.FileWatchdog; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.or.RendererMap; +import org.apache.log4j.spi.Configurator; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggerFactory; import org.apache.log4j.spi.LoggerRepository; +import org.apache.log4j.spi.OptionHandler; +import org.apache.log4j.spi.RendererSupport; +import org.apache.log4j.spi.ThrowableRenderer; +import org.apache.log4j.spi.ThrowableRendererSupport; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.net.UrlConnectionFactory; +import org.apache.logging.log4j.util.StackLocatorUtil; /** - * A configurator for properties. + * Configures Log4j from properties. */ -public class PropertyConfigurator { +public class PropertyConfigurator implements Configurator { + + static class NameValue { + String key, value; + + public NameValue(final String key, final String value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return key + "=" + value; + } + } + + static class PropertyWatchdog extends FileWatchdog { + + private final ClassLoader classLoader; + + PropertyWatchdog(final String fileName, final ClassLoader classLoader) { + super(fileName); + this.classLoader = classLoader; + } + + /** + * Call {@link PropertyConfigurator#configure(String)} with the filename to reconfigure log4j. + */ + @Override + public void doOnChange() { + new PropertyConfigurator().doConfigure(filename, LogManager.getLoggerRepository(), classLoader); + } + } + + class SortedKeyEnumeration implements Enumeration { + + private final Enumeration e; + + public SortedKeyEnumeration(final Hashtable ht) { + final Enumeration f = ht.keys(); + final Vector keys = new Vector(ht.size()); + for (int i, last = 0; f.hasMoreElements(); ++last) { + final String key = (String) f.nextElement(); + for (i = 0; i < last; ++i) { + final String s = (String) keys.get(i); + if (key.compareTo(s) <= 0) { + break; + } + } + keys.add(i, key); + } + e = keys.elements(); + } + + @Override + public boolean hasMoreElements() { + return e.hasMoreElements(); + } + + @Override + public Object nextElement() { + return e.nextElement(); + } + } + + static final String CATEGORY_PREFIX = "log4j.category."; + static final String LOGGER_PREFIX = "log4j.logger."; + static final String FACTORY_PREFIX = "log4j.factory"; + static final String ADDITIVITY_PREFIX = "log4j.additivity."; + static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory"; + static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger"; + static final String APPENDER_PREFIX = "log4j.appender."; + static final String RENDERER_PREFIX = "log4j.renderer."; + static final String THRESHOLD_PREFIX = "log4j.threshold"; + + private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer"; + private static final String LOGGER_REF = "logger-ref"; + private static final String ROOT_REF = "root-ref"; + private static final String APPENDER_REF_TAG = "appender-ref"; /** - * Read configuration options from configuration file. - * - * @param configFileName The configuration file - * @param hierarchy The hierarchy + * Key for specifying the {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}. Currently set to + * "log4j.loggerFactory". + */ + public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory"; + + /** + * If property set to true, then hierarchy will be reset before configuration. */ - public void doConfigure(final String configFileName, final LoggerRepository hierarchy) { + private static final String RESET_KEY = "log4j.reset"; + static final private String INTERNAL_ROOT_NAME = "root"; + + /** + * Reads configuration options from an InputStream. + * + * @param inputStream The input stream + */ + public static void configure(final InputStream inputStream) { + new PropertyConfigurator().doConfigure(inputStream, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from properties. + * Reads configuration options from properties. * * See {@link #doConfigure(String, LoggerRepository)} for the expected format. * * @param properties The properties - * @param hierarchy The hierarchy */ - public void doConfigure(final Properties properties, final LoggerRepository hierarchy) { + public static void configure(final Properties properties) { + new PropertyConfigurator().doConfigure(properties, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from an InputStream. + * Reads configuration options from configuration file. * - * @param inputStream The input stream - * @param hierarchy The hierarchy + * @param fileName The configuration file. */ - public void doConfigure(final InputStream inputStream, final LoggerRepository hierarchy) { + public static void configure(final String fileName) { + new PropertyConfigurator().doConfigure(fileName, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from url configURL. + * Reads configuration options from url configURL. * * @param configURL The configuration URL - * @param hierarchy The hierarchy */ - public void doConfigure(final URL configURL, final LoggerRepository hierarchy) { + public static void configure(final URL configURL) { + new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from configuration file. + * Like {@link #configureAndWatch(String, long)} except that the default delay as defined by FileWatchdog.DEFAULT_DELAY + * is used. * - * @param configFileName The configuration file. + * @param configFilename A file in key=value format. */ - public static void configure(final String configFileName) { + public static void configureAndWatch(final String configFilename) { + configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY, StackLocatorUtil.getCallerClassLoader(2)); } /** - * Read configuration options from url configURL. + * Reads the configuration file configFilename if it exists. Moreover, a thread will be created that will + * periodically check if configFilename has been created or modified. The period is determined by the + * delay argument. If a change or file creation is detected, then configFilename is read to + * configure log4j. * - * @param configURL The configuration URL + * @param configFilename A file in key=value format. + * @param delayMillis The delay in milliseconds to wait between each check. */ - public static void configure(final URL configURL) { + public static void configureAndWatch(final String configFilename, final long delayMillis) { + configureAndWatch(configFilename, delayMillis, StackLocatorUtil.getCallerClassLoader(2)); + } + + static void configureAndWatch(final String configFilename, final long delay, final ClassLoader classLoader) { + final PropertyWatchdog watchdog = new PropertyWatchdog(configFilename, classLoader); + watchdog.setDelay(delay); + watchdog.start(); + } + + private static Configuration reconfigure(final Configuration configuration) { + org.apache.logging.log4j.core.config.Configurator.reconfigure(configuration); + return configuration; + } + + /** + * Used internally to keep track of configured appenders. + */ + protected Hashtable registry = new Hashtable(11); + + private LoggerRepository repository; + + protected LoggerFactory loggerFactory = new DefaultCategoryFactory(); + + /** + * Checks the provided Properties object for a {@link org.apache.log4j.spi.LoggerFactory LoggerFactory} + * entry specified by {@link #LOGGER_FACTORY_KEY}. If such an entry exists, an attempt is made to create an instance + * using the default constructor. This instance is used for subsequent Category creations within this configurator. + * + * @see #parseCatsAndRenderers + */ + protected void configureLoggerFactory(final Properties properties) { + final String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, properties); + if (factoryClassName != null) { + LogLog.debug("Setting category factory to [" + factoryClassName + "]."); + loggerFactory = (LoggerFactory) OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory); + PropertySetter.setProperties(loggerFactory, properties, FACTORY_PREFIX + "."); + } + } + + void configureRootCategory(final Properties properties, final LoggerRepository loggerRepository) { + String effectiveFrefix = ROOT_LOGGER_PREFIX; + String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, properties); + + if (value == null) { + value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, properties); + effectiveFrefix = ROOT_CATEGORY_PREFIX; + } + + if (value == null) { + LogLog.debug("Could not find root logger information. Is this OK?"); + } else { + final Logger root = loggerRepository.getRootLogger(); + synchronized (root) { + parseCategory(properties, root, effectiveFrefix, INTERNAL_ROOT_NAME, value); + } + } } /** * Reads configuration options from an InputStream. * * @param inputStream The input stream + * @param loggerRepository The hierarchy */ - public static void configure(final InputStream inputStream) { + @Override + public void doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository) { + doConfigure(inputStream, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); + } + + Configuration doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + return doConfigure(loadProperties(inputStream), loggerRepository, classLoader); } /** - * Read configuration options from properties. + * Reads configuration options from properties. * * See {@link #doConfigure(String, LoggerRepository)} for the expected format. * * @param properties The properties + * @param loggerRepository The hierarchy */ - public static void configure(final Properties properties) { + public void doConfigure(final Properties properties, final LoggerRepository loggerRepository) { + doConfigure(properties, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); } /** - * Like {@link #configureAndWatch(String, long)} except that the - * default delay as defined by FileWatchdog.DEFAULT_DELAY is - * used. + * Reads configuration options from properties. * - * @param configFilename A file in key=value format. + * See {@link #doConfigure(String, LoggerRepository)} for the expected format. + * + * @param properties The properties + * @param loggerRepository The hierarchy */ - public static void configureAndWatch(final String configFilename) { + Configuration doConfigure(final Properties properties, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + final PropertiesConfiguration configuration = new PropertiesConfiguration(LogManager.getContext(classLoader), properties); + configuration.doConfigure(); + + repository = loggerRepository; +// String value = properties.getProperty(LogLog.DEBUG_KEY); +// if (value == null) { +// value = properties.getProperty("log4j.configDebug"); +// if (value != null) { +// LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); +// } +// } +// +// if (value != null) { +// LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true)); +// } +// +// // +// // if log4j.reset=true then +// // reset hierarchy +// final String reset = properties.getProperty(RESET_KEY); +// if (reset != null && OptionConverter.toBoolean(reset, false)) { +// hierarchy.resetConfiguration(); +// } +// +// final String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties); +// if (thresholdStr != null) { +// hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL)); +// LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "]."); +// } +// +// configureRootCategory(properties, hierarchy); +// configureLoggerFactory(properties); +// parseCatsAndRenderers(properties, hierarchy); +// + // We don't want to hold references to appenders preventing their + // garbage collection. + registry.clear(); + + return reconfigure(configuration); } /** - * Read the configuration file configFilename if it - * exists. Moreover, a thread will be created that will periodically - * check if configFilename has been created or - * modified. The period is determined by the delay - * argument. If a change or file creation is detected, then - * configFilename is read to configure log4j. + * Reads configuration options from configuration file. * - * @param configFilename A file in key=value format. - * @param delay The delay in milliseconds to wait between each check. + * @param fileName The configuration file + * @param loggerRepository The hierarchy + */ + public void doConfigure(final String fileName, final LoggerRepository loggerRepository) { + doConfigure(fileName, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); + } + + /** + * Reads configuration options from configuration file. + * + * @param fileName The configuration file + * @param loggerRepository The hierarchy + */ + Configuration doConfigure(final String fileName, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + try (InputStream inputStream = Files.newInputStream(Paths.get(fileName))) { + return doConfigure(inputStream, loggerRepository, classLoader); + } catch (final Exception e) { + if (e instanceof InterruptedIOException || e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + LogLog.error("Could not read configuration file [" + fileName + "].", e); + LogLog.error("Ignoring configuration file [" + fileName + "]."); + return null; + } + } + + /** + * Read configuration options from url configURL. + * + * @param url The configuration URL + * @param loggerRepository The hierarchy */ - public static void configureAndWatch(final String configFilename, final long delay) { + @Override + public void doConfigure(final URL url, final LoggerRepository loggerRepository) { + doConfigure(url, loggerRepository, StackLocatorUtil.getCallerClassLoader(2)); + } + + Configuration doConfigure(final URL url, final LoggerRepository loggerRepository, final ClassLoader classLoader) { + LogLog.debug("Reading configuration from URL " + url); + try { + final URLConnection urlConnection = UrlConnectionFactory.createConnection(url); + try (InputStream inputStream = urlConnection.getInputStream()) { + return doConfigure(inputStream, loggerRepository, classLoader); + } + } catch (final IOException e) { + LogLog.error("Could not read configuration file from URL [" + url + "].", e); + LogLog.error("Ignoring configuration file [" + url + "]."); + return null; + } + + } + + private Properties loadProperties(final InputStream inputStream) { + final Properties loaded = new Properties(); + try { + loaded.load(inputStream); + } catch (final IOException | IllegalArgumentException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.error("Could not read configuration file from InputStream [" + inputStream + "].", e); + LogLog.error("Ignoring configuration InputStream [" + inputStream + "]."); + return null; + } + return loaded; + } + + /** + * Parse the additivity option for a non-root category. + */ + void parseAdditivityForLogger(final Properties properties, final Logger logger, final String loggerName) { + final String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, properties); + LogLog.debug("Handling " + ADDITIVITY_PREFIX + loggerName + "=[" + value + "]"); + // touch additivity only if necessary + if ((value != null) && (!value.equals(""))) { + final boolean additivity = OptionConverter.toBoolean(value, true); + LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity); + logger.setAdditivity(additivity); + } + } + + Appender parseAppender(final Properties properties, final String appenderName) { + Appender appender = registryGet(appenderName); + if ((appender != null)) { + LogLog.debug("Appender \"" + appenderName + "\" was already parsed."); + return appender; + } + // Appender was not previously initialized. + final String prefix = APPENDER_PREFIX + appenderName; + final String layoutPrefix = prefix + ".layout"; + + appender = (Appender) OptionConverter.instantiateByKey(properties, prefix, org.apache.log4j.Appender.class, null); + if (appender == null) { + LogLog.error("Could not instantiate appender named \"" + appenderName + "\"."); + return null; + } + appender.setName(appenderName); + + if (appender instanceof OptionHandler) { + if (appender.requiresLayout()) { + final Layout layout = (Layout) OptionConverter.instantiateByKey(properties, layoutPrefix, Layout.class, null); + if (layout != null) { + appender.setLayout(layout); + LogLog.debug("Parsing layout options for \"" + appenderName + "\"."); + // configureOptionHandler(layout, layoutPrefix + ".", props); + PropertySetter.setProperties(layout, properties, layoutPrefix + "."); + LogLog.debug("End of parsing for \"" + appenderName + "\"."); + } + } + final String errorHandlerPrefix = prefix + ".errorhandler"; + final String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, properties); + if (errorHandlerClass != null) { + final ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(properties, errorHandlerPrefix, ErrorHandler.class, null); + if (eh != null) { + appender.setErrorHandler(eh); + LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\"."); + parseErrorHandler(eh, errorHandlerPrefix, properties, repository); + final Properties edited = new Properties(); + final String[] keys = new String[] {errorHandlerPrefix + "." + ROOT_REF, errorHandlerPrefix + "." + LOGGER_REF, + errorHandlerPrefix + "." + APPENDER_REF_TAG}; + for (final Object element : properties.entrySet()) { + final Map.Entry entry = (Map.Entry) element; + int i = 0; + for (; i < keys.length; i++) { + if (keys[i].equals(entry.getKey())) { + break; + } + } + if (i == keys.length) { + edited.put(entry.getKey(), entry.getValue()); + } + } + PropertySetter.setProperties(eh, edited, errorHandlerPrefix + "."); + LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\"."); + } + + } + // configureOptionHandler((OptionHandler) appender, prefix + ".", props); + PropertySetter.setProperties(appender, properties, prefix + "."); + LogLog.debug("Parsed \"" + appenderName + "\" options."); + } + parseAppenderFilters(properties, appenderName, appender); + registryPut(appender); + return appender; + } + + void parseAppenderFilters(final Properties properties, final String appenderName, final Appender appender) { + // extract filters and filter options from props into a hashtable mapping + // the property name defining the filter class to a list of pre-parsed + // name-value pairs associated to that filter + final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter."; + final int fIdx = filterPrefix.length(); + final Hashtable filters = new Hashtable(); + final Enumeration e = properties.keys(); + String name = ""; + while (e.hasMoreElements()) { + final String key = (String) e.nextElement(); + if (key.startsWith(filterPrefix)) { + final int dotIdx = key.indexOf('.', fIdx); + String filterKey = key; + if (dotIdx != -1) { + filterKey = key.substring(0, dotIdx); + name = key.substring(dotIdx + 1); + } + Vector filterOpts = (Vector) filters.get(filterKey); + if (filterOpts == null) { + filterOpts = new Vector(); + filters.put(filterKey, filterOpts); + } + if (dotIdx != -1) { + final String value = OptionConverter.findAndSubst(key, properties); + filterOpts.add(new NameValue(name, value)); + } + } + } + + // sort filters by IDs, insantiate filters, set filter options, + // add filters to the appender + final Enumeration g = new SortedKeyEnumeration(filters); + Filter head = null; + while (g.hasMoreElements()) { + final String key = (String) g.nextElement(); + final String clazz = properties.getProperty(key); + if (clazz != null) { + LogLog.debug("Filter key: [" + key + "] class: [" + properties.getProperty(key) + "] props: " + filters.get(key)); + final Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null); + if (filter != null) { + final PropertySetter propSetter = new PropertySetter(filter); + final Vector v = (Vector) filters.get(key); + final Enumeration filterProps = v.elements(); + while (filterProps.hasMoreElements()) { + final NameValue kv = (NameValue) filterProps.nextElement(); + propSetter.setProperty(kv.key, kv.value); + } + propSetter.activate(); + LogLog.debug("Adding filter of type [" + filter.getClass() + "] to appender named [" + appender.getName() + "]."); + head = FilterAdapter.addFilter(head, filter); + } + } else { + LogLog.warn("Missing class definition for filter: [" + key + "]"); + } + } + appender.addFilter(head); + } + + /** + * This method must work for the root category as well. + */ + void parseCategory(final Properties properties, final Logger logger, final String optionKey, final String loggerName, final String value) { + + LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "]."); + // We must skip over ',' but not white space + final StringTokenizer st = new StringTokenizer(value, ","); + + // If value is not in the form ", appender.." or "", then we should set + // the level of the loggeregory. + + if (!(value.startsWith(",") || value.equals(""))) { + + // just to be on the safe side... + if (!st.hasMoreTokens()) { + return; + } + + final String levelStr = st.nextToken(); + LogLog.debug("Level token is [" + levelStr + "]."); + + // If the level value is inherited, set category level value to + // null. We also check that the user has not specified inherited for the + // root category. + if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) { + if (loggerName.equals(INTERNAL_ROOT_NAME)) { + LogLog.warn("The root logger cannot be set to null."); + } else { + logger.setLevel(null); + } + } else { + logger.setLevel(OptionConverter.toLevel(levelStr, Log4j1Configuration.DEFAULT_LEVEL)); + } + LogLog.debug("Category " + loggerName + " set to " + logger.getLevel()); + } + + // Begin by removing all existing appenders. + logger.removeAllAppenders(); + + Appender appender; + String appenderName; + while (st.hasMoreTokens()) { + appenderName = st.nextToken().trim(); + if (appenderName == null || appenderName.equals(",")) { + continue; + } + LogLog.debug("Parsing appender named \"" + appenderName + "\"."); + appender = parseAppender(properties, appenderName); + if (appender != null) { + logger.addAppender(appender); + } + } + } + + /** + * Parse non-root elements, such non-root categories and renderers. + */ + protected void parseCatsAndRenderers(final Properties properties, final LoggerRepository loggerRepository) { + final Enumeration enumeration = properties.propertyNames(); + while (enumeration.hasMoreElements()) { + final String key = (String) enumeration.nextElement(); + if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) { + String loggerName = null; + if (key.startsWith(CATEGORY_PREFIX)) { + loggerName = key.substring(CATEGORY_PREFIX.length()); + } else if (key.startsWith(LOGGER_PREFIX)) { + loggerName = key.substring(LOGGER_PREFIX.length()); + } + final String value = OptionConverter.findAndSubst(key, properties); + final Logger logger = loggerRepository.getLogger(loggerName, loggerFactory); + synchronized (logger) { + parseCategory(properties, logger, key, loggerName, value); + parseAdditivityForLogger(properties, logger, loggerName); + } + } else if (key.startsWith(RENDERER_PREFIX)) { + final String renderedClass = key.substring(RENDERER_PREFIX.length()); + final String renderingClass = OptionConverter.findAndSubst(key, properties); + if (loggerRepository instanceof RendererSupport) { + RendererMap.addRenderer((RendererSupport) loggerRepository, renderedClass, renderingClass); + } + } else if (key.equals(THROWABLE_RENDERER_PREFIX)) { + if (loggerRepository instanceof ThrowableRendererSupport) { + final ThrowableRenderer tr = (ThrowableRenderer) OptionConverter.instantiateByKey(properties, THROWABLE_RENDERER_PREFIX, + org.apache.log4j.spi.ThrowableRenderer.class, null); + if (tr == null) { + LogLog.error("Could not instantiate throwableRenderer."); + } else { + final PropertySetter setter = new PropertySetter(tr); + setter.setProperties(properties, THROWABLE_RENDERER_PREFIX + "."); + ((ThrowableRendererSupport) loggerRepository).setThrowableRenderer(tr); + + } + } + } + } + } + + private void parseErrorHandler(final ErrorHandler errorHandler, final String errorHandlerPrefix, final Properties props, + final LoggerRepository loggerRepository) { + if (errorHandler != null && loggerRepository != null) { + final boolean rootRef = OptionConverter.toBoolean(OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false); + if (rootRef) { + errorHandler.setLogger(loggerRepository.getRootLogger()); + } + final String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF, props); + if (loggerName != null) { + final Logger logger = loggerFactory == null ? loggerRepository.getLogger(loggerName) : loggerRepository.getLogger(loggerName, loggerFactory); + errorHandler.setLogger(logger); + } + final String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props); + if (appenderName != null) { + final Appender backup = parseAppender(props, appenderName); + if (backup != null) { + errorHandler.setBackupAppender(backup); + } + } + } + } + + Appender registryGet(final String name) { + return (Appender) registry.get(name); + } + + void registryPut(final Appender appender) { + registry.put(appender.getName(), appender); } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/ProvisionNode.java b/log4j-1.2-api/src/main/java/org/apache/log4j/ProvisionNode.java new file mode 100644 index 00000000000..31cfb825d6f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/ProvisionNode.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import java.util.Vector; + +class ProvisionNode extends Vector { + private static final long serialVersionUID = -4479121426311014469L; + + ProvisionNode(final Logger logger) { + super(); + this.addElement(logger); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/RenderedMessage.java b/log4j-1.2-api/src/main/java/org/apache/log4j/RenderedMessage.java new file mode 100644 index 00000000000..422a565a398 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/RenderedMessage.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.or.ObjectRenderer; +import org.apache.logging.log4j.message.Message; + +/** + * Implements object rendering for Log4j 1.x compatibility. + */ +public class RenderedMessage implements Message { + + private final ObjectRenderer renderer; + private final Object object; + private String rendered = null; + + public RenderedMessage(ObjectRenderer renderer, Object object) { + this.renderer = renderer; + this.object = object; + } + + + @Override + public String getFormattedMessage() { + if (rendered == null) { + rendered = renderer.doRender(object); + } + + return rendered; + } + + @Override + public String getFormat() { + return getFormattedMessage(); + } + + @Override + public Object[] getParameters() { + return null; + } + + @Override + public Throwable getThrowable() { + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java new file mode 100644 index 00000000000..2577b9a271b --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/RollingFileAppender.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import java.io.File; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.Writer; + +import org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * RollingFileAppender extends FileAppender to backup the log files when they reach a certain size. + * + * The log4j extras companion includes alternatives which should be considered for new deployments and which are + * discussed in the documentation for org.apache.log4j.rolling.RollingFileAppender. + */ +public class RollingFileAppender extends FileAppender { + + /** + * The default maximum file size is 10MB. + */ + protected long maxFileSize = 10 * 1024 * 1024; + + /** + * There is one backup file by default. + */ + protected int maxBackupIndex = 1; + + private long nextRollover = 0; + + /** + * The default constructor simply calls its {@link FileAppender#FileAppender parents constructor}. + */ + public RollingFileAppender() { + super(); + } + + /** + * Constructs a RollingFileAppender and open the file designated by filename. The opened filename will + * become the ouput destination for this appender. + * + *

+ * If the append parameter is true, the file will be appended to. Otherwise, the file desginated by + * filename will be truncated before being opened. + *

+ */ + public RollingFileAppender(Layout layout, String filename, boolean append) throws IOException { + super(layout, filename, append); + } + + /** + * Constructs a FileAppender and open the file designated by filename. The opened filename will become the + * output destination for this appender. + * + *

+ * The file will be appended to. + *

+ */ + public RollingFileAppender(Layout layout, String filename) throws IOException { + super(layout, filename); + } + + /** + * Gets the value of the MaxBackupIndex option. + */ + public int getMaxBackupIndex() { + return maxBackupIndex; + } + + /** + * Gets the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * @since 1.1 + */ + public long getMaximumFileSize() { + return maxFileSize; + } + + /** + * Implements the usual roll over behaviour. + *

+ * If MaxBackupIndex is positive, then files {File.1, ..., + * File.MaxBackupIndex -1} are renamed to {File.2, ..., File.MaxBackupIndex}. + * Moreover, File is renamed File.1 and closed. A new File is created to receive + * further log output. + *

+ *

+ * If MaxBackupIndex is equal to zero, then the File is truncated with no backup files + * created. + *

+ */ + public // synchronization not necessary since doAppend is alreasy synched + void rollOver() { + File target; + File file; + + if (qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + LogLog.debug("rolling over count=" + size); + // if operation fails, do not roll again until + // maxFileSize more bytes are written + nextRollover = size + maxFileSize; + } + LogLog.debug("maxBackupIndex=" + maxBackupIndex); + + boolean renameSucceeded = true; + // If maxBackups <= 0, then there is no file renaming to be done. + if (maxBackupIndex > 0) { + // Delete the oldest file, to keep Windows happy. + file = new File(fileName + '.' + maxBackupIndex); + if (file.exists()) + renameSucceeded = file.delete(); + + // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} + for (int i = maxBackupIndex - 1; i >= 1 && renameSucceeded; i--) { + file = new File(fileName + "." + i); + if (file.exists()) { + target = new File(fileName + '.' + (i + 1)); + LogLog.debug("Renaming file " + file + " to " + target); + renameSucceeded = file.renameTo(target); + } + } + + if (renameSucceeded) { + // Rename fileName to fileName.1 + target = new File(fileName + "." + 1); + + this.closeFile(); // keep windows happy. + + file = new File(fileName); + LogLog.debug("Renaming file " + file + " to " + target); + renameSucceeded = file.renameTo(target); + // + // if file rename failed, reopen file with append = true + // + if (!renameSucceeded) { + try { + this.setFile(fileName, true, bufferedIO, bufferSize); + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.error("setFile(" + fileName + ", true) call failed.", e); + } + } + } + } + + // + // if all renames were successful, then + // + if (renameSucceeded) { + try { + // This will also close the file. This is OK since multiple + // close operations are safe. + this.setFile(fileName, false, bufferedIO, bufferSize); + nextRollover = 0; + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.error("setFile(" + fileName + ", false) call failed.", e); + } + } + } + + public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize) throws IOException { + super.setFile(fileName, append, this.bufferedIO, this.bufferSize); + if (append) { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + /** + * Sets the maximum number of backup files to keep around. + * + *

+ * The MaxBackupIndex option determines how many backup files are kept before the oldest is erased. This option + * takes a positive integer value. If set to zero, then there will be no backup files and the log file will be truncated + * when it reaches MaxFileSize. + *

+ */ + public void setMaxBackupIndex(int maxBackups) { + this.maxBackupIndex = maxBackups; + } + + /** + * Sets the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + *

+ * This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter taking + * a long argument from the setter taking a String argument by the JavaBeans + * {@link java.beans.Introspector Introspector}. + *

+ * + * @see #setMaxFileSize(String) + */ + public void setMaximumFileSize(long maxFileSize) { + this.maxFileSize = maxFileSize; + } + + /** + * Sets the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + *

+ * In configuration files, the MaxFileSize option takes an long integer in the range 0 - 2^63. You can specify + * the value with the suffixes "KB", "MB" or "GB" so that the integer is interpreted being expressed respectively in + * kilobytes, megabytes or gigabytes. For example, the value "10KB" will be interpreted as 10240. + *

+ */ + public void setMaxFileSize(String value) { + maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1); + } + + protected void setQWForFiles(Writer writer) { + this.qw = new CountingQuietWriter(writer, errorHandler); + } + + /** + * This method differentiates RollingFileAppender from its super class. + * + * @since 0.9.0 + */ + protected void subAppend(LoggingEvent event) { + super.subAppend(event); + if (fileName != null && qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + if (size >= maxFileSize && size >= nextRollover) { + rollOver(); + } + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/SimpleLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/SimpleLayout.java new file mode 100644 index 00000000000..2ed850dc360 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/SimpleLayout.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.util.Strings; + +/** + * Simple-layout. + */ +public class SimpleLayout extends Layout { + + /** + * {@inheritDoc} + */ + @Override + public String format(final LoggingEvent theEvent) { + return Strings.EMPTY; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean ignoresThrowable() { + return true; + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/VectorAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/VectorAppender.java new file mode 100644 index 00000000000..97af6055bf9 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/VectorAppender.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import java.util.Vector; + +import org.apache.log4j.spi.LoggingEvent; + +/** + * Appends logging events to a vector. + */ +public class VectorAppender extends AppenderSkeleton { + + public Vector vector; + + public VectorAppender() { + vector = new Vector(); + } + + /** + * Does nothing. + */ + @Override + public void activateOptions() { + // noop + } + + /** + * This method is called by the {@link AppenderSkeleton#doAppend} method. + * + */ + @Override + public void append(final LoggingEvent event) { + // System.out.println("---Vector appender called with message ["+event.getRenderedMessage()+"]."); + // System.out.flush(); + try { + Thread.sleep(100); + } catch (final Exception e) { + // ignore + } + vector.addElement(event); + } + + @Override + public synchronized void close() { + if (this.closed) { + return; + } + this.closed = true; + } + + public Vector getVector() { + return vector; + } + + public boolean isClosed() { + return closed; + } + + @Override + public boolean requiresLayout() { + return false; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/WriterAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/WriterAppender.java new file mode 100644 index 00000000000..760449c2374 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/WriterAppender.java @@ -0,0 +1,396 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import org.apache.log4j.helpers.QuietWriter; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.status.StatusLogger; + + +/** + * WriterAppender appends log events to a {@link Writer} or an + * {@link OutputStream} depending on the user's choice. + */ +public class WriterAppender extends AppenderSkeleton { + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + /** + * Immediate flush means that the underlying writer or output stream + * will be flushed at the end of each append operation unless shouldFlush() + * is overridden. Immediate + * flush is slower but ensures that each append request is actually + * written. If immediateFlush is set to + * false, then there is a good chance that the last few + * logs events are not actually written to persistent media if and + * when the application crashes. + * + *

The immediateFlush variable is set to + * true by default. + */ + protected boolean immediateFlush = true; + + /** + * The encoding to use when writing.

The + * encoding variable is set to null by + * default which results in the utilization of the system's default + * encoding. + */ + protected String encoding; + + /** + * This is the {@link QuietWriter quietWriter} where we will write + * to. + */ + protected QuietWriter qw; + + + /** + * This default constructor does nothing. + */ + public WriterAppender() { + } + + /** + * Instantiate a WriterAppender and set the output destination to a + * new {@link OutputStreamWriter} initialized with os + * as its {@link OutputStream}. + * @param layout The Layout. + * @param os The OutputStream. + */ + public WriterAppender(Layout layout, OutputStream os) { + this(layout, new OutputStreamWriter(os)); + } + + /** + * Instantiate a WriterAppender and set the output destination to + * writer. + * + *

The writer must have been previously opened by + * the user. + * + * @param layout The Layout. + * @param writer The Writer. + */ + public WriterAppender(Layout layout, Writer writer) { + this.layout = layout; + this.setWriter(writer); + } + + /** + * Returns value of the ImmediateFlush option. + * @return the value of the immediate flush setting. + */ + public boolean getImmediateFlush() { + return immediateFlush; + } + + /** + * If the ImmediateFlush option is set to + * true, the appender will flush at the end of each + * write. This is the default behavior. If the option is set to + * false, then the underlying stream can defer writing + * to physical medium to a later time. + * + *

Avoiding the flush operation at the end of each append results in + * a performance gain of 10 to 20 percent. However, there is safety + * tradeoff involved in skipping flushing. Indeed, when flushing is + * skipped, then it is likely that the last few log events will not + * be recorded on disk when the application exits. This is a high + * price to pay even for a 20% performance gain. + * + * @param value the value to set the immediate flush setting to. + */ + public void setImmediateFlush(boolean value) { + immediateFlush = value; + } + + /** + * Does nothing. + */ + @Override + public void activateOptions() { + } + + + /** + * This method is called by the {@link AppenderSkeleton#doAppend} + * method. + * + *

If the output stream exists and is writable then write a log + * statement to the output stream. Otherwise, write a single warning + * message to System.err. + * + *

The format of the output will depend on this appender's + * layout. + */ + @Override + public void append(LoggingEvent event) { + + // Reminder: the nesting of calls is: + // + // doAppend() + // - check threshold + // - filter + // - append(); + // - checkEntryConditions(); + // - subAppend(); + + if (!checkEntryConditions()) { + return; + } + subAppend(event); + } + + /** + * This method determines if there is a sense in attempting to append. + * + *

It checks whether there is a set output target and also if + * there is a set layout. If these checks fail, then the boolean + * value false is returned. + * @return true if appending is allowed, false otherwise. + */ + protected boolean checkEntryConditions() { + if (this.closed) { + LOGGER.warn("Not allowed to write to a closed appender."); + return false; + } + + if (this.qw == null) { + errorHandler.error("No output stream or file set for the appender named [" + name + "]."); + return false; + } + + if (this.layout == null) { + errorHandler.error("No layout set for the appender named [" + name + "]."); + return false; + } + return true; + } + + + /** + * Close this appender instance. The underlying stream or writer is + * also closed. + * + *

Closed appenders cannot be reused. + * + * @see #setWriter + * @since 0.8.4 + */ + @Override + public synchronized void close() { + if (this.closed) { + return; + } + this.closed = true; + writeFooter(); + reset(); + } + + /** + * Close the underlying {@link Writer}. + */ + protected void closeWriter() { + if (qw != null) { + try { + qw.close(); + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + // There is do need to invoke an error handler at this late + // stage. + LOGGER.error("Could not close " + qw, e); + } + } + } + + /** + * Returns an OutputStreamWriter when passed an OutputStream. The + * encoding used will depend on the value of the + * encoding property. If the encoding value is + * specified incorrectly the writer will be opened using the default + * system encoding (an error message will be printed to the LOGGER. + * @param os The OutputStream. + * @return The OutputStreamWriter. + */ + protected OutputStreamWriter createWriter(OutputStream os) { + OutputStreamWriter retval = null; + + String enc = getEncoding(); + if (enc != null) { + try { + retval = new OutputStreamWriter(os, enc); + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.warn("Error initializing output writer."); + LOGGER.warn("Unsupported encoding?"); + } + } + if (retval == null) { + retval = new OutputStreamWriter(os); + } + return retval; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String value) { + encoding = value; + } + + + /** + * Set the {@link ErrorHandler} for this WriterAppender and also the + * underlying {@link QuietWriter} if any. + */ + @Override + public synchronized void setErrorHandler(ErrorHandler eh) { + if (eh == null) { + LOGGER.warn("You have tried to set a null error-handler."); + } else { + this.errorHandler = eh; + if (this.qw != null) { + this.qw.setErrorHandler(eh); + } + } + } + + /** + *

Sets the Writer where the log output will go. The + * specified Writer must be opened by the user and be + * writable. + * + *

The java.io.Writer will be closed when the + * appender instance is closed. + * + * + *

WARNING: Logging to an unopened Writer will fail. + *

+ * + * @param writer An already opened Writer. + */ + public synchronized void setWriter(Writer writer) { + reset(); + this.qw = new QuietWriter(writer, errorHandler); + //this.tp = new TracerPrintWriter(qw); + writeHeader(); + } + + + /** + * Actual writing occurs here. + * + *

Most subclasses of WriterAppender will need to + * override this method. + * @param event The event to log. + * + * @since 0.9.0 + */ + protected void subAppend(LoggingEvent event) { + this.qw.write(this.layout.format(event)); + + if (layout.ignoresThrowable()) { + String[] s = event.getThrowableStrRep(); + if (s != null) { + int len = s.length; + for (int i = 0; i < len; i++) { + this.qw.write(s[i]); + this.qw.write(Layout.LINE_SEP); + } + } + } + + if (shouldFlush(event)) { + this.qw.flush(); + } + } + + + /** + * The WriterAppender requires a layout. Hence, this method returns + * true. + */ + @Override + public boolean requiresLayout() { + return true; + } + + /** + * Clear internal references to the writer and other variables. + *

+ * Subclasses can override this method for an alternate closing + * behavior. + */ + protected void reset() { + closeWriter(); + this.qw = null; + //this.tp = null; + } + + + /** + * Write a footer as produced by the embedded layout's {@link + * Layout#getFooter} method. + */ + protected void writeFooter() { + if (layout != null) { + String f = layout.getFooter(); + if (f != null && this.qw != null) { + this.qw.write(f); + this.qw.flush(); + } + } + } + + /** + * Write a header as produced by the embedded layout's {@link + * Layout#getHeader} method. + */ + protected void writeHeader() { + if (layout != null) { + String h = layout.getHeader(); + if (h != null && this.qw != null) { + this.qw.write(h); + } + } + } + + /** + * Determines whether the writer should be flushed after + * this event is written. + * @param event The event to log. + * @return true if the writer should be flushed. + * + * @since 1.2.16 + */ + protected boolean shouldFlush(final LoggingEvent event) { + return immediateFlush; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java new file mode 100644 index 00000000000..23931b2ee65 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import java.io.Serializable; + +import org.apache.log4j.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.util.Strings; + +/** + * Binds a Log4j 1.x Appender to Log4j 2. + */ +public class AppenderAdapter { + + private final Appender appender; + private final Adapter adapter; + + /** + * Adapts a Log4j 1.x appender into a Log4j 2.x appender. Applying this method + * on the result of + * {@link AppenderWrapper#adapt(org.apache.logging.log4j.core.Appender)} should + * return the original Log4j 2.x appender. + * + * @param appender a Log4j 1.x appender + * @return a Log4j 2.x appender or {@code null} if the parameter is {@code null} + */ + public static org.apache.logging.log4j.core.Appender adapt(Appender appender) { + if (appender instanceof org.apache.logging.log4j.core.Appender) { + return (org.apache.logging.log4j.core.Appender) appender; + } + if (appender instanceof AppenderWrapper) { + return ((AppenderWrapper) appender).getAppender(); + } + if (appender != null) { + return new AppenderAdapter(appender).getAdapter(); + } + return null; + } + + /** + * Constructor. + * @param appender The Appender to wrap. + */ + private AppenderAdapter(Appender appender) { + this.appender = appender; + final org.apache.logging.log4j.core.Filter appenderFilter = FilterAdapter.adapt(appender.getFilter()); + String name = appender.getName(); + if (Strings.isEmpty(name)) { + name = String.format("0x%08x", appender.hashCode()); + } + this.adapter = new Adapter(name, appenderFilter, null, true, null); + } + + public Adapter getAdapter() { + return adapter; + } + + public class Adapter extends AbstractAppender { + + protected Adapter(final String name, final Filter filter, final Layout layout, + final boolean ignoreExceptions, final Property[] properties) { + super(name, filter, layout, ignoreExceptions, properties); + } + + @Override + public void append(LogEvent event) { + appender.doAppend(new LogEventAdapter(event)); + } + + @Override + public void stop() { + appender.close(); + } + + public Appender getAppender() { + return appender; + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java new file mode 100644 index 00000000000..d4b1869cd0a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderAdapter.Adapter; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.filter.AbstractFilterable; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Wraps a Log4j 2 Appender in an empty Log4j 1 Appender so it can be extracted when constructing the configuration. + * Allows a Log4j 1 Appender to reference a Log4j 2 Appender. + */ +public class AppenderWrapper implements Appender { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private final org.apache.logging.log4j.core.Appender appender; + + /** + * Adapts a Log4j 2.x appender into a Log4j 1.x appender. Applying this method + * on the result of {@link AppenderAdapter#adapt(Appender)} should return the + * original Log4j 1.x appender. + * + * @param appender a Log4j 2.x appender + * @return a Log4j 1.x appender or {@code null} if the parameter is {@code null} + */ + public static Appender adapt(org.apache.logging.log4j.core.Appender appender) { + if (appender instanceof Appender) { + return (Appender) appender; + } + if (appender instanceof Adapter) { + Adapter adapter = (Adapter) appender; + // Don't unwrap an appender with filters + if (!adapter.hasFilter()) { + return adapter.getAppender(); + } + } + if (appender != null) { + return new AppenderWrapper(appender); + } + return null; + } + + /** + * Constructs a new instance for a Core Appender. + * + * @param appender a Core Appender. + */ + public AppenderWrapper(org.apache.logging.log4j.core.Appender appender) { + this.appender = appender; + } + + /** + * Gets the wrapped Core Appender. + * + * @return the wrapped Core Appender. + */ + public org.apache.logging.log4j.core.Appender getAppender() { + return appender; + } + + @Override + public void addFilter(Filter newFilter) { + if (appender instanceof AbstractFilterable) { + ((AbstractFilterable) appender).addFilter(FilterAdapter.adapt(newFilter)); + } else { + LOGGER.warn("Unable to add filter to appender {}, it does not support filters", appender.getName()); + } + } + + @Override + public Filter getFilter() { + return null; + } + + @Override + public void clearFilters() { + // noop + } + + @Override + public void close() { + // Not supported with Log4j 2. + } + + @Override + public void doAppend(LoggingEvent event) { + if (event instanceof LogEventAdapter) { + appender.append(((LogEventAdapter) event).getEvent()); + } + } + + @Override + public String getName() { + return appender.getName(); + } + + @Override + public void setErrorHandler(ErrorHandler errorHandler) { + appender.setHandler(new ErrorHandlerAdapter(errorHandler)); + } + + @Override + public ErrorHandler getErrorHandler() { + return ((ErrorHandlerAdapter) appender.getHandler()).getHandler(); + } + + @Override + public void setLayout(Layout layout) { + // Log4j 2 doesn't support this. + } + + @Override + public Layout getLayout() { + return new LayoutWrapper(appender.getLayout()); + } + + @Override + public void setName(String name) { + // Log4j 2 doesn't support this. + } + + @Override + public boolean requiresLayout() { + return false; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java new file mode 100644 index 00000000000..1f166a25b02 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import org.apache.log4j.spi.ErrorHandler; +import org.apache.logging.log4j.core.LogEvent; + +/** + * Makes a Log4j 1 ErrorHandler usable by a Log4j 2 Appender. + */ +public class ErrorHandlerAdapter implements org.apache.logging.log4j.core.ErrorHandler { + + private final ErrorHandler errorHandler; + + public ErrorHandlerAdapter(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + public ErrorHandler getHandler() { + return errorHandler; + } + + @Override + public void error(String msg) { + errorHandler.error(msg); + } + + @Override + public void error(String msg, Throwable t) { + if (t instanceof Exception) { + errorHandler.error(msg, (Exception) t, 0); + } else { + errorHandler.error(msg); + } + } + + @Override + public void error(String msg, LogEvent event, Throwable t) { + if (t == null || t instanceof Exception) { + errorHandler.error(msg, (Exception) t, 0, new LogEventAdapter(event)); + } else { + errorHandler.error(msg); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java new file mode 100644 index 00000000000..71104333bd8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.filter.AbstractFilter; +import org.apache.logging.log4j.core.filter.CompositeFilter; + +/** + * Binds a Log4j 1.x Filter with Log4j 2. + */ +public class FilterAdapter extends AbstractFilter { + + private final Filter filter; + + /** + * Adapts a Log4j 1.x filter into a Log4j 2.x filter. Applying this method to + * the result of + * {@link FilterWrapper#adapt(org.apache.logging.log4j.core.Filter)} should + * return the original Log4j 2.x filter. + * + * @param filter a Log4j 1.x filter + * @return a Log4j 2.x filter or {@code null} if the parameter is {@code null} + */ + public static org.apache.logging.log4j.core.Filter adapt(Filter filter) { + if (filter instanceof org.apache.logging.log4j.core.Filter) { + return (org.apache.logging.log4j.core.Filter) filter; + } + // Don't unwrap the head of a filter chain + if (filter instanceof FilterWrapper && filter.getNext() == null) { + return ((FilterWrapper) filter).getFilter(); + } + if (filter != null) { + return new FilterAdapter(filter); + } + return null; + } + + /** + * Appends one filter to another using Log4j 2.x concatenation utilities. + * @param first + * @param second + * @return + */ + public static Filter addFilter(Filter first, Filter second) { + if (first == null) { + return second; + } + if (second == null) { + return first; + } + final CompositeFilter composite; + if (first instanceof FilterWrapper && ((FilterWrapper) first).getFilter() instanceof CompositeFilter) { + composite = (CompositeFilter) ((FilterWrapper) first).getFilter(); + } else { + composite = CompositeFilter.createFilters(new org.apache.logging.log4j.core.Filter[] {adapt(first)}); + } + return FilterWrapper.adapt(composite.addFilter(adapt(second))); + } + + private FilterAdapter(Filter filter) { + this.filter = filter; + } + + @Override + public Result filter(LogEvent event) { + LoggingEvent loggingEvent = new LogEventAdapter(event); + Filter next = filter; + while (next != null) { + switch (next.decide(loggingEvent)) { + case Filter.ACCEPT: + return Result.ACCEPT; + case Filter.DENY: + return Result.DENY; + default: + } + next = next.getNext(); + } + return Result.NEUTRAL; + } + + /** + * Gets the actual filter. + * + * @return the actual filter. + * @since 2.17.1 + */ + public Filter getFilter() { + return filter; + } + + @Override + public void start() { + filter.activateOptions(); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterWrapper.java new file mode 100644 index 00000000000..9783be38cad --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterWrapper.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * This acts as a container for Log4j 2 Filters to be attached to Log4j 1 components. However, the Log4j 2 + * Filters will always be called directly so this class just acts as a container. + */ +public class FilterWrapper extends Filter { + + private final org.apache.logging.log4j.core.Filter filter; + + /** + * Adapts a Log4j 2.x filter into a Log4j 1.x filter. Applying this method to + * the result of {@link FilterAdapter#adapt(Filter)} should return the original + * Log4j 1.x filter. + * + * @param filter a Log4j 2.x filter + * @return a Log4j 1.x filter or {@code null} if the parameter is {@code null} + */ + public static Filter adapt(org.apache.logging.log4j.core.Filter filter) { + if (filter instanceof Filter) { + return (Filter) filter; + } + if (filter instanceof FilterAdapter) { + return ((FilterAdapter) filter).getFilter(); + } + if (filter != null) { + return new FilterWrapper(filter); + } + return null; + } + + public FilterWrapper(org.apache.logging.log4j.core.Filter filter) { + this.filter = filter; + } + + public org.apache.logging.log4j.core.Filter getFilter() { + return filter; + } + + /** + * This method is never called. + * @param event The LoggingEvent to decide upon. + * @return 0 + */ + @Override + public int decide(LoggingEvent event) { + return 0; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutAdapter.java new file mode 100644 index 00000000000..a98b0a86466 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutAdapter.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.layout.ByteBufferDestination; + +/** + * Class Description goes here. + */ +public class LayoutAdapter implements org.apache.logging.log4j.core.Layout { + private final Layout layout; + + /** + * Adapts a Log4j 1.x layout into a Log4j 2.x layout. Applying this method to + * the result of + * {@link LayoutWrapper#adapt(org.apache.logging.log4j.core.Layout)} should + * return the original Log4j 2.x layout. + * + * @param layout a Log4j 1.x layout + * @return a Log4j 2.x layout or {@code null} if the parameter is {@code null} + */ + public static org.apache.logging.log4j.core.Layout adapt(Layout layout) { + if (layout instanceof LayoutWrapper) { + return ((LayoutWrapper) layout).getLayout(); + } + if (layout != null) { + return new LayoutAdapter(layout); + } + return null; + } + + private LayoutAdapter(Layout layout) { + this.layout = layout; + } + + public Layout getLayout() { + return layout; + } + + @Override + public byte[] getFooter() { + return layout.getFooter() == null ? null : layout.getFooter().getBytes(); + } + + @Override + public byte[] getHeader() { + return layout.getHeader() == null ? null : layout.getHeader().getBytes(); + } + + @Override + public byte[] toByteArray(LogEvent event) { + String result = layout.format(new LogEventAdapter(event)); + return result == null ? null : result.getBytes(); + } + + @Override + public String toSerializable(LogEvent event) { + return layout.format(new LogEventAdapter(event)); + } + + @Override + public String getContentType() { + return layout.getContentType(); + } + + @Override + public Map getContentFormat() { + return new HashMap<>(); + } + + @Override + public void encode(LogEvent event, ByteBufferDestination destination) { + final byte[] data = toByteArray(event); + destination.writeBytes(data, 0, data.length); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutWrapper.java new file mode 100644 index 00000000000..76d6d5de6eb --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutWrapper.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import org.apache.log4j.Layout; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Bridge between the Log4j 1 Layout and a Log4j 2 Layout. + */ +public class LayoutWrapper extends Layout { + + private final org.apache.logging.log4j.core.Layout layout; + + /** + * Adapts a Log4j 2.x layout into a Log4j 1.x layout. Applying this method to + * the result of {@link LayoutAdapter#adapt(Layout)} should return the original + * Log4j 1.x layout. + * + * @param layout a Log4j 2.x layout + * @return a Log4j 1.x layout or {@code null} if the parameter is {@code null} + */ + public static Layout adapt(org.apache.logging.log4j.core.Layout layout) { + if (layout instanceof LayoutAdapter) { + return ((LayoutAdapter) layout).getLayout(); + } + if (layout != null) { + return new LayoutWrapper(layout); + } + return null; + } + + /** + * Constructs a new instance. + * + * @param layout The layout to wrap. + */ + public LayoutWrapper(org.apache.logging.log4j.core.Layout layout) { + this.layout = layout; + } + + @Override + public String format(LoggingEvent event) { + return layout.toSerializable(((LogEventAdapter)event).getEvent()).toString(); + } + + /** + * Unwraps. + * + * @return The wrapped object. + */ + public org.apache.logging.log4j.core.Layout getLayout() { + return this.layout; + } + + @Override + public boolean ignoresThrowable() { + return false; + } + + @Override + public String toString() { + return String.format("LayoutWrapper [layout=%s]", layout); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java new file mode 100644 index 00000000000..a3ce275335f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Category; +import org.apache.log4j.Level; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LocationInfo; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; + +/** + * Converts a Log4j 2 LogEvent into the components needed by a Log4j 1.x LoggingEvent. + * This class requires Log4j 2. + */ +public class LogEventAdapter extends LoggingEvent { + + private static final long JVM_START_TIME = initStartTime(); + + private final LogEvent event; + + public LogEventAdapter(LogEvent event) { + this.event = event; + } + + /** + * Returns the time when the application started, in milliseconds + * elapsed since 01.01.1970. + * @return the time when the JVM started. + */ + public static long getStartTime() { + return JVM_START_TIME; + } + + /** + * Returns the result of {@code ManagementFactory.getRuntimeMXBean().getStartTime()}, + * or the current system time if JMX is not available. + */ + private static long initStartTime() { + // We'd like to call ManagementFactory.getRuntimeMXBean().getStartTime(), + // but Google App Engine throws a java.lang.NoClassDefFoundError + // "java.lang.management.ManagementFactory is a restricted class". + // The reflection is necessary because without it, Google App Engine + // will refuse to initialize this class. + try { + final Class factoryClass = Loader.loadSystemClass("java.lang.management.ManagementFactory"); + final Method getRuntimeMXBean = factoryClass.getMethod("getRuntimeMXBean"); + final Object runtimeMXBean = getRuntimeMXBean.invoke(null); + + final Class runtimeMXBeanClass = Loader.loadSystemClass("java.lang.management.RuntimeMXBean"); + final Method getStartTime = runtimeMXBeanClass.getMethod("getStartTime"); + return (Long) getStartTime.invoke(runtimeMXBean); + } catch (final Throwable t) { + StatusLogger.getLogger().error("Unable to call ManagementFactory.getRuntimeMXBean().getStartTime(), " + + "using system time for OnStartupTriggeringPolicy", t); + // We have little option but to declare "now" as the beginning of time. + return System.currentTimeMillis(); + } + } + + public LogEvent getEvent() { + return this.event; + } + + /** + * Set the location information for this logging event. The collected + * information is cached for future use. + */ + @Override + public LocationInfo getLocationInformation() { + return new LocationInfo(event.getSource()); + } + + /** + * Return the level of this event. Use this form instead of directly + * accessing the level field. + */ + @Override + public Level getLevel() { + return OptionConverter.convertLevel(event.getLevel()); + } + + /** + * Return the name of the logger. Use this form instead of directly + * accessing the categoryName field. + */ + @Override + public String getLoggerName() { + return event.getLoggerName(); + } + + @Override + public long getTimeStamp() { + return event.getTimeMillis(); + } + + /** + * Gets the logger of the event. + */ + @Override + public Category getLogger() { + return Category.getInstance(event.getLoggerName()); + } + + /* + Return the message for this logging event. + */ + @Override + public Object getMessage() { + return event.getMessage(); + } + + /* + * This method returns the NDC for this event. + */ + @Override + public String getNDC() { + return event.getContextStack().toString(); + } + + /* + Returns the context corresponding to the key parameter. + */ + @Override + public Object getMDC(String key) { + if (event.getContextData() != null) { + return event.getContextData().getValue(key); + } + return null; + } + + /** + * Obtain a copy of this thread's MDC prior to serialization or + * asynchronous logging. + */ + @Override + public void getMDCCopy() { + } + + @Override + public String getRenderedMessage() { + return event.getMessage().getFormattedMessage(); + } + + @Override + public String getThreadName() { + return event.getThreadName(); + } + + /** + * Returns the throwable information contained within this + * event. May be null if there is no such information. + * + *

Note that the {@link Throwable} object contained within a + * {@link ThrowableInformation} does not survive serialization. + * + * @since 1.1 + */ + @Override + public ThrowableInformation getThrowableInformation() { + if (event.getThrown() != null) { + return new ThrowableInformation(event.getThrown()); + } + return null; + } + + /** + * Return this event's throwable's string[] representaion. + */ + @Override + public String[] getThrowableStrRep() { + if (event.getThrown() != null) { + return Throwables.toStringList(event.getThrown()).toArray(Strings.EMPTY_ARRAY); + } + return null; + } + + @Override + public String getProperty(final String key) { + return event.getContextData().getValue(key); + } + + @Override + public Set getPropertyKeySet() { + return event.getContextData().toMap().keySet(); + } + + @Override + public Map getProperties() { + return event.getContextData().toMap(); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventWrapper.java new file mode 100644 index 00000000000..600f69f6d0e --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventWrapper.java @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.apache.log4j.NDC; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LocationInfo; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.MutableThreadContextStack; +import org.apache.logging.log4j.util.BiConsumer; +import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.TriConsumer; + +/** + * Exposes a Log4j 1 logging event as a Log4j 2 LogEvent. + */ +public class LogEventWrapper implements LogEvent { + + private final LoggingEvent event; + private final ContextDataMap contextData; + private final MutableThreadContextStack contextStack; + private Thread thread; + + public LogEventWrapper(LoggingEvent event) { + this.event = event; + this.contextData = new ContextDataMap(event.getProperties()); + this.contextStack = new MutableThreadContextStack(NDC.cloneStack()); + this.thread = Objects.equals(event.getThreadName(), Thread.currentThread().getName()) + ? Thread.currentThread() : null; + } + + @Override + public LogEvent toImmutable() { + return this; + } + + @Override + public ReadOnlyStringMap getContextData() { + return contextData; + } + + @Override + public ThreadContext.ContextStack getContextStack() { + return contextStack; + } + + @Override + public String getLoggerFqcn() { + return null; + } + + @Override + public Level getLevel() { + return OptionConverter.convertLevel(event.getLevel()); + } + + @Override + public String getLoggerName() { + return event.getLoggerName(); + } + + @Override + public Marker getMarker() { + return null; + } + + @Override + public Message getMessage() { + return new SimpleMessage(event.getRenderedMessage()); + } + + @Override + public long getTimeMillis() { + return event.getTimeStamp(); + } + + @Override + public Instant getInstant() { + MutableInstant mutable = new MutableInstant(); + mutable.initFromEpochMilli(event.getTimeStamp(), 0); + return mutable; + } + + @Override + public StackTraceElement getSource() { + LocationInfo info = event.getLocationInformation(); + return new StackTraceElement(info.getClassName(), info.getMethodName(), info.getFileName(), + Integer.parseInt(info.getLineNumber())); + } + + @Override + public String getThreadName() { + return event.getThreadName(); + } + + @Override + public long getThreadId() { + Thread thread = getThread(); + return thread != null ? thread.getId() : 0; + } + + @Override + public int getThreadPriority() { + Thread thread = getThread(); + return thread != null ? thread.getPriority() : 0; + } + + private Thread getThread() { + if (thread == null && event.getThreadName() != null) { + for (Thread thread : Thread.getAllStackTraces().keySet()) { + if (thread.getName().equals(event.getThreadName())) { + this.thread = thread; + return thread; + } + } + } + return thread; + } + + @Override + public Throwable getThrown() { + ThrowableInformation throwableInformation = event.getThrowableInformation(); + return throwableInformation == null ? null : throwableInformation.getThrowable(); + } + + @Override + public ThrowableProxy getThrownProxy() { + return null; + } + + @Override + public boolean isEndOfBatch() { + return false; + } + + @Override + public boolean isIncludeLocation() { + return false; + } + + @Override + public void setEndOfBatch(boolean endOfBatch) { + + } + + @Override + public void setIncludeLocation(boolean locationRequired) { + + } + + @Override + public long getNanoTime() { + return 0; + } + + private static class ContextDataMap extends HashMap implements ReadOnlyStringMap { + + ContextDataMap(Map map) { + if (map != null) { + super.putAll(map); + } + } + + @Override + public Map toMap() { + return this; + } + + @Override + public boolean containsKey(String key) { + return super.containsKey(key); + } + + @Override + public void forEach(BiConsumer action) { + super.forEach((k,v) -> action.accept(k, (V) v)); + } + + @Override + public void forEach(TriConsumer action, S state) { + super.forEach((k,v) -> action.accept(k, (V) v, state)); + } + + @Override + public V getValue(String key) { + return (V) super.get(key); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyAdapter.java new file mode 100644 index 00000000000..cd037548452 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyAdapter.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import org.apache.log4j.rewrite.RewritePolicy; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.LogEvent; + + +/** + * Binds a Log4j 1.x RewritePolicy to Log4j 2. + */ +public class RewritePolicyAdapter implements org.apache.logging.log4j.core.appender.rewrite.RewritePolicy { + + private final RewritePolicy policy; + + /** + * Constructor. + * @param policy The Rewrite policy. + */ + public RewritePolicyAdapter(RewritePolicy policy) { + this.policy = policy; + } + + @Override + public LogEvent rewrite(LogEvent source) { + LoggingEvent event = policy.rewrite(new LogEventAdapter(source)); + return event instanceof LogEventAdapter ? ((LogEventAdapter) event).getEvent() : new LogEventWrapper(event); + } + + public RewritePolicy getPolicy() { + return this.policy; + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyWrapper.java new file mode 100644 index 00000000000..2a944816a48 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyWrapper.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.bridge; + +import org.apache.log4j.rewrite.RewritePolicy; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.LogEvent; + +/** + * Binds a Log4j 2 RewritePolicy to Log4j 1. + */ +public class RewritePolicyWrapper implements RewritePolicy { + + private final org.apache.logging.log4j.core.appender.rewrite.RewritePolicy policy; + + public RewritePolicyWrapper(org.apache.logging.log4j.core.appender.rewrite.RewritePolicy policy) { + this.policy = policy; + } + + @Override + public LoggingEvent rewrite(LoggingEvent source) { + LogEvent event = source instanceof LogEventAdapter ? ((LogEventAdapter) source).getEvent() : + new LogEventWrapper(source); + return new LogEventAdapter(policy.rewrite(event)); + } + + public org.apache.logging.log4j.core.appender.rewrite.RewritePolicy getPolicy() { + return policy; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java new file mode 100644 index 00000000000..0969ef04cf9 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders; + +import static org.apache.log4j.xml.XmlConfiguration.NAME_ATTR; +import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.bridge.FilterWrapper; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.Filter; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; +import org.w3c.dom.Element; + +/** + * Base class for Log4j 1 component builders. + * + * @param The type to build. + */ +public abstract class AbstractBuilder implements Builder { + + private static final Logger LOGGER = StatusLogger.getLogger(); + protected static final String FILE_PARAM = "File"; + protected static final String APPEND_PARAM = "Append"; + protected static final String BUFFERED_IO_PARAM = "BufferedIO"; + protected static final String BUFFER_SIZE_PARAM = "BufferSize"; + protected static final String IMMEDIATE_FLUSH_PARAM = "ImmediateFlush"; + protected static final String MAX_SIZE_PARAM = "MaxFileSize"; + protected static final String MAX_BACKUP_INDEX = "MaxBackupIndex"; + protected static final String RELATIVE = "RELATIVE"; + protected static final String NULL = "NULL"; + + private final String prefix; + private final Properties properties; + + public AbstractBuilder() { + this(null, new Properties()); + } + + public AbstractBuilder(final String prefix, final Properties props) { + this.prefix = prefix != null ? prefix + "." : null; + this.properties = (Properties) props.clone(); + final Map map = new HashMap<>(); + System.getProperties().forEach((k, v) -> map.put(k.toString(), v.toString())); + props.forEach((k, v) -> map.put(k.toString(), v.toString())); + // normalize keys to lower case for case-insensitive access. + props.forEach((k, v) -> map.put(toBeanKey(k.toString()), v.toString())); + props.entrySet().forEach(e -> this.properties.put(toBeanKey(e.getKey().toString()), e.getValue())); + } + + protected static org.apache.logging.log4j.core.Filter buildFilters(final String level, final Filter filter) { + Filter head = null; + if (level != null) { + final org.apache.logging.log4j.core.Filter thresholdFilter = ThresholdFilter.createFilter(OptionConverter.convertLevel(level, Level.TRACE), + org.apache.logging.log4j.core.Filter.Result.NEUTRAL, org.apache.logging.log4j.core.Filter.Result.DENY); + head = new FilterWrapper(thresholdFilter); + } + if (filter != null) { + head = FilterAdapter.addFilter(head, filter); + } + return FilterAdapter.adapt(head); + } + + private String capitalize(final String value) { + if (Strings.isEmpty(value) || Character.isUpperCase(value.charAt(0))) { + return value; + } + final char[] chars = value.toCharArray(); + chars[0] = Character.toUpperCase(chars[0]); + return new String(chars); + } + + public boolean getBooleanProperty(final String key, final boolean defaultValue) { + return Boolean.parseBoolean(getProperty(key, Boolean.toString(defaultValue))); + } + + public boolean getBooleanProperty(final String key) { + return getBooleanProperty(key, false); + } + + protected boolean getBooleanValueAttribute(final Element element) { + return Boolean.parseBoolean(getValueAttribute(element)); + } + + public int getIntegerProperty(final String key, final int defaultValue) { + String value = null; + try { + value = getProperty(key); + if (value != null) { + return Integer.parseInt(value); + } + } catch (final Exception ex) { + LOGGER.warn("Error converting value {} of {} to an integer: {}", value, key, ex.getMessage()); + } + return defaultValue; + } + + protected String getNameAttribute(final Element element) { + return element.getAttribute(NAME_ATTR); + } + + protected String getNameAttributeKey(final Element element) { + return toBeanKey(element.getAttribute(NAME_ATTR)); + } + + public Properties getProperties() { + return properties; + } + + public String getProperty(final String key) { + return getProperty(key, null); + } + + public String getProperty(final String key, final String defaultValue) { + String value = properties.getProperty(prefix + toJavaKey(key)); + value = value != null ? value : properties.getProperty(prefix + toBeanKey(key), defaultValue); + value = value != null ? substVars(value) : defaultValue; + return value != null ? value.trim() : defaultValue; + } + + protected String getValueAttribute(final Element element) { + return getValueAttribute(element, null); + } + + protected String getValueAttribute(final Element element, final String defaultValue) { + final String attribute = element.getAttribute(VALUE_ATTR); + return substVars(attribute != null ? attribute.trim() : defaultValue); + } + + protected String substVars(final String value) { + return OptionConverter.substVars(value, properties); + } + + String toBeanKey(final String value) { + return capitalize(value); + } + + String toJavaKey(final String value) { + return uncapitalize(value); + } + + private String uncapitalize(final String value) { + if (Strings.isEmpty(value) || Character.isLowerCase(value.charAt(0))) { + return value; + } + final char[] chars = value.toCharArray(); + chars[0] = Character.toLowerCase(chars[0]); + return new String(chars); + } + + protected void set(final String name, final Element element, AtomicBoolean ref) { + final String value = getValueAttribute(element); + if (value == null) { + LOGGER.warn("No value for {} parameter, using default {}", name, ref); + } else { + ref.set(Boolean.parseBoolean(value)); + } + } + + protected void set(final String name, final Element element, AtomicInteger ref) { + final String value = getValueAttribute(element); + if (value == null) { + LOGGER.warn("No value for {} parameter, using default {}", name, ref); + } else { + try { + ref.set(Integer.parseInt(value)); + } catch (NumberFormatException e) { + LOGGER.warn("{} parsing {} parameter, using default {}: {}", e.getClass().getName(), name, ref, e.getMessage(), e); + } + } + } + + protected void set(final String name, final Element element, AtomicReference ref) { + final String value = getValueAttribute(element); + if (value == null) { + LOGGER.warn("No value for {} parameter, using default {}", name, ref); + } else { + ref.set(value); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Builder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Builder.java new file mode 100644 index 00000000000..8a9b67036ad --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Builder.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j.builders; + +/** + * A marker interface for Log4j 1.x component builders. + * + * @param The type to build. + */ +public interface Builder { + // empty +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.java new file mode 100644 index 00000000000..8df2cc03980 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders; + +import java.util.Locale; +import java.util.Objects; +import java.util.Properties; +import java.util.function.Function; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.FilterWrapper; +import org.apache.log4j.bridge.LayoutWrapper; +import org.apache.log4j.bridge.RewritePolicyWrapper; +import org.apache.log4j.builders.appender.AppenderBuilder; +import org.apache.log4j.builders.filter.FilterBuilder; +import org.apache.log4j.builders.layout.LayoutBuilder; +import org.apache.log4j.builders.rewrite.RewritePolicyBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.rewrite.RewritePolicy; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Cast; +import org.w3c.dom.Element; + +/** + * + */ +public class BuilderManager { + + /** Plugin namespace. */ + public static final String NAMESPACE = "Log4j Builder"; + public static final Appender INVALID_APPENDER = new AppenderWrapper(null); + public static final Filter INVALID_FILTER = new FilterWrapper(null); + public static final Layout INVALID_LAYOUT = new LayoutWrapper(null); + public static final RewritePolicy INVALID_REWRITE_POLICY = new RewritePolicyWrapper(null); + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final Class[] CONSTRUCTOR_PARAMS = new Class[] { String.class, Properties.class }; + private final Injector injector; + private final PluginNamespace plugins; + + /** + * Constructs a new instance. + */ + @Inject + public BuilderManager(final Injector injector, @Namespace(NAMESPACE) PluginNamespace plugins) { + this.injector = injector; + this.plugins = plugins; + } + + private , U> T createBuilder(final PluginType plugin, final String prefix, final Properties props) { + if (plugin == null) { + return null; + } + try { + final Class clazz = plugin.getPluginClass(); + if (AbstractBuilder.class.isAssignableFrom(clazz)) { + return clazz.getConstructor(CONSTRUCTOR_PARAMS).newInstance(prefix, props); + } + final T builder = injector.getInstance(clazz); + // Reasonable message instead of `ClassCastException` + if (!Builder.class.isAssignableFrom(clazz)) { + LOGGER.warn("Unable to load plugin: builder {} does not implement {}", clazz, Builder.class); + return null; + } + return builder; + } catch (final ReflectiveOperationException ex) { + LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage()); + return null; + } + } + + private PluginType getPlugin(final String className) { + Objects.requireNonNull(plugins, "plugins"); + Objects.requireNonNull(className, "className"); + final String key = className.toLowerCase(Locale.ROOT).trim(); + final PluginType pluginType = plugins.get(key); + if (pluginType == null) { + LOGGER.warn("Unable to load plugin class name {} with key {}", className, key); + } + return Cast.cast(pluginType); + } + + private , U> U newInstance(final PluginType plugin, final Function consumer, + final U invalidValue) { + if (plugin != null) { + final T builder = injector.getInstance(plugin.getPluginClass()); + if (builder != null) { + final U result = consumer.apply(builder); + // returning an empty wrapper is short for "we support this legacy class, but it has validation errors" + return result != null ? result : invalidValue; + } + } + return null; + } + + public

, T> T parse(final String className, final String prefix, final Properties props, + final PropertiesConfiguration config, T invalidValue) { + final P parser = createBuilder(getPlugin(className), prefix, props); + if (parser != null) { + final T value = parser.parse(config); + return value != null ? value : invalidValue; + } + return null; + } + + public Appender parseAppender(final String className, final Element appenderElement, + final XmlConfiguration config) { + return newInstance(this.>getPlugin(className), + b -> b.parseAppender(appenderElement, config), INVALID_APPENDER); + } + + public Appender parseAppender(final String name, final String className, final String prefix, final String layoutPrefix, final String filterPrefix, + final Properties props, final PropertiesConfiguration config) { + final AppenderBuilder builder = createBuilder(getPlugin(className), prefix, props); + if (builder != null) { + final Appender appender = builder.parseAppender(name, prefix, layoutPrefix, filterPrefix, props, config); + return appender != null ? appender : INVALID_APPENDER; + } + return null; + } + + public Filter parseFilter(final String className, final Element filterElement, final XmlConfiguration config) { + return newInstance(this.getPlugin(className), b -> b.parse(filterElement, config), + INVALID_FILTER); + } + + public Layout parseLayout(final String className, final Element layoutElement, final XmlConfiguration config) { + return newInstance(this.getPlugin(className), b -> b.parse(layoutElement, config), + INVALID_LAYOUT); + } + + public RewritePolicy parseRewritePolicy(final String className, final Element rewriteElement, + final XmlConfiguration config) { + return newInstance(this.getPlugin(className), b -> b.parse(rewriteElement, config), + INVALID_REWRITE_POLICY); + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Parser.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Parser.java new file mode 100644 index 00000000000..01812ef8e4d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Parser.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j.builders; + +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.w3c.dom.Element; + +/** + * Parses DOM and properties. + * + * @param The type to build. + */ +public interface Parser extends Builder { + + /** + * Parses a DOM Element. + * + * @param element the DOM Element. + * @param config the XML configuration. + * @return parse result. + */ + T parse(Element element, XmlConfiguration config); + + /** + * Parses a PropertiesConfigurationt. + * + * @param element the PropertiesConfiguration. + * @return parse result. + */ + T parse(PropertiesConfiguration config); + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.java new file mode 100644 index 00000000000..aacbc80d750 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import java.util.Properties; + +import org.apache.log4j.Appender; +import org.apache.log4j.builders.Builder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.w3c.dom.Element; + +/** + * Define an Appender Builder. + * + * @param The type to build. + */ +public interface AppenderBuilder extends Builder { + + Appender parseAppender(Element element, XmlConfiguration configuration); + + Appender parseAppender(String name, String appenderPrefix, String layoutPrefix, String filterPrefix, + Properties props, PropertiesConfiguration configuration); + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.java new file mode 100644 index 00000000000..e69111394aa --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.appender.AsyncAppender.Builder; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.APPENDER_REF_TAG; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build an Async Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.AsyncAppender") +public class AsyncAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String BLOCKING_PARAM = "Blocking"; + private static final String INCLUDE_LOCATION_PARAM = "IncludeLocation"; + + public AsyncAppenderBuilder() { + } + + public AsyncAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference> appenderRefs = new AtomicReference<>(new ArrayList<>()); + final AtomicBoolean blocking = new AtomicBoolean(); + final AtomicBoolean includeLocation = new AtomicBoolean(); + final AtomicReference level = new AtomicReference<>("trace"); + final AtomicInteger bufferSize = new AtomicInteger(1024); + final AtomicReference filter = new AtomicReference<>(); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case APPENDER_REF_TAG: + final Appender appender = config.findAppenderByReference(currentElement); + if (appender != null) { + appenderRefs.get().add(appender.getName()); + } + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: { + switch (getNameAttributeKey(currentElement)) { + case BUFFER_SIZE_PARAM: + set(BUFFER_SIZE_PARAM, currentElement, bufferSize); + break; + case BLOCKING_PARAM: + set(BLOCKING_PARAM, currentElement, blocking); + break; + case INCLUDE_LOCATION_PARAM: + set(INCLUDE_LOCATION_PARAM, currentElement, includeLocation); + break; + case THRESHOLD_PARAM: + set(THRESHOLD_PARAM, currentElement, level); + break; + } + break; + } + } + }); + return createAppender(name, level.get(), appenderRefs.get().toArray(Strings.EMPTY_ARRAY), blocking.get(), + bufferSize.get(), includeLocation.get(), filter.get(), config); + } + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + final String appenderRef = getProperty(APPENDER_REF_TAG); + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + final boolean blocking = getBooleanProperty(BLOCKING_PARAM); + final boolean includeLocation = getBooleanProperty(INCLUDE_LOCATION_PARAM); + final String level = getProperty(THRESHOLD_PARAM); + final int bufferSize = getIntegerProperty(BUFFER_SIZE_PARAM, 1024); + if (appenderRef == null) { + LOGGER.error("No appender references configured for AsyncAppender {}", name); + return null; + } + final Appender appender = configuration.parseAppender(props, appenderRef); + if (appender == null) { + LOGGER.error("Cannot locate Appender {}", appenderRef); + return null; + } + return createAppender(name, level, new String[]{appenderRef}, blocking, bufferSize, includeLocation, filter, + configuration); + } + + private Appender createAppender(final String name, final String level, + final String[] appenderRefs, final boolean blocking, final int bufferSize, final boolean includeLocation, + final Filter filter, final T configuration) { + if (appenderRefs.length == 0) { + LOGGER.error("No appender references configured for AsyncAppender {}", name); + return null; + } + final Level logLevel = OptionConverter.convertLevel(level, + Level.TRACE); + final AppenderRef[] refs = new AppenderRef[appenderRefs.length]; + int index = 0; + for (final String appenderRef : appenderRefs) { + refs[index++] = AppenderRef.createAppenderRef(appenderRef, logLevel, null); + } + Builder builder = AsyncAppender.newBuilder(); + builder.setFilter(FilterAdapter.adapt(filter)); + return AppenderWrapper.adapt(builder.setName(name) + .setAppenderRefs(refs) + .setBlocking(blocking) + .setBufferSize(bufferSize) + .setIncludeLocation(includeLocation) + .setConfiguration(configuration) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java new file mode 100644 index 00000000000..8ef424f077f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build a Console Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.ConsoleAppender") +public class ConsoleAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final String SYSTEM_OUT = "System.out"; + private static final String SYSTEM_ERR = "System.err"; + private static final String TARGET_PARAM = "Target"; + private static final String FOLLOW_PARAM = "Follow"; + + private static final Logger LOGGER = StatusLogger.getLogger(); + + public ConsoleAppenderBuilder() { + } + + public ConsoleAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference target = new AtomicReference<>(SYSTEM_OUT); + final AtomicReference layout = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + final AtomicReference level = new AtomicReference<>(); + final AtomicBoolean follow = new AtomicBoolean(); + final AtomicBoolean immediateFlush = new AtomicBoolean(true); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case LAYOUT_TAG: + layout.set(config.parseLayout(currentElement)); + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: { + switch (getNameAttributeKey(currentElement)) { + case TARGET_PARAM: + final String value = getValueAttribute(currentElement); + if (value == null) { + LOGGER.warn("No value supplied for target parameter. Defaulting to " + SYSTEM_OUT); + } else { + switch (value) { + case SYSTEM_OUT: + target.set(SYSTEM_OUT); + break; + case SYSTEM_ERR: + target.set(SYSTEM_ERR); + break; + default: + LOGGER.warn("Invalid value \"{}\" for target parameter. Using default of {}", value, SYSTEM_OUT); + } + } + break; + case THRESHOLD_PARAM: + set(THRESHOLD_PARAM, currentElement, level); + break; + case FOLLOW_PARAM: + set(FOLLOW_PARAM, currentElement, follow); + break; + case IMMEDIATE_FLUSH_PARAM: + set(IMMEDIATE_FLUSH_PARAM, currentElement, immediateFlush); + break; + } + break; + } + } + }); + return createAppender(name, layout.get(), filter.get(), level.get(), target.get(), immediateFlush.get(), follow.get(), config); + } + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + final Layout layout = configuration.parseLayout(layoutPrefix, name, props); + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + final String level = getProperty(THRESHOLD_PARAM); + final String target = getProperty(TARGET_PARAM); + final boolean follow = getBooleanProperty(FOLLOW_PARAM); + final boolean immediateFlush = getBooleanProperty(IMMEDIATE_FLUSH_PARAM); + return createAppender(name, layout, filter, level, target, immediateFlush, follow, configuration); + } + + private Appender createAppender(final String name, final Layout layout, final Filter filter, + final String level, final String target, final boolean immediateFlush, final boolean follow, final T configuration) { + org.apache.logging.log4j.core.Layout consoleLayout = LayoutAdapter.adapt(layout); + + final org.apache.logging.log4j.core.Filter consoleFilter = buildFilters(level, filter); + final ConsoleAppender.Target consoleTarget = SYSTEM_ERR.equals(target) + ? ConsoleAppender.Target.SYSTEM_ERR : ConsoleAppender.Target.SYSTEM_OUT; + return AppenderWrapper.adapt(ConsoleAppender.newBuilder() + .setName(name) + .setTarget(consoleTarget) + .setFollow(follow) + .setLayout(consoleLayout) + .setFilter(consoleFilter) + .setConfiguration(configuration) + .setImmediateFlush(immediateFlush) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java new file mode 100644 index 00000000000..4c1ea384547 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build a Daily Rolling File Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.DailyRollingFileAppender") +public class DailyRollingFileAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final String DEFAULT_DATE_PATTERN = ".yyyy-MM-dd"; + private static final String DATE_PATTERN_PARAM = "DatePattern"; + + private static final Logger LOGGER = StatusLogger.getLogger(); + + public DailyRollingFileAppenderBuilder() { + } + + public DailyRollingFileAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference layout = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + final AtomicReference fileName = new AtomicReference<>(); + final AtomicReference level = new AtomicReference<>(); + final AtomicBoolean immediateFlush = new AtomicBoolean(true); + final AtomicBoolean append = new AtomicBoolean(true); + final AtomicBoolean bufferedIo = new AtomicBoolean(); + final AtomicInteger bufferSize = new AtomicInteger(8192); + final AtomicReference datePattern = new AtomicReference<>(DEFAULT_DATE_PATTERN); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case LAYOUT_TAG: + layout.set(config.parseLayout(currentElement)); + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: + switch (getNameAttributeKey(currentElement)) { + case FILE_PARAM: + set(FILE_PARAM, currentElement, fileName); + break; + case APPEND_PARAM: + set(APPEND_PARAM, currentElement, append); + break; + case BUFFERED_IO_PARAM: + set(BUFFERED_IO_PARAM, currentElement, bufferedIo); + break; + case BUFFER_SIZE_PARAM: + set(BUFFER_SIZE_PARAM, currentElement, bufferSize); + break; + case THRESHOLD_PARAM: + set(THRESHOLD_PARAM, currentElement, level); + break; + case DATE_PATTERN_PARAM: + set(DATE_PATTERN_PARAM, currentElement, datePattern); + break; + case IMMEDIATE_FLUSH_PARAM: + set(IMMEDIATE_FLUSH_PARAM, currentElement, immediateFlush); + break; + } + break; + } + }); + return createAppender(name, layout.get(), filter.get(), fileName.get(), append.get(), immediateFlush.get(), + level.get(), bufferedIo.get(), bufferSize.get(), datePattern.get(), config, + config.getComponent(Clock.KEY)); + } + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + final Layout layout = configuration.parseLayout(layoutPrefix, name, props); + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + final String fileName = getProperty(FILE_PARAM); + final String level = getProperty(THRESHOLD_PARAM); + final boolean append = getBooleanProperty(APPEND_PARAM, true); + final boolean immediateFlush = getBooleanProperty(IMMEDIATE_FLUSH_PARAM, true); + final boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM, false); + final int bufferSize = getIntegerProperty(BUFFER_SIZE_PARAM, 8192); + final String datePattern = getProperty(DATE_PATTERN_PARAM, DEFAULT_DATE_PATTERN); + return createAppender(name, layout, filter, fileName, append, immediateFlush, level, bufferedIo, bufferSize, + datePattern, configuration, configuration.getComponent(Clock.KEY)); + } + + private Appender createAppender(final String name, final Layout layout, + final Filter filter, final String fileName, final boolean append, boolean immediateFlush, + final String level, final boolean bufferedIo, final int bufferSize, final String datePattern, + final T configuration, final Clock clock) { + + org.apache.logging.log4j.core.Layout fileLayout = LayoutAdapter.adapt(layout); + if (bufferedIo) { + immediateFlush = false; + } + final org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter); + if (fileName == null) { + LOGGER.error("Unable to create DailyRollingFileAppender, no file name provided"); + return null; + } + final String filePattern = fileName + "%d{" + datePattern + "}"; + final TriggeringPolicy timePolicy = TimeBasedTriggeringPolicy.newBuilder().setClock(clock).setModulate(true).build(); + final TriggeringPolicy policy = CompositeTriggeringPolicy.createPolicy(timePolicy); + final RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder() + .setConfig(configuration) + .setMax(Integer.toString(Integer.MAX_VALUE)) + .build(); + return AppenderWrapper.adapt(RollingFileAppender.newBuilder() + .setName(name) + .setConfiguration(configuration) + .setLayout(fileLayout) + .setFilter(fileFilter) + .setFileName(fileName) + .setAppend(append) + .setBufferedIo(bufferedIo) + .setBufferSize(bufferSize) + .setImmediateFlush(immediateFlush) + .setFilePattern(filePattern) + .setPolicy(policy) + .setStrategy(strategy) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java new file mode 100644 index 00000000000..1c661f87647 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build a File Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.FileAppender") +public class FileAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + public FileAppenderBuilder() { + } + + public FileAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference layout = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + final AtomicReference fileName = new AtomicReference<>(); + final AtomicReference level = new AtomicReference<>(); + final AtomicBoolean immediateFlush = new AtomicBoolean(true); + final AtomicBoolean append = new AtomicBoolean(true); + final AtomicBoolean bufferedIo = new AtomicBoolean(); + final AtomicInteger bufferSize = new AtomicInteger(8192); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case LAYOUT_TAG: + layout.set(config.parseLayout(currentElement)); + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: + switch (getNameAttributeKey(currentElement)) { + case FILE_PARAM: + set(FILE_PARAM, currentElement, fileName); + break; + case APPEND_PARAM: + set(APPEND_PARAM, currentElement, append); + break; + case BUFFERED_IO_PARAM: + set(BUFFERED_IO_PARAM, currentElement, bufferedIo); + break; + case BUFFER_SIZE_PARAM: + set(BUFFER_SIZE_PARAM, currentElement, bufferSize); + break; + case THRESHOLD_PARAM: + set(THRESHOLD_PARAM, currentElement, level); + break; + case IMMEDIATE_FLUSH_PARAM: + set(IMMEDIATE_FLUSH_PARAM, currentElement, immediateFlush); + break; + } + break; + } + }); + + return createAppender(name, config, layout.get(), filter.get(), fileName.get(), level.get(), + immediateFlush.get(), append.get(), bufferedIo.get(), bufferSize.get()); + } + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + final Layout layout = configuration.parseLayout(layoutPrefix, name, props); + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + final String level = getProperty(THRESHOLD_PARAM); + final String fileName = getProperty(FILE_PARAM); + final boolean append = getBooleanProperty(APPEND_PARAM, true); + final boolean immediateFlush = getBooleanProperty(IMMEDIATE_FLUSH_PARAM, true); + final boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM, false); + final int bufferSize = Integer.parseInt(getProperty(BUFFER_SIZE_PARAM, "8192")); + return createAppender(name, configuration, layout, filter, fileName, level, immediateFlush, + append, bufferedIo, bufferSize); + } + + private Appender createAppender(final String name, final Log4j1Configuration configuration, final Layout layout, + final Filter filter, final String fileName, final String level, boolean immediateFlush, final boolean append, + final boolean bufferedIo, final int bufferSize) { + org.apache.logging.log4j.core.Layout fileLayout = LayoutAdapter.adapt(layout); + if (bufferedIo) { + immediateFlush = false; + } + final org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter); + if (fileName == null) { + LOGGER.error("Unable to create FileAppender, no file name provided"); + return null; + } + return AppenderWrapper.adapt(FileAppender.newBuilder() + .setName(name) + .setConfiguration(configuration) + .setLayout(fileLayout) + .setFilter(fileFilter) + .setFileName(fileName) + .setImmediateFlush(immediateFlush) + .setAppend(append) + .setBufferedIo(bufferedIo) + .setBufferSize(bufferSize) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.java new file mode 100644 index 00000000000..201159fbdf4 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.appender.NullAppender; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import java.util.Properties; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; + +/** + * Build a Null Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.varia.NullAppender") +public class NullAppenderBuilder implements AppenderBuilder { + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = appenderElement.getAttribute("name"); + return AppenderWrapper.adapt(NullAppender.createAppender(name)); + } + + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + return AppenderWrapper.adapt(NullAppender.createAppender(name)); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java new file mode 100644 index 00000000000..6d8c8968388 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.RewritePolicyAdapter; +import org.apache.log4j.bridge.RewritePolicyWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.builders.BuilderManager; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.rewrite.RewritePolicy; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.APPENDER_REF_TAG; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build an Rewrite Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.rewrite.RewriteAppender") +public class RewriteAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String REWRITE_POLICY_TAG = "rewritePolicy"; + + public RewriteAppenderBuilder() { + } + + public RewriteAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference> appenderRefs = new AtomicReference<>(new ArrayList<>()); + final AtomicReference rewritePolicyHolder = new AtomicReference<>(); + final AtomicReference level = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case APPENDER_REF_TAG: + final Appender appender = config.findAppenderByReference(currentElement); + if (appender != null) { + appenderRefs.get().add(appender.getName()); + } + break; + case REWRITE_POLICY_TAG: + final RewritePolicy policy = config.parseRewritePolicy(currentElement); + if (policy != null) { + rewritePolicyHolder.set(policy); + } + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: + if (getNameAttributeKey(currentElement).equalsIgnoreCase(THRESHOLD_PARAM)) { + set(THRESHOLD_PARAM, currentElement, level); + } + break; + } + }); + return createAppender(name, level.get(), appenderRefs.get().toArray(Strings.EMPTY_ARRAY), rewritePolicyHolder.get(), + filter.get(), config); + } + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + final String appenderRef = getProperty(APPENDER_REF_TAG); + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + final String policyPrefix = appenderPrefix + ".rewritePolicy"; + final String className = getProperty(policyPrefix); + final RewritePolicy policy = configuration.getBuilderManager() + .parse(className, policyPrefix, props, configuration, BuilderManager.INVALID_REWRITE_POLICY); + final String level = getProperty(THRESHOLD_PARAM); + if (appenderRef == null) { + LOGGER.error("No appender references configured for RewriteAppender {}", name); + return null; + } + final Appender appender = configuration.parseAppender(props, appenderRef); + if (appender == null) { + LOGGER.error("Cannot locate Appender {}", appenderRef); + return null; + } + return createAppender(name, level, new String[] {appenderRef}, policy, filter, configuration); + } + + private Appender createAppender(final String name, final String level, + final String[] appenderRefs, final RewritePolicy policy, final Filter filter, final T configuration) { + if (appenderRefs.length == 0) { + LOGGER.error("No appender references configured for RewriteAppender {}", name); + return null; + } + final Level logLevel = OptionConverter.convertLevel(level, + Level.TRACE); + final AppenderRef[] refs = new AppenderRef[appenderRefs.length]; + int index = 0; + for (final String appenderRef : appenderRefs) { + refs[index++] = AppenderRef.createAppenderRef(appenderRef, logLevel, null); + } + final org.apache.logging.log4j.core.Filter rewriteFilter = buildFilters(level, filter); + org.apache.logging.log4j.core.appender.rewrite.RewritePolicy rewritePolicy; + if (policy instanceof RewritePolicyWrapper) { + rewritePolicy = ((RewritePolicyWrapper) policy).getPolicy(); + } else { + rewritePolicy = new RewritePolicyAdapter(policy); + } + return AppenderWrapper.adapt(RewriteAppender.createAppender(name, true, refs, configuration, + rewritePolicy, rewriteFilter)); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java new file mode 100644 index 00000000000..169191aa061 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build a File Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.RollingFileAppender") +public class RollingFileAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final String DEFAULT_MAX_SIZE = "10 MB"; + private static final String DEFAULT_MAX_BACKUPS = "1"; + + private static final Logger LOGGER = StatusLogger.getLogger(); + + public RollingFileAppenderBuilder() { + } + + public RollingFileAppenderBuilder(final String prefix, final Properties properties) { + super(prefix, properties); + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference layout = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + final AtomicReference fileName = new AtomicReference<>(); + final AtomicBoolean immediateFlush = new AtomicBoolean(true); + final AtomicBoolean append = new AtomicBoolean(true); + final AtomicBoolean bufferedIo = new AtomicBoolean(); + final AtomicInteger bufferSize = new AtomicInteger(8192); + final AtomicReference maxSize = new AtomicReference<>(DEFAULT_MAX_SIZE); + final AtomicReference maxBackups = new AtomicReference<>(DEFAULT_MAX_BACKUPS); + final AtomicReference level = new AtomicReference<>(); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case LAYOUT_TAG: + layout.set(config.parseLayout(currentElement)); + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: + switch (getNameAttributeKey(currentElement)) { + case FILE_PARAM: + set(FILE_PARAM, currentElement, fileName); + break; + case APPEND_PARAM: + set(APPEND_PARAM, currentElement, append); + break; + case BUFFERED_IO_PARAM: + set(BUFFERED_IO_PARAM, currentElement, bufferedIo); + break; + case BUFFER_SIZE_PARAM: + set(BUFFER_SIZE_PARAM, currentElement, bufferSize); + break; + case MAX_BACKUP_INDEX: + set(MAX_BACKUP_INDEX, currentElement, maxBackups); + break; + case MAX_SIZE_PARAM: + set(MAX_SIZE_PARAM, currentElement, maxSize); + break; + case THRESHOLD_PARAM: + set(THRESHOLD_PARAM, currentElement, level); + break; + case IMMEDIATE_FLUSH_PARAM: + set(IMMEDIATE_FLUSH_PARAM, currentElement, immediateFlush); + break; + } + break; + } + }); + return createAppender(name, config, layout.get(), filter.get(), append.get(), bufferedIo.get(), + bufferSize.get(), immediateFlush.get(), fileName.get(), level.get(), maxSize.get(), maxBackups.get(), + config.getComponent(Clock.KEY)); + } + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + final Layout layout = configuration.parseLayout(layoutPrefix, name, props); + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + final String fileName = getProperty(FILE_PARAM); + final String level = getProperty(THRESHOLD_PARAM); + final boolean append = getBooleanProperty(APPEND_PARAM, true); + final boolean immediateFlush = getBooleanProperty(IMMEDIATE_FLUSH_PARAM, true); + final boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM, false); + final int bufferSize = getIntegerProperty(BUFFER_SIZE_PARAM, 8192); + final String maxSize = getProperty(MAX_SIZE_PARAM, DEFAULT_MAX_SIZE); + final String maxBackups = getProperty(MAX_BACKUP_INDEX, DEFAULT_MAX_BACKUPS); + return createAppender(name, configuration, layout, filter, append, bufferedIo, bufferSize, immediateFlush, + fileName, level, maxSize, maxBackups, configuration.getComponent(Clock.KEY)); + } + + private Appender createAppender(final String name, final Log4j1Configuration config, final Layout layout, + final Filter filter, final boolean append, final boolean bufferedIo, final int bufferSize, + boolean immediateFlush, final String fileName, final String level, final String maxSize, + final String maxBackups, final Clock clock) { + org.apache.logging.log4j.core.Layout fileLayout = LayoutAdapter.adapt(layout); + if (!bufferedIo) { + immediateFlush = false; + } + final org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter); + if (fileName == null) { + LOGGER.error("Unable to create RollingFileAppender, no file name provided"); + return null; + } + final String filePattern = fileName + ".%i"; + final TriggeringPolicy timePolicy = TimeBasedTriggeringPolicy.newBuilder().setClock(clock).setModulate(true).build(); + final SizeBasedTriggeringPolicy sizePolicy = SizeBasedTriggeringPolicy.createPolicy(maxSize); + final CompositeTriggeringPolicy policy = CompositeTriggeringPolicy.createPolicy(sizePolicy); + final RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder() + .setConfig(config) + .setMax(maxBackups) + .build(); + return AppenderWrapper.adapt(RollingFileAppender.newBuilder() + .setName(name) + .setConfiguration(config) + .setLayout(fileLayout) + .setFilter(fileFilter) + .setAppend(append) + .setBufferedIo(bufferedIo) + .setBufferSize(bufferSize) + .setImmediateFlush(immediateFlush) + .setFileName(fileName) + .setFilePattern(filePattern) + .setPolicy(policy) + .setStrategy(strategy) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.java new file mode 100644 index 00000000000..4bf4f611856 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SocketAppenderBuilder.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.SocketAppender; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build a Console Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.net.SocketAppender") +public class SocketAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final String HOST_PARAM = "RemoteHost"; + private static final String PORT_PARAM = "Port"; + private static final String RECONNECTION_DELAY_PARAM = "ReconnectionDelay"; + private static final int DEFAULT_PORT = 4560; + + /** + * The default reconnection delay (30000 milliseconds or 30 seconds). + */ + private static final int DEFAULT_RECONNECTION_DELAY = 30_000; + + public static final Logger LOGGER = StatusLogger.getLogger(); + + public SocketAppenderBuilder() { + } + + public SocketAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + private Appender createAppender(final String name, final String host, final int port, final Layout layout, + final Filter filter, final String level, final boolean immediateFlush, final int reconnectDelayMillis, final T configuration) { + org.apache.logging.log4j.core.Layout actualLayout = LayoutAdapter.adapt(layout); + final org.apache.logging.log4j.core.Filter actualFilter = buildFilters(level, filter); + // @formatter:off + return AppenderWrapper.adapt(SocketAppender.newBuilder() + .setHost(host) + .setPort(port) + .setReconnectDelayMillis(reconnectDelayMillis) + .setName(name) + .setLayout(actualLayout) + .setFilter(actualFilter) + .setConfiguration(configuration) + .setImmediateFlush(immediateFlush) + .build()); + // @formatter:on + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference host = new AtomicReference<>("localhost"); + final AtomicInteger port = new AtomicInteger(DEFAULT_PORT); + final AtomicInteger reconnectDelay = new AtomicInteger(DEFAULT_RECONNECTION_DELAY); + final AtomicReference layout = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + final AtomicReference level = new AtomicReference<>(); + final AtomicBoolean immediateFlush = new AtomicBoolean(true); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case LAYOUT_TAG: + layout.set(config.parseLayout(currentElement)); + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: + switch (getNameAttributeKey(currentElement)) { + case HOST_PARAM: + set(HOST_PARAM, currentElement, host); + break; + case PORT_PARAM: + set(PORT_PARAM, currentElement, port); + break; + case RECONNECTION_DELAY_PARAM: + set(RECONNECTION_DELAY_PARAM, currentElement, reconnectDelay); + break; + case THRESHOLD_PARAM: + set(THRESHOLD_PARAM, currentElement, level); + break; + case IMMEDIATE_FLUSH_PARAM: + set(IMMEDIATE_FLUSH_PARAM, currentElement, immediateFlush); + break; + } + break; + } + }); + return createAppender(name, host.get(), port.get(), layout.get(), filter.get(), level.get(), immediateFlush.get(), reconnectDelay.get(), config); + } + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, final String filterPrefix, final Properties props, + final PropertiesConfiguration configuration) { + // @formatter:off + return createAppender(name, + getProperty(HOST_PARAM), + getIntegerProperty(PORT_PARAM, DEFAULT_PORT), + configuration.parseLayout(layoutPrefix, name, props), + configuration.parseAppenderFilters(props, filterPrefix, name), + getProperty(THRESHOLD_PARAM), + getBooleanProperty(IMMEDIATE_FLUSH_PARAM), + getIntegerProperty(RECONNECTION_DELAY_PARAM, DEFAULT_RECONNECTION_DELAY), + configuration); + // @formatter:on + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java new file mode 100644 index 00000000000..2e215e51055 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.appender; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.layout.Log4j1SyslogLayout; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.SyslogAppender; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; +import org.w3c.dom.Element; + +import java.io.Serializable; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Build a File Appender + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.net.SyslogAppender") +public class SyslogAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + private static final String DEFAULT_HOST = "localhost"; + private static int DEFAULT_PORT = 514; + private static final String DEFAULT_FACILITY = "LOCAL0"; + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String FACILITY_PARAM = "Facility"; + private static final String FACILITY_PRINTING_PARAM = "FacilityPrinting"; + private static final String HEADER_PARAM = "Header"; + private static final String PROTOCOL_PARAM = "Protocol"; + private static final String SYSLOG_HOST_PARAM = "SyslogHost"; + + public SyslogAppenderBuilder() { + } + + public SyslogAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) { + final String name = getNameAttribute(appenderElement); + final AtomicReference layout = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + final AtomicReference facility = new AtomicReference<>(); + final AtomicReference level = new AtomicReference<>(); + final AtomicReference host = new AtomicReference<>(); + final AtomicReference protocol = new AtomicReference<>(Protocol.TCP); + final AtomicBoolean header = new AtomicBoolean(false); + final AtomicBoolean facilityPrinting = new AtomicBoolean(false); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case LAYOUT_TAG: + layout.set(config.parseLayout(currentElement)); + break; + case FILTER_TAG: + config.addFilter(filter, currentElement); + break; + case PARAM_TAG: + switch (getNameAttributeKey(currentElement)) { + case FACILITY_PARAM: + set(FACILITY_PARAM, currentElement, facility); + break; + case FACILITY_PRINTING_PARAM: + set(FACILITY_PRINTING_PARAM, currentElement, facilityPrinting); + break; + case HEADER_PARAM: + set(HEADER_PARAM, currentElement, header); + break; + case PROTOCOL_PARAM: + protocol.set(Protocol.valueOf(getValueAttribute(currentElement, Protocol.TCP.name()))); + break; + case SYSLOG_HOST_PARAM: + set(SYSLOG_HOST_PARAM, currentElement, host); + break; + case THRESHOLD_PARAM: + set(THRESHOLD_PARAM, currentElement, level); + break; + } + break; + } + }); + + return createAppender(name, config, layout.get(), facility.get(), filter.get(), host.get(), level.get(), + protocol.get(), header.get(), facilityPrinting.get()); + } + + + @Override + public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix, + final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) { + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + final Layout layout = configuration.parseLayout(layoutPrefix, name, props); + final String level = getProperty(THRESHOLD_PARAM); + final String facility = getProperty(FACILITY_PARAM, DEFAULT_FACILITY); + final boolean facilityPrinting = getBooleanProperty(FACILITY_PRINTING_PARAM, false); + final boolean header = getBooleanProperty(HEADER_PARAM, false); + final String protocol = getProperty(PROTOCOL_PARAM, Protocol.TCP.name()); + final String syslogHost = getProperty(SYSLOG_HOST_PARAM, DEFAULT_HOST + ":" + DEFAULT_PORT); + + return createAppender(name, configuration, layout, facility, filter, syslogHost, level, + Protocol.valueOf(protocol), header, facilityPrinting); + } + + private Appender createAppender(final String name, final Log4j1Configuration configuration, final Layout layout, + final String facility, final Filter filter, final String syslogHost, final String level, final Protocol protocol, + final boolean header, final boolean facilityPrinting) { + final AtomicReference host = new AtomicReference<>(); + final AtomicInteger port = new AtomicInteger(); + resolveSyslogHost(syslogHost, host, port); + final org.apache.logging.log4j.core.Layout messageLayout = LayoutAdapter.adapt(layout); + final Log4j1SyslogLayout appenderLayout = Log4j1SyslogLayout.newBuilder() + .setHeader(header) + .setFacility(Facility.toFacility(facility)) + .setFacilityPrinting(facilityPrinting) + .setMessageLayout(messageLayout) + .build(); + + final org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter); + return AppenderWrapper.adapt(SyslogAppender.newSyslogAppenderBuilder() + .setName(name) + .setConfiguration(configuration) + .setLayout(appenderLayout) + .setFilter(fileFilter) + .setPort(port.get()) + .setProtocol(protocol) + .setHost(host.get()) + .build()); + } + + private void resolveSyslogHost(final String syslogHost, final AtomicReference host, final AtomicInteger port) { + // + // If not an unbracketed IPv6 address then + // parse as a URL + // + final String[] parts = syslogHost != null ? syslogHost.split(":") : Strings.EMPTY_ARRAY; + if (parts.length == 1) { + host.set(parts[0]); + port.set(DEFAULT_PORT); + } else if (parts.length == 2) { + host.set(parts[0]); + port.set(Integer.parseInt(parts[1].trim())); + } else { + LOGGER.warn("Invalid {} setting: {}. Using default.", SYSLOG_HOST_PARAM, syslogHost); + host.set(DEFAULT_HOST); + port.set(DEFAULT_PORT); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java new file mode 100644 index 00000000000..0691665a6ea --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.filter; + +import org.apache.log4j.bridge.FilterWrapper; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.filter.DenyAllFilter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; + +/** + * Build a Pattern Layout + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.varia.DenyAllFilter") +public class DenyAllFilterBuilder implements FilterBuilder { + + @Override + public Filter parse(Element filterElement, XmlConfiguration config) { + return new FilterWrapper(DenyAllFilter.newBuilder().build()); + } + + @Override + public Filter parse(PropertiesConfiguration config) { + return new FilterWrapper(DenyAllFilter.newBuilder().build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/FilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/FilterBuilder.java new file mode 100644 index 00000000000..8153bdd7e6b --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/FilterBuilder.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.filter; + +import org.apache.log4j.builders.Parser; +import org.apache.log4j.spi.Filter; + +/** + * Define a Filter Builder. + */ +public interface FilterBuilder extends Parser { + // empty +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java new file mode 100644 index 00000000000..81554124d29 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.filter; + +import org.apache.log4j.bridge.FilterWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.filter.LevelMatchFilter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +/** + * Build a Level match filter. + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.varia.LevelMatchFilter") +public class LevelMatchFilterBuilder extends AbstractBuilder implements FilterBuilder { + + private static final String LEVEL = "LevelToMatch"; + private static final String ACCEPT_ON_MATCH = "AcceptOnMatch"; + + public LevelMatchFilterBuilder() { + } + + public LevelMatchFilterBuilder(String prefix, Properties props) { + super(prefix, props); + } + + @Override + public Filter parse(Element filterElement, XmlConfiguration config) { + final AtomicReference level = new AtomicReference<>(); + final AtomicBoolean acceptOnMatch = new AtomicBoolean(); + forEachElement(filterElement.getElementsByTagName("param"), currentElement -> { + if (currentElement.getTagName().equals("param")) { + switch (getNameAttributeKey(currentElement)) { + case LEVEL: + level.set(getValueAttribute(currentElement)); + break; + case ACCEPT_ON_MATCH: + acceptOnMatch.set(getBooleanValueAttribute(currentElement)); + break; + } + } + }); + return createFilter(level.get(), acceptOnMatch.get()); + } + + @Override + public Filter parse(PropertiesConfiguration config) { + String level = getProperty(LEVEL); + boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH); + return createFilter(level, acceptOnMatch); + } + + private Filter createFilter(String level, boolean acceptOnMatch) { + Level lvl = Level.ERROR; + if (level != null) { + lvl = OptionConverter.toLevel(level, org.apache.log4j.Level.ERROR).getVersion2Level(); + } + org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch + ? org.apache.logging.log4j.core.Filter.Result.ACCEPT + : org.apache.logging.log4j.core.Filter.Result.DENY; + return FilterWrapper.adapt(LevelMatchFilter.newBuilder() + .setLevel(lvl) + .setOnMatch(onMatch) + .setOnMismatch(org.apache.logging.log4j.core.Filter.Result.NEUTRAL) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java new file mode 100644 index 00000000000..7219ed2538a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.filter; + +import org.apache.log4j.bridge.FilterWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.filter.LevelRangeFilter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +/** + * Build a Level match filter. + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.varia.LevelRangeFilter") +public class LevelRangeFilterBuilder extends AbstractBuilder implements FilterBuilder { + + private static final String LEVEL_MAX = "LevelMax"; + private static final String LEVEL_MIN = "LevelMin"; + private static final String ACCEPT_ON_MATCH = "AcceptOnMatch"; + + public LevelRangeFilterBuilder() { + } + + public LevelRangeFilterBuilder(String prefix, Properties props) { + super(prefix, props); + } + + @Override + public Filter parse(Element filterElement, XmlConfiguration config) { + final AtomicReference levelMax = new AtomicReference<>(); + final AtomicReference levelMin = new AtomicReference<>(); + final AtomicBoolean acceptOnMatch = new AtomicBoolean(); + forEachElement(filterElement.getElementsByTagName("param"), currentElement -> { + if (currentElement.getTagName().equals("param")) { + switch (getNameAttributeKey(currentElement)) { + case LEVEL_MAX: + levelMax.set(getValueAttribute(currentElement)); + break; + case LEVEL_MIN: + levelMax.set(getValueAttribute(currentElement)); + break; + case ACCEPT_ON_MATCH: + acceptOnMatch.set(getBooleanValueAttribute(currentElement)); + break; + } + } + }); + return createFilter(levelMax.get(), levelMin.get(), acceptOnMatch.get()); + } + + @Override + public Filter parse(PropertiesConfiguration config) { + String levelMax = getProperty(LEVEL_MAX); + String levelMin = getProperty(LEVEL_MIN); + boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH); + return createFilter(levelMax, levelMin, acceptOnMatch); + } + + private Filter createFilter(String levelMax, String levelMin, boolean acceptOnMatch) { + Level max = Level.FATAL; + Level min = Level.TRACE; + if (levelMax != null) { + max = OptionConverter.toLevel(levelMax, org.apache.log4j.Level.FATAL).getVersion2Level(); + } + if (levelMin != null) { + min = OptionConverter.toLevel(levelMin, org.apache.log4j.Level.DEBUG).getVersion2Level(); + } + org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch + ? org.apache.logging.log4j.core.Filter.Result.ACCEPT + : org.apache.logging.log4j.core.Filter.Result.NEUTRAL; + + return FilterWrapper.adapt(LevelRangeFilter.createFilter(min, max, onMatch, + org.apache.logging.log4j.core.Filter.Result.DENY)); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java new file mode 100644 index 00000000000..311a1006133 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.filter; + +import org.apache.log4j.bridge.FilterWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.filter.StringMatchFilter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +/** + * Build a String match filter. + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.varia.StringMatchFilter") +public class StringMatchFilterBuilder extends AbstractBuilder implements FilterBuilder { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String STRING_TO_MATCH = "StringToMatch"; + private static final String ACCEPT_ON_MATCH = "AcceptOnMatch"; + + public StringMatchFilterBuilder() { + super(); + } + + public StringMatchFilterBuilder(String prefix, Properties props) { + super(prefix, props); + } + + @Override + public Filter parse(Element filterElement, XmlConfiguration config) { + final AtomicBoolean acceptOnMatch = new AtomicBoolean(); + final AtomicReference text = new AtomicReference<>(); + forEachElement(filterElement.getElementsByTagName("param"), currentElement -> { + if (currentElement.getTagName().equals("param")) { + switch (getNameAttributeKey(currentElement)) { + case STRING_TO_MATCH: + text.set(getValueAttribute(currentElement)); + break; + case ACCEPT_ON_MATCH: + acceptOnMatch.set(getBooleanValueAttribute(currentElement)); + break; + + } + } + }); + return createFilter(text.get(), acceptOnMatch.get()); + } + + @Override + public Filter parse(PropertiesConfiguration config) { + String text = getProperty(STRING_TO_MATCH); + boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH); + return createFilter(text, acceptOnMatch); + } + + private Filter createFilter(String text, boolean acceptOnMatch) { + if (text == null) { + LOGGER.error("No text provided for StringMatchFilter"); + return null; + } + org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch + ? org.apache.logging.log4j.core.Filter.Result.ACCEPT + : org.apache.logging.log4j.core.Filter.Result.DENY; + return FilterWrapper.adapt(StringMatchFilter.newBuilder() + .setMatchString(text) + .setOnMatch(onMatch) + .setOnMismatch(org.apache.logging.log4j.core.Filter.Result.NEUTRAL) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java new file mode 100644 index 00000000000..1c5f1f336a2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.layout; + +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.LayoutWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.layout.HtmlLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +/** + * Build a Pattern Layout + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.HTMLLayout") +public class HtmlLayoutBuilder extends AbstractBuilder implements LayoutBuilder { + + private static final String DEFAULT_TITLE = "Log4J Log Messages"; + private static final String TITLE_PARAM = "Title"; + private static final String LOCATION_INFO_PARAM = "LocationInfo"; + + public HtmlLayoutBuilder() { + } + + public HtmlLayoutBuilder(String prefix, Properties props) { + super(prefix, props); + } + + + @Override + public Layout parse(Element layoutElement, XmlConfiguration config) { + final AtomicReference title = new AtomicReference<>("Log4J Log Messages"); + final AtomicBoolean locationInfo = new AtomicBoolean(); + forEachElement(layoutElement.getElementsByTagName("param"), currentElement -> { + if (currentElement.getTagName().equals(PARAM_TAG)) { + if (TITLE_PARAM.equalsIgnoreCase(currentElement.getAttribute("name"))) { + title.set(currentElement.getAttribute("value")); + } else if (LOCATION_INFO_PARAM.equalsIgnoreCase(currentElement.getAttribute("name"))) { + locationInfo.set(getBooleanValueAttribute(currentElement)); + } + } + }); + return createLayout(title.get(), locationInfo.get()); + } + + @Override + public Layout parse(PropertiesConfiguration config) { + String title = getProperty(TITLE_PARAM, DEFAULT_TITLE); + boolean locationInfo = getBooleanProperty(LOCATION_INFO_PARAM); + return createLayout(title, locationInfo); + } + + private Layout createLayout(String title, boolean locationInfo) { + return LayoutWrapper.adapt(HtmlLayout.newBuilder() + .setTitle(title) + .setLocationInfo(locationInfo) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/LayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/LayoutBuilder.java new file mode 100644 index 00000000000..72c17637bcd --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/LayoutBuilder.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.layout; + +import org.apache.log4j.Layout; +import org.apache.log4j.builders.Parser; + +/** + * Define a Layout Builder. + */ +public interface LayoutBuilder extends Parser { + // empty +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/PatternLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/PatternLayoutBuilder.java new file mode 100644 index 00000000000..e55b5b00056 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/PatternLayoutBuilder.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.layout; + +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.LayoutWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.status.StatusLogger; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.Properties; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; + +/** + * Build a Pattern Layout + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.PatternLayout") +@PluginAliases("org.apache.log4j.EnhancedPatternLayout") +public class PatternLayoutBuilder extends AbstractBuilder implements LayoutBuilder { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String PATTERN = "ConversionPattern"; + + public PatternLayoutBuilder() { + } + + public PatternLayoutBuilder(String prefix, Properties props) { + super(prefix, props); + } + + @Override + public Layout parse(final Element layoutElement, final XmlConfiguration config) { + NodeList params = layoutElement.getElementsByTagName("param"); + final int length = params.getLength(); + String pattern = null; + for (int index = 0; index < length; ++ index) { + Node currentNode = params.item(index); + if (currentNode.getNodeType() == Node.ELEMENT_NODE) { + Element currentElement = (Element) currentNode; + if (currentElement.getTagName().equals(PARAM_TAG)) { + if (PATTERN.equalsIgnoreCase(currentElement.getAttribute("name"))) { + pattern = currentElement.getAttribute("value"); + break; + } + } + } + } + return createLayout(pattern, config); + } + + @Override + public Layout parse(final PropertiesConfiguration config) { + String pattern = getProperty(PATTERN); + return createLayout(pattern, config); + } + + Layout createLayout(String pattern, final Log4j1Configuration config) { + if (pattern == null) { + LOGGER.info("No pattern provided for pattern layout, using default pattern"); + pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; + } + return LayoutWrapper.adapt(PatternLayout.newBuilder() + .setPattern(pattern + // Log4j 2 and Log4j 1 level names differ for custom levels + .replaceAll("%([-\\.\\d]*)p(?!\\w)", "%$1v1Level") + // Log4j 2's %x (NDC) is not compatible with Log4j 1's + // %x + // Log4j 1: "foo bar baz" + // Log4j 2: "[foo, bar, baz]" + // Use %ndc to get the Log4j 1 format + .replaceAll("%([-\\.\\d]*)x(?!\\w)", "%$1ndc") + + // Log4j 2's %X (MDC) is not compatible with Log4j 1's + // %X + // Log4j 1: "{{foo,bar}{hoo,boo}}" + // Log4j 2: "{foo=bar,hoo=boo}" + // Use %properties to get the Log4j 1 format + .replaceAll("%([-\\.\\d]*)X(?!\\w)", "%$1properties")) + .setConfiguration(config) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.java new file mode 100644 index 00000000000..85bef1e1b0e --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.layout; + +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.LayoutWrapper; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; + +/** + * Build a Pattern Layout + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.SimpleLayout") +public class SimpleLayoutBuilder implements LayoutBuilder { + + @Override + public Layout parse(Element layoutElement, XmlConfiguration config) { + return new LayoutWrapper(PatternLayout.newBuilder() + .setPattern("%v1Level - %m%n") + .setConfiguration(config) + .build()); + } + + @Override + public Layout parse(PropertiesConfiguration config) { + return new LayoutWrapper(PatternLayout.newBuilder() + .setPattern("%v1Level - %m%n") + .setConfiguration(config) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.java new file mode 100644 index 00000000000..de7f384949f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.layout; + +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.LayoutWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +/** + * Build a Pattern Layout + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.TTCCLayout") +public class TTCCLayoutBuilder extends AbstractBuilder implements LayoutBuilder { + + private static final String THREAD_PRINTING_PARAM = "ThreadPrinting"; + private static final String CATEGORY_PREFIXING_PARAM = "CategoryPrefixing"; + private static final String CONTEXT_PRINTING_PARAM = "ContextPrinting"; + private static final String DATE_FORMAT_PARAM = "DateFormat"; + private static final String TIMEZONE_FORMAT = "TimeZone"; + + public TTCCLayoutBuilder() { + } + + public TTCCLayoutBuilder(String prefix, Properties props) { + super(prefix, props); + } + + @Override + public Layout parse(Element layoutElement, XmlConfiguration config) { + final AtomicBoolean threadPrinting = new AtomicBoolean(Boolean.TRUE); + final AtomicBoolean categoryPrefixing = new AtomicBoolean(Boolean.TRUE); + final AtomicBoolean contextPrinting = new AtomicBoolean(Boolean.TRUE); + final AtomicReference dateFormat = new AtomicReference<>(RELATIVE); + final AtomicReference timezone = new AtomicReference<>(); + forEachElement(layoutElement.getElementsByTagName("param"), currentElement -> { + if (currentElement.getTagName().equals(PARAM_TAG)) { + switch (getNameAttributeKey(currentElement)) { + case THREAD_PRINTING_PARAM: + threadPrinting.set(getBooleanValueAttribute(currentElement)); + break; + case CATEGORY_PREFIXING_PARAM: + categoryPrefixing.set(getBooleanValueAttribute(currentElement)); + break; + case CONTEXT_PRINTING_PARAM: + contextPrinting.set(getBooleanValueAttribute(currentElement)); + break; + case DATE_FORMAT_PARAM: + dateFormat.set(getValueAttribute(currentElement)); + break; + case TIMEZONE_FORMAT: + timezone.set(getValueAttribute(currentElement)); + break; + } + } + }); + return createLayout(threadPrinting.get(), categoryPrefixing.get(), contextPrinting.get(), + dateFormat.get(), timezone.get(), config); + } + + @Override + public Layout parse(PropertiesConfiguration config) { + boolean threadPrinting = getBooleanProperty(THREAD_PRINTING_PARAM, true); + boolean categoryPrefixing = getBooleanProperty(CATEGORY_PREFIXING_PARAM, true); + boolean contextPrinting = getBooleanProperty(CONTEXT_PRINTING_PARAM, true); + String dateFormat = getProperty(DATE_FORMAT_PARAM, RELATIVE); + String timezone = getProperty(TIMEZONE_FORMAT); + + return createLayout(threadPrinting, categoryPrefixing, contextPrinting, + dateFormat, timezone, config); + } + + private Layout createLayout(boolean threadPrinting, boolean categoryPrefixing, boolean contextPrinting, + String dateFormat, String timezone, Log4j1Configuration config) { + StringBuilder sb = new StringBuilder(); + if (dateFormat != null) { + if (RELATIVE.equalsIgnoreCase(dateFormat)) { + sb.append("%r "); + } else if (!NULL.equalsIgnoreCase(dateFormat)){ + sb.append("%d{").append(dateFormat).append("}"); + if (timezone != null) { + sb.append("{").append(timezone).append("}"); + } + sb.append(" "); + } + } + if (threadPrinting) { + sb.append("[%t] "); + } + sb.append("%p "); + if (categoryPrefixing) { + sb.append("%c "); + } + if (contextPrinting) { + sb.append("%notEmpty{%ndc }"); + } + sb.append("- %m%n"); + return LayoutWrapper.adapt(PatternLayout.newBuilder() + .setPattern(sb.toString()) + .setConfiguration(config) + .build()); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java new file mode 100644 index 00000000000..c0137f4296f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.layout; + +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.LayoutWrapper; +import org.apache.log4j.builders.AbstractBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.layout.Log4j1XmlLayout; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG; +import static org.apache.log4j.xml.XmlConfiguration.forEachElement; + +/** + * Build an XML Layout + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.log4j.xml.XMLLayout") +public class XmlLayoutBuilder extends AbstractBuilder implements LayoutBuilder { + + private static final String LOCATION_INFO = "LocationInfo"; + private static final String PROPERTIES = "Properties"; + + public XmlLayoutBuilder() { + } + + public XmlLayoutBuilder(String prefix, Properties props) { + super(prefix, props); + } + + + @Override + public Layout parse(Element layoutElement, XmlConfiguration config) { + final AtomicBoolean properties = new AtomicBoolean(); + final AtomicBoolean locationInfo = new AtomicBoolean(); + forEachElement(layoutElement.getElementsByTagName(PARAM_TAG), currentElement -> { + if (PROPERTIES.equalsIgnoreCase(currentElement.getAttribute("name"))) { + properties.set(getBooleanValueAttribute(currentElement)); + } else if (LOCATION_INFO.equalsIgnoreCase(currentElement.getAttribute("name"))) { + locationInfo.set(getBooleanValueAttribute(currentElement)); + } + }); + return createLayout(properties.get(), locationInfo.get()); + } + + @Override + public Layout parse(PropertiesConfiguration config) { + boolean properties = getBooleanProperty(PROPERTIES); + boolean locationInfo = getBooleanProperty(LOCATION_INFO); + return createLayout(properties, locationInfo); + } + + private Layout createLayout(boolean properties, boolean locationInfo) { + return LayoutWrapper.adapt(Log4j1XmlLayout.createLayout(locationInfo, properties)); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rewrite/RewritePolicyBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rewrite/RewritePolicyBuilder.java new file mode 100644 index 00000000000..e83d0338727 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rewrite/RewritePolicyBuilder.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.rewrite; + +import org.apache.log4j.builders.Parser; +import org.apache.log4j.rewrite.RewritePolicy; + +/** + * Define a RewritePolicy Builder. + */ +public interface RewritePolicyBuilder extends Parser { + // empty +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/component/helpers/Constants.java b/log4j-1.2-api/src/main/java/org/apache/log4j/component/helpers/Constants.java new file mode 100644 index 00000000000..9a41b2cd21a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/component/helpers/Constants.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j.component.helpers; + + +/** + * Constants used internally throughout log4j. + * + */ +public interface Constants { + + /** + * log4j package name string literal. + */ + String LOG4J_PACKAGE_NAME = "org.apache.log4j"; + + /** + * The name of the default repository is "default" (without the quotes). + */ + String DEFAULT_REPOSITORY_NAME = "default"; + + /** + * application string literal. + */ + String APPLICATION_KEY = "application"; + /** + * hostname string literal. + */ + String HOSTNAME_KEY = "hostname"; + /** + * receiver string literal. + */ + String RECEIVER_NAME_KEY = "receiver"; + /** + * log4jid string literal. + */ + String LOG4J_ID_KEY = "log4jid"; + /** + * time stamp pattern string literal. + */ + String TIMESTAMP_RULE_FORMAT = "yyyy/MM/dd HH:mm:ss"; + + /** + * The default property file name for automatic configuration. + */ + String DEFAULT_CONFIGURATION_FILE = "log4j.properties"; + /** + * The default XML configuration file name for automatic configuration. + */ + String DEFAULT_XML_CONFIGURATION_FILE = "log4j.xml"; + /** + * log4j.configuration string literal. + */ + String DEFAULT_CONFIGURATION_KEY = "log4j.configuration"; + /** + * log4j.configuratorClass string literal. + */ + String CONFIGURATOR_CLASS_KEY = "log4j.configuratorClass"; + + /** + * JNDI context name string literal. + */ + String JNDI_CONTEXT_NAME = "java:comp/env/log4j/context-name"; + + /** + * TEMP_LIST_APPENDER string literal. + */ + String TEMP_LIST_APPENDER_NAME = "TEMP_LIST_APPENDER"; + /** + * TEMP_CONSOLE_APPENDER string literal. + */ + String TEMP_CONSOLE_APPENDER_NAME = "TEMP_CONSOLE_APPENDER"; + /** + * Codes URL string literal. + */ + String CODES_HREF = + "http://logging.apache.org/log4j/docs/codes.html"; + + + /** + * ABSOLUTE string literal. + */ + String ABSOLUTE_FORMAT = "ABSOLUTE"; + /** + * SimpleTimePattern for ABSOLUTE. + */ + String ABSOLUTE_TIME_PATTERN = "HH:mm:ss,SSS"; + + /** + * SimpleTimePattern for ABSOLUTE. + */ + String SIMPLE_TIME_PATTERN = "HH:mm:ss"; + + /** + * DATE string literal. + */ + String DATE_AND_TIME_FORMAT = "DATE"; + /** + * SimpleTimePattern for DATE. + */ + String DATE_AND_TIME_PATTERN = "dd MMM yyyy HH:mm:ss,SSS"; + + /** + * ISO8601 string literal. + */ + String ISO8601_FORMAT = "ISO8601"; + /** + * SimpleTimePattern for ISO8601. + */ + String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS"; +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java new file mode 100644 index 00000000000..256f8170c47 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.Level; +import org.apache.log4j.builders.BuilderManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.AbstractConfiguration; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Reconfigurable; + +/** + * Base Configuration for Log4j 1. + */ +public class Log4j1Configuration extends AbstractConfiguration implements Reconfigurable { + + public static final String MONITOR_INTERVAL = "log4j1.monitorInterval"; + public static final String APPENDER_REF_TAG = "appender-ref"; + public static final String THRESHOLD_PARAM = "Threshold"; + + public static final String INHERITED = "inherited"; + + public static final String NULL = "null"; + + /** + * The effective level used, when the configuration uses a non-existent custom + * level. + */ + public static final Level DEFAULT_LEVEL = Level.DEBUG; + + protected final BuilderManager manager; + + public Log4j1Configuration(final LoggerContext loggerContext, final ConfigurationSource configurationSource, + int monitorIntervalSeconds) { + super(loggerContext, configurationSource); + initializeWatchers(this, configurationSource, monitorIntervalSeconds); + manager = injector.getInstance(BuilderManager.class); + } + + public BuilderManager getBuilderManager() { + return manager; + } + + /** + * Initialize the configuration. + */ + @Override + public void initialize() { + injector.registerBinding(Configuration.KEY, () -> this); + getStrSubstitutor().setConfiguration(this); + getConfigurationStrSubstitutor().setConfiguration(this); + super.getScheduler().start(); + doConfigure(); + setState(State.INITIALIZED); + LOGGER.debug("Configuration {} initialized", this); + } + + @Override + public Configuration reconfigure() { + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java index 4475f6aba7d..caede4e4479 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java @@ -16,6 +16,8 @@ */ package org.apache.log4j.config; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -26,9 +28,14 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.concurrent.atomic.AtomicInteger; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + import org.apache.logging.log4j.core.config.ConfigurationException; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder; import org.apache.logging.log4j.core.tools.BasicCommandLineArguments; import org.apache.logging.log4j.core.tools.picocli.CommandLine; import org.apache.logging.log4j.core.tools.picocli.CommandLine.Command; @@ -175,11 +182,16 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr final int lastIndex = newFile.lastIndexOf("."); newFile = lastIndex < 0 ? newFile + FILE_EXT_XML : newFile.substring(0, lastIndex) + FILE_EXT_XML; - final Path resolved = file.resolveSibling(newFile); + final Path resolvedPath = file.resolveSibling(newFile); try (final InputStream input = new InputStreamWrapper(Files.newInputStream(file), file.toString()); - final OutputStream output = Files.newOutputStream(resolved)) { + final OutputStream output = Files.newOutputStream(resolvedPath)) { try { - convert(input, output); + final ByteArrayOutputStream tmpOutput = new ByteArrayOutputStream(); + convert(input, tmpOutput); + tmpOutput.close(); + DefaultConfigurationBuilder.formatXml( + new StreamSource(new ByteArrayInputStream(tmpOutput.toByteArray())), + new StreamResult(output)); countOKs.incrementAndGet(); } catch (ConfigurationException | IOException e) { countFails.incrementAndGet(); @@ -187,8 +199,14 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr throw e; } e.printStackTrace(); + } catch (TransformerException e) { + countFails.incrementAndGet(); + if (cla.isFailFast()) { + throw new IOException(e); + } + e.printStackTrace(); } - verbose("Wrote %s", resolved); + verbose("Wrote %s", resolvedPath); } } return FileVisitResult.CONTINUE; diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java index 83f66753d93..af4de345c0d 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java @@ -16,9 +16,6 @@ */ package org.apache.log4j.config; -import java.io.IOException; -import java.io.InputStream; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationException; @@ -27,17 +24,21 @@ import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import java.io.IOException; +import java.io.InputStream; + /** * Experimental ConfigurationFactory for Log4j 1.2 properties configuration files. */ // TODO -// @Plugin(name = "Log4j1ConfigurationFactory", category = ConfigurationFactory.CATEGORY) +// @Category(ConfigurationFactory.CATEGORY) +// @Plugin("Log4j1ConfigurationFactory") // // Best Value? // @Order(50) public class Log4j1ConfigurationFactory extends ConfigurationFactory { - private static final String[] SUFFIXES = {".properties"}; + private static final String[] SUFFIXES = { ".properties" }; @Override public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) { diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java index 112ab4247fb..3d364bca02c 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java @@ -25,7 +25,9 @@ import java.util.Properties; import java.util.TreeMap; +import org.apache.log4j.helpers.OptionConverter; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter.Result; import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.appender.FileAppender; import org.apache.logging.log4j.core.appender.NullAppender; @@ -39,7 +41,6 @@ import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; @@ -47,7 +48,7 @@ * Experimental parser for Log4j 1.2 properties configuration files. * * This class is not thread-safe. - * + * *

* From the Log4j 1.2 Javadocs: *

@@ -66,10 +67,10 @@ public class Log4j1ConfigurationParser { private static final String ROOTCATEGORY = "rootCategory"; private static final String TRUE = "true"; private static final String FALSE = "false"; + private static final String RELATIVE = "RELATIVE"; + private static final String NULL = "NULL"; private final Properties properties = new Properties(); - private StrSubstitutor strSubstitutorProperties; - private StrSubstitutor strSubstitutorSystem; private final ConfigurationBuilder builder = ConfigurationBuilderFactory .newConfigurationBuilder(); @@ -89,8 +90,6 @@ public ConfigurationBuilder buildConfigurationBuilder(final throws IOException { try { properties.load(input); - strSubstitutorProperties = new StrSubstitutor(properties); - strSubstitutorSystem = new StrSubstitutor(System.getProperties()); final String rootCategoryValue = getLog4jValue(ROOTCATEGORY); final String rootLoggerValue = getLog4jValue(ROOTLOGGER); if (rootCategoryValue == null && rootLoggerValue == null) { @@ -102,9 +101,16 @@ public ConfigurationBuilder buildConfigurationBuilder(final builder.setConfigurationName("Log4j1"); // DEBUG final String debugValue = getLog4jValue("debug"); - if (Boolean.valueOf(debugValue)) { + if (Boolean.parseBoolean(debugValue)) { builder.setStatusLevel(Level.DEBUG); } + // global threshold + final String threshold = OptionConverter.findAndSubst(PropertiesConfiguration.THRESHOLD_KEY, properties); + if (threshold != null) { + final Level level = OptionConverter.convertLevel(threshold.trim(), Level.ALL); + builder.add(builder.newFilter("ThresholdFilter", Result.NEUTRAL, Result.DENY) + .addAttribute("level", level)); + } // Root buildRootLogger(getLog4jValue(ROOTCATEGORY)); buildRootLogger(getLog4jValue(ROOTLOGGER)); @@ -145,13 +151,13 @@ private Map buildClassToPropertyPrefixMap() { for (final Map.Entry entry : properties.entrySet()) { final Object keyObj = entry.getKey(); if (keyObj != null) { - final String key = keyObj.toString(); + final String key = keyObj.toString().trim(); if (key.startsWith(prefix)) { if (key.indexOf('.', preLength) < 0) { final String name = key.substring(preLength); final Object value = entry.getValue(); if (value != null) { - map.put(name, value.toString()); + map.put(name, value.toString().trim()); } } } @@ -230,7 +236,7 @@ private void buildDailyRollingFileAppender(final String appenderName) { RollingFileAppender.PLUGIN_NAME); buildFileAppender(appenderName, appenderBuilder); final String fileName = getLog4jAppenderValue(appenderName, "File"); - final String datePattern = getLog4jAppenderValue(appenderName, "DatePattern", fileName + "'.'yyyy-MM-dd"); + final String datePattern = getLog4jAppenderValue(appenderName, "DatePattern", ".yyyy-MM-dd"); appenderBuilder.addAttribute("filePattern", fileName + "%d{" + datePattern + "}"); final ComponentBuilder triggeringPolicy = builder.newComponent("Policies") .addComponent(builder.newComponent("TimeBasedTriggeringPolicy").addAttribute("modulate", true)); @@ -256,7 +262,7 @@ private void buildRollingFileAppender(final String appenderName) { builder.add(appenderBuilder); } - private void buildAttribute(final String componentName, final ComponentBuilder componentBuilder, + private void buildAttribute(final String componentName, final ComponentBuilder componentBuilder, final String sourceAttributeName, final String targetAttributeName) { final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName); if (attributeValue != null) { @@ -264,13 +270,7 @@ private void buildAttribute(final String componentName, final ComponentBuilder c } } - private void buildAttributeWithDefault(final String componentName, final ComponentBuilder componentBuilder, - final String sourceAttributeName, final String targetAttributeName, final String defaultValue) { - final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName, defaultValue); - componentBuilder.addAttribute(targetAttributeName, attributeValue); - } - - private void buildMandatoryAttribute(final String componentName, final ComponentBuilder componentBuilder, + private void buildMandatoryAttribute(final String componentName, final ComponentBuilder componentBuilder, final String sourceAttributeName, final String targetAttributeName) { final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName); if (attributeValue != null) { @@ -291,31 +291,49 @@ private void buildAppenderLayout(final String name, final AppenderComponentBuild switch (layoutClass) { case "org.apache.log4j.PatternLayout": case "org.apache.log4j.EnhancedPatternLayout": { - final String pattern = getLog4jAppenderValue(name, "layout.ConversionPattern", null) - - // Log4j 2's %x (NDC) is not compatible with Log4j 1's - // %x - // Log4j 1: "foo bar baz" - // Log4j 2: "[foo, bar, baz]" - // Use %ndc to get the Log4j 1 format - .replace("%x", "%ndc") - - // Log4j 2's %X (MDC) is not compatible with Log4j 1's - // %X - // Log4j 1: "{{foo,bar}{hoo,boo}}" - // Log4j 2: "{foo=bar,hoo=boo}" - // Use %properties to get the Log4j 1 format - .replace("%X", "%properties"); - + String pattern = getLog4jAppenderValue(name, "layout.ConversionPattern", null); + if (pattern != null) { + pattern = pattern + // Log4j 2 and Log4j 1 level names differ for custom levels + .replaceAll("%([-\\.\\d]*)p(?!\\w)", "%$1v1Level") + // Log4j 2's %x (NDC) is not compatible with Log4j 1's + // %x + // Log4j 1: "foo bar baz" + // Log4j 2: "[foo, bar, baz]" + // Use %ndc to get the Log4j 1 format + .replaceAll("%([-\\.\\d]*)x(?!\\w)", "%$1ndc") + + // Log4j 2's %X (MDC) is not compatible with Log4j 1's + // %X + // Log4j 1: "{{foo,bar}{hoo,boo}}" + // Log4j 2: "{foo=bar,hoo=boo}" + // Use %properties to get the Log4j 1 format + .replaceAll("%([-\\.\\d]*)X(?!\\w)", "%$1properties"); + } else { + pattern = "%m%n"; + } appenderBuilder.add(newPatternLayout(pattern)); break; } case "org.apache.log4j.SimpleLayout": { - appenderBuilder.add(newPatternLayout("%level - %m%n")); + appenderBuilder.add(newPatternLayout("%v1Level - %m%n")); break; } case "org.apache.log4j.TTCCLayout": { - String pattern = "%r "; + String pattern = ""; + final String dateFormat = getLog4jAppenderValue(name, "layout.DateFormat", RELATIVE); + final String timezone = getLog4jAppenderValue(name, "layout.TimeZone", null); + if (dateFormat != null) { + if (RELATIVE.equalsIgnoreCase(dateFormat)) { + pattern += "%r "; + } else if (!NULL.equalsIgnoreCase(dateFormat)){ + pattern += "%d{" + dateFormat + "}"; + if (timezone != null) { + pattern += "{" + timezone + "}"; + } + pattern += " "; + } + } if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ThreadPrinting", TRUE))) { pattern += "[%t] "; } @@ -386,13 +404,13 @@ private void buildLoggers(final String prefix) { for (final Map.Entry entry : properties.entrySet()) { final Object keyObj = entry.getKey(); if (keyObj != null) { - final String key = keyObj.toString(); + final String key = keyObj.toString().trim(); if (key.startsWith(prefix)) { final String name = key.substring(preLength); final Object value = entry.getValue(); if (value != null) { // a Level may be followed by a list of Appender refs. - final String valueStr = value.toString(); + final String valueStr = value.toString().trim(); final String[] split = valueStr.split(COMMA_DELIMITED_RE); final String level = getLevelString(split, null); if (level == null) { @@ -421,8 +439,8 @@ private String getLog4jAppenderValue(final String appenderName, final String att private String getProperty(final String key) { final String value = properties.getProperty(key); - final String sysValue = strSubstitutorSystem.replace(value); - return strSubstitutorProperties.replace(sysValue); + final String substVars = OptionConverter.substVars(value, properties); + return substVars == null ? null : substVars.trim(); } private String getProperty(final String key, final String defaultValue) { diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java new file mode 100644 index 00000000000..7edee3d86b8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java @@ -0,0 +1,615 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.SortedMap; +import java.util.StringTokenizer; +import java.util.TreeMap; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.LogManager; +import org.apache.log4j.PatternLayout; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.builders.BuilderManager; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.status.StatusConfiguration; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.util.LoaderUtil; + +/** + * Constructs a configuration based on Log4j 1 properties. + */ +public class PropertiesConfiguration extends Log4j1Configuration { + + private static final String CATEGORY_PREFIX = "log4j.category."; + private static final String LOGGER_PREFIX = "log4j.logger."; + private static final String ADDITIVITY_PREFIX = "log4j.additivity."; + private static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory"; + private static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger"; + private static final String APPENDER_PREFIX = "log4j.appender."; + private static final String LOGGER_REF = "logger-ref"; + private static final String ROOT_REF = "root-ref"; + private static final String APPENDER_REF_TAG = "appender-ref"; + + /** + * If property set to true, then hierarchy will be reset before configuration. + */ + private static final String RESET_KEY = "log4j.reset"; + + public static final String THRESHOLD_KEY = "log4j.threshold"; + public static final String DEBUG_KEY = "log4j.debug"; + + private static final String INTERNAL_ROOT_NAME = "root"; + + private final Map registry = new HashMap<>(); + private Properties properties; + + /** + * Constructs a new instance. + * + * @param loggerContext The LoggerContext. + * @param source The ConfigurationSource. + * @param monitorIntervalSeconds The monitoring interval in seconds. + */ + public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, final int monitorIntervalSeconds) { + super(loggerContext, source, monitorIntervalSeconds); + } + + /** + * Constructs a new instance. + * + * @param loggerContext The LoggerContext. + * @param properties The ConfigurationSource, may be null. + */ + public PropertiesConfiguration(final LoggerContext loggerContext, final Properties properties) { + super(loggerContext, ConfigurationSource.NULL_SOURCE, 0); + this.properties = properties; + } + + /** + * Constructs a new instance. + * + * @param loggerContext The LoggerContext. + * @param properties The ConfigurationSource. + */ + public PropertiesConfiguration(org.apache.logging.log4j.spi.LoggerContext loggerContext, Properties properties) { + this((LoggerContext) loggerContext, properties); + } + + @Override + public void doConfigure() { + if (properties == null) { + properties = new Properties(); + final InputStream inputStream = getConfigurationSource().getInputStream(); + if (inputStream != null) { + try { + properties.load(inputStream); + } catch (final Exception e) { + LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e); + return; + } + } + } + // If we reach here, then the config file is alright. + doConfigure(properties); + } + + @Override + public Configuration reconfigure() { + try { + final ConfigurationSource source = getConfigurationSource().resetInputStream(); + if (source == null) { + return null; + } + final Configuration config = new PropertiesConfigurationFactory().getConfiguration(getLoggerContext(), source); + return config == null || config.getState() != State.INITIALIZING ? null : config; + } catch (final IOException ex) { + LOGGER.error("Cannot locate file {}: {}", getConfigurationSource(), ex); + } + return null; + } + + /** + * Reads a configuration from a file. The existing configuration is not cleared nor reset. If you require a + * different behavior, then call {@link LogManager#resetConfiguration resetConfiguration} method before calling + * doConfigure. + *

+ * The configuration file consists of statements in the format key=value. The syntax of different + * configuration elements are discussed below. + *

+ *

+ * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a custom level + * value. A custom level value can be specified in the form level#classname. By default the repository-wide threshold is + * set to the lowest possible value, namely the level ALL. + *

+ * + *

Appender configuration

+ *

+ * Appender configuration syntax is: + *

+ *
+     * # For appender named appenderName, set its class.
+     * # Note: The appender name can contain dots.
+     * log4j.appender.appenderName=fully.qualified.name.of.appender.class
+     *
+     * # Set appender specific options.
+     * log4j.appender.appenderName.option1=value1
+     * ...
+     * log4j.appender.appenderName.optionN=valueN
+     * 
+ *

+ * For each named appender you can configure its {@link Layout}. The syntax for configuring an appender's layout is: + *

+ *
+     * log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
+     * log4j.appender.appenderName.layout.option1=value1
+     * ....
+     * log4j.appender.appenderName.layout.optionN=valueN
+     * 
+ *

+ * The syntax for adding {@link Filter}s to an appender is: + *

+ *
+     * log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
+     * log4j.appender.appenderName.filter.ID.option1=value1
+     * ...
+     * log4j.appender.appenderName.filter.ID.optionN=valueN
+     * 
+ *

+ * The first line defines the class name of the filter identified by ID; subsequent lines with the same ID specify + * filter option - value pairs. Multiple filters are added to the appender in the lexicographic order of IDs. + *

+ *

+ * The syntax for adding an {@link ErrorHandler} to an appender is: + *

+ *
+     * log4j.appender.appenderName.errorhandler=fully.qualified.name.of.errorhandler.class
+     * log4j.appender.appenderName.errorhandler.appender-ref=appenderName
+     * log4j.appender.appenderName.errorhandler.option1=value1
+     * ...
+     * log4j.appender.appenderName.errorhandler.optionN=valueN
+     * 
+ * + *

Configuring loggers

+ *

+ * The syntax for configuring the root logger is: + *

+ *
+     * log4j.rootLogger=[level], appenderName, appenderName, ...
+     * 
+ *

+ * This syntax means that an optional level can be supplied followed by appender names separated by commas. + *

+ *

+ * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a custom level + * value. A custom level value can be specified in the form level#classname. + *

+ *

+ * If a level value is specified, then the root level is set to the corresponding level. If no level value is specified, + * then the root level remains untouched. + *

+ *

+ * The root logger can be assigned multiple appenders. + *

+ *

+ * Each appenderName (separated by commas) will be added to the root logger. The named appender is defined using + * the appender syntax defined above. + *

+ *

+ * For non-root categories the syntax is almost the same: + *

+ *
+     * log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
+     * 
+ *

+ * The meaning of the optional level value is discussed above in relation to the root logger. In addition however, the + * value INHERITED can be specified meaning that the named logger should inherit its level from the logger hierarchy. + *

+ *

+ * If no level value is supplied, then the level of the named logger remains untouched. + *

+ *

+ * By default categories inherit their level from the hierarchy. However, if you set the level of a logger and later + * decide that that logger should inherit its level, then you should specify INHERITED as the value for the level value. + * NULL is a synonym for INHERITED. + *

+ *

+ * Similar to the root logger syntax, each appenderName (separated by commas) will be attached to the named + * logger. + *

+ *

+ * See the appender additivity rule in the user manual for the meaning + * of the additivity flag. + *

+ *
+     * # Set options for appender named "A1". # Appender "A1" will be a SyslogAppender
+     * log4j.appender.A1=org.apache.log4j.net.SyslogAppender
+     *
+     * # The syslog daemon resides on www.abc.net log4j.appender.A1.SyslogHost=www.abc.net
+     *
+     * # A1's layout is a PatternLayout, using the conversion pattern # %r %-5p %c{2} %M.%L %x - %m\n. Thus, the log
+     * output will # include # the relative time since the start of the application in # milliseconds, followed by the level
+     * of the log request, # followed by the two rightmost components of the logger name, # followed by the callers method
+     * name, followed by the line number, # the nested diagnostic context and finally the message itself. # Refer to the
+     * documentation of {@link PatternLayout} for further information # on the syntax of the ConversionPattern key.
+     * log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2}
+     * %M.%L %x - %m\n
+     *
+     * # Set options for appender named "A2" # A2 should be a RollingFileAppender, with maximum file size of 10 MB # using
+     * at most one backup file. A2's layout is TTCC, using the # ISO8061 date format with context printing enabled.
+     * log4j.appender.A2=org.apache.log4j.RollingFileAppender log4j.appender.A2.MaxFileSize=10MB
+     * log4j.appender.A2.MaxBackupIndex=1 log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
+     * log4j.appender.A2.layout.ContextPrinting=enabled log4j.appender.A2.layout.DateFormat=ISO8601
+     *
+     * # Root logger set to DEBUG using the A2 appender defined above. log4j.rootLogger=DEBUG, A2
+     *
+     * # Logger definitions: # The SECURITY logger inherits is level from root. However, it's output # will go to A1
+     * appender defined above. It's additivity is non-cumulative. log4j.logger.SECURITY=INHERIT, A1
+     * log4j.additivity.SECURITY=false
+     *
+     * # Only warnings or above will be logged for the logger "SECURITY.access". # Output will go to A1.
+     * log4j.logger.SECURITY.access=WARN
+     *
+     *
+     * # The logger "class.of.the.day" inherits its level from the # logger hierarchy. Output will go to the appender's of
+     * the root # logger, A2 in this case. log4j.logger.class.of.the.day=INHERIT
+     * 
+ *

+ * Refer to the setOption method in each Appender and Layout for class specific options. + *

+ *

+ * Use the # or ! characters at the beginning of a line for comments. + *

+ */ + private void doConfigure(final Properties properties) { + String status = "error"; + String value = properties.getProperty(DEBUG_KEY); + if (value == null) { + value = properties.getProperty("log4j.configDebug"); + if (value != null) { + LOGGER.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead."); + } + } + + if (value != null) { + status = OptionConverter.toBoolean(value, false) ? "debug" : "error"; + } + + final StatusConfiguration statusConfig = new StatusConfiguration().setStatus(status); + statusConfig.initialize(); + + // if log4j.reset=true then reset hierarchy + final String reset = properties.getProperty(RESET_KEY); + if (reset != null && OptionConverter.toBoolean(reset, false)) { + LogManager.resetConfiguration(); + } + + final String threshold = OptionConverter.findAndSubst(THRESHOLD_KEY, properties); + if (threshold != null) { + final Level level = OptionConverter.convertLevel(threshold.trim(), Level.ALL); + addFilter(ThresholdFilter.createFilter(level, Result.NEUTRAL, Result.DENY)); + } + + configureRoot(properties); + parseLoggers(properties); + + LOGGER.debug("Finished configuring."); + } + + // -------------------------------------------------------------------------- + // Internal stuff + // -------------------------------------------------------------------------- + + private void configureRoot(final Properties props) { + String effectiveFrefix = ROOT_LOGGER_PREFIX; + String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props); + + if (value == null) { + value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props); + effectiveFrefix = ROOT_CATEGORY_PREFIX; + } + + if (value == null) { + LOGGER.debug("Could not find root logger information. Is this OK?"); + } else { + final LoggerConfig root = getRootLogger(); + parseLogger(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value); + } + } + + /** + * Parses non-root elements, such non-root categories and renderers. + */ + private void parseLoggers(final Properties props) { + final Enumeration enumeration = props.propertyNames(); + while (enumeration.hasMoreElements()) { + final String key = Objects.toString(enumeration.nextElement(), null); + if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) { + String loggerName = null; + if (key.startsWith(CATEGORY_PREFIX)) { + loggerName = key.substring(CATEGORY_PREFIX.length()); + } else if (key.startsWith(LOGGER_PREFIX)) { + loggerName = key.substring(LOGGER_PREFIX.length()); + } + final String value = OptionConverter.findAndSubst(key, props); + LoggerConfig loggerConfig = getLogger(loggerName); + if (loggerConfig == null) { + final boolean additivity = getAdditivityForLogger(props, loggerName); + loggerConfig = new LoggerConfig(loggerName, org.apache.logging.log4j.Level.ERROR, additivity); + addLogger(loggerName, loggerConfig); + } + parseLogger(props, loggerConfig, key, loggerName, value); + } + } + } + + /** + * Parses the additivity option for a non-root category. + */ + private boolean getAdditivityForLogger(final Properties props, final String loggerName) { + boolean additivity = true; + final String key = ADDITIVITY_PREFIX + loggerName; + final String value = OptionConverter.findAndSubst(key, props); + LOGGER.debug("Handling {}=[{}]", key, value); + // touch additivity only if necessary + if ((value != null) && (!value.equals(""))) { + additivity = OptionConverter.toBoolean(value, true); + } + return additivity; + } + + /** + * This method must work for the root category as well. + */ + private void parseLogger(final Properties props, final LoggerConfig loggerConfig, final String optionKey, final String loggerName, final String value) { + + LOGGER.debug("Parsing for [{}] with value=[{}].", loggerName, value); + // We must skip over ',' but not white space + final StringTokenizer st = new StringTokenizer(value, ","); + + // If value is not in the form ", appender.." or "", then we should set the level of the logger. + + if (!(value.startsWith(",") || value.equals(""))) { + + // just to be on the safe side... + if (!st.hasMoreTokens()) { + return; + } + + final String levelStr = st.nextToken(); + LOGGER.debug("Level token is [{}].", levelStr); + + final org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR + : OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG); + loggerConfig.setLevel(level); + LOGGER.debug("Logger {} level set to {}", loggerName, level); + } + + Appender appender; + String appenderName; + while (st.hasMoreTokens()) { + appenderName = st.nextToken().trim(); + if (appenderName == null || appenderName.equals(",")) { + continue; + } + LOGGER.debug("Parsing appender named \"{}\".", appenderName); + appender = parseAppender(props, appenderName); + if (appender != null) { + LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName, loggerConfig.getName()); + loggerConfig.addAppender(getAppender(appenderName), null, null); + } else { + LOGGER.debug("Appender named [{}] not found.", appenderName); + } + } + } + + public Appender parseAppender(final Properties props, final String appenderName) { + Appender appender = registry.get(appenderName); + if ((appender != null)) { + LOGGER.debug("Appender \"" + appenderName + "\" was already parsed."); + return appender; + } + // Appender was not previously initialized. + final String prefix = APPENDER_PREFIX + appenderName; + final String layoutPrefix = prefix + ".layout"; + final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter."; + final String className = OptionConverter.findAndSubst(prefix, props); + if (className == null) { + LOGGER.debug("Appender \"" + appenderName + "\" does not exist."); + return null; + } + appender = manager.parseAppender(appenderName, className, prefix, layoutPrefix, filterPrefix, props, this); + if (appender == null) { + appender = buildAppender(appenderName, className, prefix, layoutPrefix, filterPrefix, props); + } else { + registry.put(appenderName, appender); + addAppender(AppenderAdapter.adapt(appender)); + } + return appender; + } + + private Appender buildAppender(final String appenderName, final String className, final String prefix, final String layoutPrefix, final String filterPrefix, + final Properties props) { + final Appender appender = newInstanceOf(className, "Appender"); + if (appender == null) { + return null; + } + appender.setName(appenderName); + appender.setLayout(parseLayout(layoutPrefix, appenderName, props)); + final String errorHandlerPrefix = prefix + ".errorhandler"; + final String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props); + if (errorHandlerClass != null) { + final ErrorHandler eh = parseErrorHandler(props, errorHandlerPrefix, errorHandlerClass, appender); + if (eh != null) { + appender.setErrorHandler(eh); + } + } + appender.addFilter(parseAppenderFilters(props, filterPrefix, appenderName)); + final String[] keys = new String[] {layoutPrefix}; + addProperties(appender, keys, props, prefix); + addAppender(AppenderAdapter.adapt(appender)); + registry.put(appenderName, appender); + return appender; + } + + public Layout parseLayout(final String layoutPrefix, final String appenderName, final Properties props) { + final String layoutClass = OptionConverter.findAndSubst(layoutPrefix, props); + if (layoutClass == null) { + return null; + } + Layout layout = manager.parse(layoutClass, layoutPrefix, props, this, BuilderManager.INVALID_LAYOUT); + if (layout == null) { + layout = buildLayout(layoutPrefix, layoutClass, appenderName, props); + } + return layout; + } + + private Layout buildLayout(final String layoutPrefix, final String className, final String appenderName, final Properties props) { + final Layout layout = newInstanceOf(className, "Layout"); + if (layout == null) { + return null; + } + LOGGER.debug("Parsing layout options for \"{}\".", appenderName); + PropertySetter.setProperties(layout, props, layoutPrefix + "."); + LOGGER.debug("End of parsing for \"{}\".", appenderName); + return layout; + } + + public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix, final String errorHandlerClass, final Appender appender) { + final ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler"); + final String[] keys = new String[] { + // @formatter:off + errorHandlerPrefix + "." + ROOT_REF, + errorHandlerPrefix + "." + LOGGER_REF, + errorHandlerPrefix + "." + APPENDER_REF_TAG}; + // @formatter:on + addProperties(eh, keys, props, errorHandlerPrefix); + return eh; + } + + public void addProperties(final Object obj, final String[] keys, final Properties props, final String prefix) { + final Properties edited = new Properties(); + props.stringPropertyNames().stream().filter(name -> { + if (name.startsWith(prefix)) { + for (final String key : keys) { + if (name.equals(key)) { + return false; + } + } + return true; + } + return false; + }).forEach(name -> edited.put(name, props.getProperty(name))); + PropertySetter.setProperties(obj, edited, prefix + "."); + } + + public Filter parseAppenderFilters(final Properties props, final String filterPrefix, final String appenderName) { + // extract filters and filter options from props into a hashtable mapping + // the property name defining the filter class to a list of pre-parsed + // name-value pairs associated to that filter + final int fIdx = filterPrefix.length(); + final SortedMap> filters = new TreeMap<>(); + final Enumeration e = props.keys(); + String name = ""; + while (e.hasMoreElements()) { + final String key = (String) e.nextElement(); + if (key.startsWith(filterPrefix)) { + final int dotIdx = key.indexOf('.', fIdx); + String filterKey = key; + if (dotIdx != -1) { + filterKey = key.substring(0, dotIdx); + name = key.substring(dotIdx + 1); + } + final List filterOpts = filters.computeIfAbsent(filterKey, k -> new ArrayList<>()); + if (dotIdx != -1) { + final String value = OptionConverter.findAndSubst(key, props); + filterOpts.add(new NameValue(name, value)); + } + } + } + + Filter head = null; + for (final Map.Entry> entry : filters.entrySet()) { + final String clazz = props.getProperty(entry.getKey()); + Filter filter = null; + if (clazz != null) { + filter = manager.parse(clazz, entry.getKey(), props, this, BuilderManager.INVALID_FILTER); + if (filter == null) { + LOGGER.debug("Filter key: [{}] class: [{}] props: {}", entry.getKey(), clazz, entry.getValue()); + filter = buildFilter(clazz, appenderName, entry.getValue()); + } + } + head = FilterAdapter.addFilter(head, filter); + } + return head; + } + + private Filter buildFilter(final String className, final String appenderName, final List props) { + final Filter filter = newInstanceOf(className, "Filter"); + if (filter != null) { + final PropertySetter propSetter = new PropertySetter(filter); + for (final NameValue property : props) { + propSetter.setProperty(property.key, property.value); + } + propSetter.activate(); + } + return filter; + } + + private static T newInstanceOf(final String className, final String type) { + try { + return LoaderUtil.newInstanceOf(className); + } catch (ReflectiveOperationException ex) { + LOGGER.error("Unable to create {} {} due to {}:{}", type, className, ex.getClass().getSimpleName(), ex.getMessage(), ex); + return null; + } + } + + private static class NameValue { + String key, value; + + NameValue(final String key, final String value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return key + "=" + value; + } + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfigurationFactory.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfigurationFactory.java new file mode 100644 index 00000000000..bf8f1cab2eb --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfigurationFactory.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Order; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * Configures Log4j from a log4j 1 format properties file. + */ +@Namespace(ConfigurationFactory.NAMESPACE) +@Plugin("Log4j1PropertiesConfigurationFactory") +@Order(2) +public class PropertiesConfigurationFactory extends ConfigurationFactory { + + static final String FILE_EXTENSION = ".properties"; + + /** + * File name prefix for test configurations. + */ + protected static final String TEST_PREFIX = "log4j-test"; + + /** + * File name prefix for standard configurations. + */ + protected static final String DEFAULT_PREFIX = "log4j"; + + @Override + protected String[] getSupportedTypes() { + if (!PropertiesUtil.getProperties().getBooleanProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL, Boolean.FALSE)) { + return null; + } + return new String[] { FILE_EXTENSION }; + } + + @Override + public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) { + int interval = PropertiesUtil.getProperties().getIntegerProperty(Log4j1Configuration.MONITOR_INTERVAL, 0); + return new PropertiesConfiguration(loggerContext, source, interval); + } + + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + + @Override + protected String getDefaultPrefix() { + return DEFAULT_PREFIX; + } + + @Override + protected String getVersion() { + return LOG4J1_VERSION; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java index 342024ad9f2..27a7b0ddb62 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java @@ -1,47 +1,159 @@ /* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 + * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. + * See the License for the specific language governing permissions and + * limitations under the License. */ + +// Contributors: Georg Lundesgaard + package org.apache.log4j.config; +import org.apache.log4j.Appender; +import org.apache.log4j.Level; +import org.apache.log4j.Priority; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.OptionHandler; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.util.OptionConverter; +import org.apache.logging.log4j.status.StatusLogger; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; import java.beans.PropertyDescriptor; +import java.io.InterruptedIOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Properties; /** + * General purpose Object property setter. Clients repeatedly invokes + * {@link #setProperty setProperty(name,value)} in order to invoke setters + * on the Object specified in the constructor. This class relies on the + * JavaBeans {@link Introspector} to analyze the given Object Class using + * reflection. * - * @since 1.1 + *

Usage: + *

+ * PropertySetter ps = new PropertySetter(anObject);
+ * ps.set("name", "Joe");
+ * ps.set("age", "32");
+ * ps.set("isMale", "true");
+ * 
+ * will cause the invocations anObject.setName("Joe"), anObject.setAge(32), + * and setMale(true) if such methods exist with those signatures. + * Otherwise an {@link IntrospectionException} are thrown. */ public class PropertySetter { + private static final PropertyDescriptor[] EMPTY_PROPERTY_DESCRIPTOR_ARRAY = {}; + private static final Logger LOGGER = StatusLogger.getLogger(); + protected Object obj; + protected PropertyDescriptor[] props; /** * Create a new PropertySetter for the specified Object. This is done * in preparation for invoking {@link #setProperty} one or more times. * - * @param obj the object for which to set properties + * @param obj the object for which to set properties */ - public PropertySetter(final Object obj) { + public PropertySetter(Object obj) { + this.obj = obj; } - /** - * Set the properties for the object that match the prefix passed as parameter. + * Set the properties of an object passed as a parameter in one + * go. The properties are parsed relative to a + * prefix. * - * @param properties The properties - * @param prefix The prefix + * @param obj The object to configure. + * @param properties A java.util.Properties containing keys and values. + * @param prefix Only keys having the specified prefix will be set. */ - public void setProperties(final Properties properties, final String prefix) { + public static void setProperties(Object obj, Properties properties, String prefix) { + new PropertySetter(obj).setProperties(properties, prefix); + } + + /** + * Uses JavaBeans {@link Introspector} to computer setters of object to be + * configured. + */ + protected void introspect() { + try { + BeanInfo bi = Introspector.getBeanInfo(obj.getClass()); + props = bi.getPropertyDescriptors(); + } catch (IntrospectionException ex) { + LOGGER.error("Failed to introspect {}: {}", obj, ex.getMessage()); + props = EMPTY_PROPERTY_DESCRIPTOR_ARRAY; + } + } + + /** + * Set the properties for the object that match the + * prefix passed as parameter. + * @param properties The properties. + * @param prefix The prefix of the properties to use. + */ + public void setProperties(Properties properties, String prefix) { + int len = prefix.length(); + + for (String key : properties.stringPropertyNames()) { + + // handle only properties that start with the desired prefix. + if (key.startsWith(prefix)) { + + + // ignore key if it contains dots after the prefix + if (key.indexOf('.', len + 1) > 0) { + continue; + } + + String value = OptionConverter.findAndSubst(key, properties); + key = key.substring(len); + if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) { + continue; + } + // + // if the property type is an OptionHandler + // (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender) + PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key)); + if (prop != null + && OptionHandler.class.isAssignableFrom(prop.getPropertyType()) + && prop.getWriteMethod() != null) { + OptionHandler opt = (OptionHandler) + OptionConverter.instantiateByKey(properties, prefix + key, + prop.getPropertyType(), + null); + PropertySetter setter = new PropertySetter(opt); + setter.setProperties(properties, prefix + key + "."); + try { + prop.getWriteMethod().invoke(this.obj, opt); + } catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof InterruptedException + || ex.getTargetException() instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex); + } catch (IllegalAccessException | RuntimeException ex) { + LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex); + } + continue; + } + + setProperty(key, value); + } + } + activate(); } /** @@ -56,33 +168,127 @@ public void setProperties(final Properties properties, final String prefix) { * to an int using new Integer(value). If the setter expects a boolean, * the conversion is by new Boolean(value). * - * @param name name of the property - * @param value String value of the property + * @param name name of the property + * @param value String value of the property */ - public void setProperty(final String name, final String value) { + public void setProperty(String name, String value) { + if (value == null) { + return; + } + + name = Introspector.decapitalize(name); + PropertyDescriptor prop = getPropertyDescriptor(name); + + //LOGGER.debug("---------Key: "+name+", type="+prop.getPropertyType()); + + if (prop == null) { + LOGGER.warn("No such property [" + name + "] in " + + obj.getClass().getName() + "."); + } else { + try { + setProperty(prop, name, value); + } catch (PropertySetterException ex) { + LOGGER.warn("Failed to set property [{}] to value \"{}\".", name, value, ex.rootCause); + } + } } /** * Set the named property given a {@link PropertyDescriptor}. * - * @param prop A PropertyDescriptor describing the characteristics of the property to set. - * @param name The named of the property to set. + * @param prop A PropertyDescriptor describing the characteristics + * of the property to set. + * @param name The named of the property to set. * @param value The value of the property. - * @throws PropertySetterException (Never actually throws this exception. Kept for historical purposes.) + * @throws PropertySetterException if no setter is available. */ - public void setProperty(final PropertyDescriptor prop, final String name, final String value) - throws PropertySetterException { + public void setProperty(PropertyDescriptor prop, String name, String value) + throws PropertySetterException { + Method setter = prop.getWriteMethod(); + if (setter == null) { + throw new PropertySetterException("No setter for property [" + name + "]."); + } + Class[] paramTypes = setter.getParameterTypes(); + if (paramTypes.length != 1) { + throw new PropertySetterException("#params for setter != 1"); + } + + Object arg; + try { + arg = convertArg(value, paramTypes[0]); + } catch (Throwable t) { + throw new PropertySetterException("Conversion to type [" + paramTypes[0] + + "] failed. Reason: " + t); + } + if (arg == null) { + throw new PropertySetterException( + "Conversion to type [" + paramTypes[0] + "] failed."); + } + LOGGER.debug("Setting property [" + name + "] to [" + arg + "]."); + try { + setter.invoke(obj, arg); + } catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof InterruptedException + || ex.getTargetException() instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + throw new PropertySetterException(ex); + } catch (IllegalAccessException | RuntimeException ex) { + throw new PropertySetterException(ex); + } } + /** - * Set the properties of an object passed as a parameter in one - * go. The properties are parsed relative to a - * prefix. - * - * @param obj The object to configure. - * @param properties A java.util.Properties containing keys and values. - * @param prefix Only keys having the specified prefix will be set. + * Convert val a String parameter to an object of a + * given type. + * @param val The value to convert. + * @param type The type of the value to convert to. + * @return The result of the conversion. */ - public static void setProperties(final Object obj, final Properties properties, final String prefix) { + protected Object convertArg(String val, Class type) { + if (val == null) { + return null; + } + + String v = val.trim(); + if (String.class.isAssignableFrom(type)) { + return val; + } else if (Integer.TYPE.isAssignableFrom(type)) { + return Integer.parseInt(v); + } else if (Long.TYPE.isAssignableFrom(type)) { + return Long.parseLong(v); + } else if (Boolean.TYPE.isAssignableFrom(type)) { + if ("true".equalsIgnoreCase(v)) { + return Boolean.TRUE; + } else if ("false".equalsIgnoreCase(v)) { + return Boolean.FALSE; + } + } else if (Priority.class.isAssignableFrom(type)) { + return org.apache.log4j.helpers.OptionConverter.toLevel(v, Log4j1Configuration.DEFAULT_LEVEL); + } else if (ErrorHandler.class.isAssignableFrom(type)) { + return OptionConverter.instantiateByClassName(v, + ErrorHandler.class, null); + } + return null; + } + + + protected PropertyDescriptor getPropertyDescriptor(String name) { + if (props == null) { + introspect(); + } + for (PropertyDescriptor prop : props) { + if (name.equals(prop.getName())) { + return prop; + } + } + return null; + } + + public void activate() { + if (obj instanceof OptionHandler) { + ((OptionHandler) obj).activateOptions(); + } } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetterException.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetterException.java index c9dc4cfb579..703900cbe1d 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetterException.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetterException.java @@ -45,7 +45,6 @@ public PropertySetterException(final String msg) { * @param rootCause The root cause */ public PropertySetterException(final Throwable rootCause) { - super(); this.rootCause = rootCause; } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java new file mode 100644 index 00000000000..6caee8ae7b5 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AbsoluteTimeDateFormat.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.ParsePosition; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * Formats a {@link Date} in the format "HH:mm:ss,SSS" for example, "15:49:37,459". + * + * @since 0.7.5 + */ +public class AbsoluteTimeDateFormat extends DateFormat { + + private static final long serialVersionUID = -388856345976723342L; + + /** + * String constant used to specify {@link org.apache.log4j.helpers.AbsoluteTimeDateFormat} in layouts. Current value is + * ABSOLUTE. + */ + public final static String ABS_TIME_DATE_FORMAT = "ABSOLUTE"; + + /** + * String constant used to specify {@link org.apache.log4j.helpers.DateTimeDateFormat} in layouts. Current value is + * DATE. + */ + public final static String DATE_AND_TIME_DATE_FORMAT = "DATE"; + + /** + * String constant used to specify {@link org.apache.log4j.helpers.ISO8601DateFormat} in layouts. Current value is + * ISO8601. + */ + public final static String ISO8601_DATE_FORMAT = "ISO8601"; + + private static long previousTime; + + private static char[] previousTimeWithoutMillis = new char[9]; // "HH:mm:ss." + + public AbsoluteTimeDateFormat() { + setCalendar(Calendar.getInstance()); + } + + public AbsoluteTimeDateFormat(final TimeZone timeZone) { + setCalendar(Calendar.getInstance(timeZone)); + } + + /** + * Appends to sbuf the time in the format "HH:mm:ss,SSS" for example, "15:49:37,459" + * + * @param date the date to format + * @param sbuf the string buffer to write to + * @param fieldPosition remains untouched + */ + @Override + public StringBuffer format(final Date date, final StringBuffer sbuf, final FieldPosition fieldPosition) { + + final long now = date.getTime(); + final int millis = (int) (now % 1000); + + if ((now - millis) != previousTime || previousTimeWithoutMillis[0] == 0) { + // We reach this point at most once per second + // across all threads instead of each time format() + // is called. This saves considerable CPU time. + + calendar.setTime(date); + + final int start = sbuf.length(); + + final int hour = calendar.get(Calendar.HOUR_OF_DAY); + if (hour < 10) { + sbuf.append('0'); + } + sbuf.append(hour); + sbuf.append(':'); + + final int mins = calendar.get(Calendar.MINUTE); + if (mins < 10) { + sbuf.append('0'); + } + sbuf.append(mins); + sbuf.append(':'); + + final int secs = calendar.get(Calendar.SECOND); + if (secs < 10) { + sbuf.append('0'); + } + sbuf.append(secs); + sbuf.append(','); + + // store the time string for next time to avoid recomputation + sbuf.getChars(start, sbuf.length(), previousTimeWithoutMillis, 0); + + previousTime = now - millis; + } else { + sbuf.append(previousTimeWithoutMillis); + } + + if (millis < 100) { + sbuf.append('0'); + } + if (millis < 10) { + sbuf.append('0'); + } + + sbuf.append(millis); + return sbuf; + } + + /** + * Always returns null. + */ + @Override + public Date parse(final String s, final ParsePosition pos) { + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.java new file mode 100644 index 00000000000..42086f8d697 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.helpers; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Objects; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.log4j.Appender; +import org.apache.log4j.spi.AppenderAttachable; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Allows Classes to attach Appenders. + */ +public class AppenderAttachableImpl implements AppenderAttachable { + + private final ConcurrentMap appenders = new ConcurrentHashMap<>(); + + /** Array of appenders. TODO */ + protected Vector appenderList; + + @Override + public void addAppender(final Appender appender) { + if (appender != null) { + // NullAppender name is null. + appenders.put(Objects.toString(appender.getName()), appender); + } + } + + /** + * Calls the doAppend method on all attached appenders. + * + * @param event The event to log. + * @return The number of appenders. + */ + public int appendLoopOnAppenders(final LoggingEvent event) { + for (final Appender appender : appenders.values()) { + appender.doAppend(event); + } + return appenders.size(); + } + + /** + * Closes all appenders. + */ + public void close() { + for (final Appender appender : appenders.values()) { + appender.close(); + } + } + + @Override + public Enumeration getAllAppenders() { + return Collections.enumeration(appenders.values()); + } + + @Override + public Appender getAppender(final String name) { + // No null keys allowed in a CHM. + return name == null ? null : appenders.get(name); + } + + @Override + public boolean isAttached(final Appender appender) { + return appender != null ? appenders.containsValue(appender) : false; + } + + @Override + public void removeAllAppenders() { + appenders.clear(); + } + + @Override + public void removeAppender(final Appender appender) { + if (appender != null) { + final String name = appender.getName(); + if (name != null) { + appenders.remove(name, appender); + } + } + } + + @Override + public void removeAppender(final String name) { + if (name != null) { + appenders.remove(name); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/BoundedFIFO.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/BoundedFIFO.java new file mode 100644 index 00000000000..69383613dc1 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/BoundedFIFO.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import org.apache.log4j.spi.LoggingEvent; + +/** + * Bounded first-in-first-out buffer. + * + * @since version 0.9.1 + */ +public class BoundedFIFO { + + LoggingEvent[] buf; + int numElements = 0; + int first = 0; + int next = 0; + int maxSize; + + /** + * Constructs a new instance with a maximum size passed as argument. + */ + public BoundedFIFO(final int maxSize) { + if (maxSize < 1) { + throw new IllegalArgumentException("The maxSize argument (" + maxSize + ") is not a positive integer."); + } + this.maxSize = maxSize; + buf = new LoggingEvent[maxSize]; + } + + /** + * Gets the first element in the buffer. Returns null if there are no elements in the buffer. + */ + public LoggingEvent get() { + if (numElements == 0) { + return null; + } + + final LoggingEvent r = buf[first]; + buf[first] = null; // help garbage collection + + if (++first == maxSize) { + first = 0; + } + numElements--; + return r; + } + + /** + * Gets the maximum size of the buffer. + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Returns true if the buffer is full, that is, whether the number of elements in the buffer equals the + * buffer size. + */ + public boolean isFull() { + return numElements == maxSize; + } + + /** + * Gets the number of elements in the buffer. This number is guaranteed to be in the range 0 to maxSize + * (inclusive). + */ + public int length() { + return numElements; + } + + int min(final int a, final int b) { + return a < b ? a : b; + } + + /** + * Puts a {@link LoggingEvent} in the buffer. If the buffer is full then the event is silently dropped. It is the + * caller's responsability to make sure that the buffer has free space. + */ + public void put(final LoggingEvent o) { + if (numElements != maxSize) { + buf[next] = o; + if (++next == maxSize) { + next = 0; + } + numElements++; + } + } + + /** + * Resizes the buffer to a new size. If the new size is smaller than the old size events might be lost. + * + * @since 1.1 + */ + synchronized public void resize(final int newSize) { + if (newSize == maxSize) { + return; + } + + final LoggingEvent[] tmp = new LoggingEvent[newSize]; + + // we should not copy beyond the buf array + int len1 = maxSize - first; + + // we should not copy beyond the tmp array + len1 = min(len1, newSize); + + // er.. how much do we actually need to copy? + // We should not copy more than the actual number of elements. + len1 = min(len1, numElements); + + // Copy from buf starting a first, to tmp, starting at position 0, len1 elements. + System.arraycopy(buf, first, tmp, 0, len1); + + // Are there any uncopied elements and is there still space in the new array? + int len2 = 0; + if ((len1 < numElements) && (len1 < newSize)) { + len2 = numElements - len1; + len2 = min(len2, newSize - len1); + System.arraycopy(buf, 0, tmp, len1, len2); + } + + this.buf = tmp; + this.maxSize = newSize; + this.first = 0; + this.numElements = len1 + len2; + this.next = this.numElements; + if (this.next == this.maxSize) { + this.next = 0; + } + } + + /** + * Returns true if there is just one element in the buffer. In other words, if there were no elements + * before the last {@link #put} operation completed. + */ + public boolean wasEmpty() { + return numElements == 1; + } + + /** + * Returns true if the number of elements in the buffer plus 1 equals the maximum buffer size, returns + * false otherwise. + */ + public boolean wasFull() { + return numElements + 1 == maxSize; + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CountingQuietWriter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CountingQuietWriter.java new file mode 100644 index 00000000000..49b90584ef8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CountingQuietWriter.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.io.IOException; +import java.io.Writer; + +import org.apache.log4j.spi.ErrorCode; +import org.apache.log4j.spi.ErrorHandler; + +/** + * Counts the number of bytes written. + * + * @since 0.8.1 + */ +public class CountingQuietWriter extends QuietWriter { + + protected long count; + + public CountingQuietWriter(final Writer writer, final ErrorHandler eh) { + super(writer, eh); + } + + public long getCount() { + return count; + } + + public void setCount(final long count) { + this.count = count; + } + + @Override + public void write(final String string) { + try { + out.write(string); + count += string.length(); + } catch (final IOException e) { + errorHandler.error("Write failure.", e, ErrorCode.WRITE_FAILURE); + } + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CyclicBuffer.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CyclicBuffer.java new file mode 100644 index 00000000000..62918ea01ca --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/CyclicBuffer.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import org.apache.log4j.spi.LoggingEvent; + +/** + * Holds {@link LoggingEvent LoggingEvents} for immediate or differed display. + * + *

+ * This buffer gives read access to any element in the buffer not just the first or last element. + *

+ * + * @since 0.9.0 + */ +public class CyclicBuffer { + + LoggingEvent[] ea; + int first; + int last; + int numElems; + int maxSize; + + /** + * Constructs a new instance of at most maxSize events. + * + * The maxSize argument must a positive integer. + * + * @param maxSize The maximum number of elements in the buffer. + */ + public CyclicBuffer(final int maxSize) throws IllegalArgumentException { + if (maxSize < 1) { + throw new IllegalArgumentException("The maxSize argument (" + maxSize + ") is not a positive integer."); + } + this.maxSize = maxSize; + ea = new LoggingEvent[maxSize]; + first = 0; + last = 0; + numElems = 0; + } + + /** + * Adds an event as the last event in the buffer. + */ + public void add(final LoggingEvent event) { + ea[last] = event; + if (++last == maxSize) { + last = 0; + } + + if (numElems < maxSize) { + numElems++; + } else if (++first == maxSize) { + first = 0; + } + } + + /** + * Gets the oldest (first) element in the buffer. The oldest element is removed from the buffer. + */ + public LoggingEvent get() { + LoggingEvent r = null; + if (numElems > 0) { + numElems--; + r = ea[first]; + ea[first] = null; + if (++first == maxSize) { + first = 0; + } + } + return r; + } + + /** + * Gets the ith oldest event currently in the buffer. If i is outside the range 0 to the number of + * elements currently in the buffer, then null is returned. + */ + public LoggingEvent get(final int i) { + if (i < 0 || i >= numElems) { + return null; + } + + return ea[(first + i) % maxSize]; + } + + public int getMaxSize() { + return maxSize; + } + + /** + * Gets the number of elements in the buffer. This number is guaranteed to be in the range 0 to maxSize + * (inclusive). + */ + public int length() { + return numElems; + } + + /** + * Resizes the cyclic buffer to newSize. + * + * @throws IllegalArgumentException if newSize is negative. + */ + public void resize(final int newSize) { + if (newSize < 0) { + throw new IllegalArgumentException("Negative array size [" + newSize + "] not allowed."); + } + if (newSize == numElems) { + return; // nothing to do + } + + final LoggingEvent[] temp = new LoggingEvent[newSize]; + + final int loopLen = newSize < numElems ? newSize : numElems; + + for (int i = 0; i < loopLen; i++) { + temp[i] = ea[first]; + ea[first] = null; + if (++first == numElems) { + first = 0; + } + } + ea = temp; + first = 0; + numElems = loopLen; + maxSize = newSize; + if (loopLen == newSize) { + last = 0; + } else { + last = loopLen; + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/DateLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/DateLayout.java new file mode 100644 index 00000000000..2893773aae4 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/DateLayout.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.apache.log4j.Layout; +import org.apache.log4j.spi.LoggingEvent; + +/** + * This abstract layout takes care of all the date related options and formatting work. + */ +abstract public class DateLayout extends Layout { + + /** + * String constant designating no time information. Current value of this constant is NULL. + * + */ + public final static String NULL_DATE_FORMAT = "NULL"; + + /** + * String constant designating relative time. Current value of this constant is RELATIVE. + */ + public final static String RELATIVE_TIME_DATE_FORMAT = "RELATIVE"; + + /** + * @deprecated Options are now handled using the JavaBeans paradigm. This constant is not longer needed and will be + * removed in the near term. + */ + @Deprecated + final static public String DATE_FORMAT_OPTION = "DateFormat"; + + /** + * @deprecated Options are now handled using the JavaBeans paradigm. This constant is not longer needed and will be + * removed in the near term. + */ + @Deprecated + final static public String TIMEZONE_OPTION = "TimeZone"; + + protected FieldPosition pos = new FieldPosition(0); + + private String timeZoneID; + private String dateFormatOption; + + protected DateFormat dateFormat; + protected Date date = new Date(); + + public void activateOptions() { + setDateFormat(dateFormatOption); + if (timeZoneID != null && dateFormat != null) { + dateFormat.setTimeZone(TimeZone.getTimeZone(timeZoneID)); + } + } + + public void dateFormat(final StringBuffer buf, final LoggingEvent event) { + if (dateFormat != null) { + date.setTime(event.timeStamp); + dateFormat.format(date, buf, this.pos); + buf.append(' '); + } + } + + /** + * Returns value of the DateFormat option. + */ + public String getDateFormat() { + return dateFormatOption; + } + + /** + * @deprecated Use the setter method for the option directly instead of the generic setOption method. + */ + @Deprecated + public String[] getOptionStrings() { + return new String[] {DATE_FORMAT_OPTION, TIMEZONE_OPTION}; + } + + /** + * Returns value of the TimeZone option. + */ + public String getTimeZone() { + return timeZoneID; + } + + /** + * Sets the {@link DateFormat} used to format time and date in the zone determined by timeZone. + */ + public void setDateFormat(final DateFormat dateFormat, final TimeZone timeZone) { + this.dateFormat = dateFormat; + this.dateFormat.setTimeZone(timeZone); + } + + /** + * The value of the DateFormat option should be either an argument to the constructor of {@link SimpleDateFormat} + * or one of the srings "NULL", "RELATIVE", "ABSOLUTE", "DATE" or "ISO8601. + */ + public void setDateFormat(final String dateFormat) { + if (dateFormat != null) { + dateFormatOption = dateFormat; + } + setDateFormat(dateFormatOption, TimeZone.getDefault()); + } + + /** + * Sets the DateFormat used to format date and time in the time zone determined by timeZone parameter. The + * {@link DateFormat} used will depend on the dateFormatType. + * + *

+ * The recognized types are {@link #NULL_DATE_FORMAT}, {@link #RELATIVE_TIME_DATE_FORMAT} + * {@link AbsoluteTimeDateFormat#ABS_TIME_DATE_FORMAT}, {@link AbsoluteTimeDateFormat#DATE_AND_TIME_DATE_FORMAT} and + * {@link AbsoluteTimeDateFormat#ISO8601_DATE_FORMAT}. If the dateFormatType is not one of the above, then + * the argument is assumed to be a date pattern for {@link SimpleDateFormat}. + */ + public void setDateFormat(final String dateFormatType, final TimeZone timeZone) { + if (dateFormatType == null) { + this.dateFormat = null; + return; + } + if (dateFormatType.equalsIgnoreCase(NULL_DATE_FORMAT)) { + this.dateFormat = null; + } else if (dateFormatType.equalsIgnoreCase(RELATIVE_TIME_DATE_FORMAT)) { + this.dateFormat = new RelativeTimeDateFormat(); + } else if (dateFormatType.equalsIgnoreCase(AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) { + this.dateFormat = new AbsoluteTimeDateFormat(timeZone); + } else if (dateFormatType.equalsIgnoreCase(AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) { + this.dateFormat = new DateTimeDateFormat(timeZone); + } else if (dateFormatType.equalsIgnoreCase(AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) { + this.dateFormat = new ISO8601DateFormat(timeZone); + } else { + this.dateFormat = new SimpleDateFormat(dateFormatType); + this.dateFormat.setTimeZone(timeZone); + } + } + + /** + * @deprecated Use the setter method for the option directly instead of the generic setOption method. + */ + @Deprecated + public void setOption(final String option, final String value) { + if (option.equalsIgnoreCase(DATE_FORMAT_OPTION)) { + dateFormatOption = value.toUpperCase(); + } else if (option.equalsIgnoreCase(TIMEZONE_OPTION)) { + timeZoneID = value; + } + } + + /** + * The TimeZoneID option is a time zone ID string in the format expected by the {@link TimeZone#getTimeZone} + * method. + */ + public void setTimeZone(final String timeZone) { + this.timeZoneID = timeZone; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/DateTimeDateFormat.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/DateTimeDateFormat.java new file mode 100644 index 00000000000..b1a96a30d11 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/DateTimeDateFormat.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.text.DateFormatSymbols; +import java.text.FieldPosition; +import java.text.ParsePosition; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * Formats a {@link Date} in the format "dd MMM yyyy HH:mm:ss,SSS" for example, "06 Nov 1994 15:49:37,459". + * + * @since 0.7.5 + */ +public class DateTimeDateFormat extends AbsoluteTimeDateFormat { + private static final long serialVersionUID = 5547637772208514971L; + + String[] shortMonths; + + public DateTimeDateFormat() { + super(); + shortMonths = new DateFormatSymbols().getShortMonths(); + } + + public DateTimeDateFormat(final TimeZone timeZone) { + this(); + setCalendar(Calendar.getInstance(timeZone)); + } + + /** + * Appends to sbuf the date in the format "dd MMM yyyy HH:mm:ss,SSS" for example, "06 Nov 1994 + * 08:49:37,459". + * + * @param sbuf the string buffer to write to + */ + @Override + public StringBuffer format(final Date date, final StringBuffer sbuf, final FieldPosition fieldPosition) { + + calendar.setTime(date); + + final int day = calendar.get(Calendar.DAY_OF_MONTH); + if (day < 10) { + sbuf.append('0'); + } + sbuf.append(day); + sbuf.append(' '); + sbuf.append(shortMonths[calendar.get(Calendar.MONTH)]); + sbuf.append(' '); + + final int year = calendar.get(Calendar.YEAR); + sbuf.append(year); + sbuf.append(' '); + + return super.format(date, sbuf, fieldPosition); + } + + /** + * Always returns null. + */ + @Override + public Date parse(final String s, final ParsePosition pos) { + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FileWatchdog.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FileWatchdog.java new file mode 100644 index 00000000000..b601416db45 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FileWatchdog.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Contributors: Mathias Bogaert + +package org.apache.log4j.helpers; + +import java.io.File; + +/** + * Checks every now and then that a certain file has not changed. If it has, then call the {@link #doOnChange} method. + * + * @since version 0.9.1 + */ +public abstract class FileWatchdog extends Thread { + + /** + * The default delay between every file modification check, set to 60 seconds. + */ + static final public long DEFAULT_DELAY = 60_000; + + /** + * The name of the file to observe for changes. + */ + protected String filename; + + /** + * The delay to observe between every check. By default set {@link #DEFAULT_DELAY}. + */ + protected long delay = DEFAULT_DELAY; + + File file; + long lastModified; + boolean warnedAlready; + boolean interrupted; + + protected FileWatchdog(final String fileName) { + super("FileWatchdog"); + this.filename = fileName; + this.file = new File(fileName); + setDaemon(true); + checkAndConfigure(); + } + + protected void checkAndConfigure() { + boolean fileExists; + try { + fileExists = file.exists(); + } catch (final SecurityException e) { + LogLog.warn("Was not allowed to read check file existance, file:[" + filename + "]."); + interrupted = true; // there is no point in continuing + return; + } + + if (fileExists) { + final long fileLastMod = file.lastModified(); // this can also throw a SecurityException + if (fileLastMod > lastModified) { // however, if we reached this point this + lastModified = fileLastMod; // is very unlikely. + doOnChange(); + warnedAlready = false; + } + } else { + if (!warnedAlready) { + LogLog.debug("[" + filename + "] does not exist."); + warnedAlready = true; + } + } + } + + abstract protected void doOnChange(); + + @Override + public void run() { + while (!interrupted) { + try { + Thread.sleep(delay); + } catch (final InterruptedException e) { + // no interruption expected + } + checkAndConfigure(); + } + } + + /** + * Sets the delay in milliseconds to observe between each check of the file changes. + * + * @param delayMillis the delay in milliseconds + */ + public void setDelay(final long delayMillis) { + this.delay = delayMillis; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FormattingInfo.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FormattingInfo.java new file mode 100644 index 00000000000..7b6266030a5 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/FormattingInfo.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +/** + * FormattingInfo instances contain the information obtained when parsing formatting modifiers in conversion modifiers. + * + * @since 0.8.2 + */ +public class FormattingInfo { + int min = -1; + int max = 0x7FFFFFFF; + boolean leftAlign = false; + + void dump() { + LogLog.debug("min=" + min + ", max=" + max + ", leftAlign=" + leftAlign); + } + + void reset() { + min = -1; + max = 0x7FFFFFFF; + leftAlign = false; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/ISO8601DateFormat.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/ISO8601DateFormat.java new file mode 100644 index 00000000000..78f97942347 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/ISO8601DateFormat.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.text.FieldPosition; +import java.text.ParsePosition; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * Formats a {@link Date} in the format "yyyy-MM-dd HH:mm:ss,SSS" for example "1999-11-27 15:49:37,459". + * + *

+ * Refer to the summary of the International Standard Date and Time + * Notation for more information on this format. + *

+ * + * @since 0.7.5 + */ +public class ISO8601DateFormat extends AbsoluteTimeDateFormat { + + private static final long serialVersionUID = -759840745298755296L; + + static private long lastTime; + + static private char[] lastTimeString = new char[20]; + + public ISO8601DateFormat() { + } + + public ISO8601DateFormat(final TimeZone timeZone) { + super(timeZone); + } + + /** + * Appends a date in the format "YYYY-mm-dd HH:mm:ss,SSS" to sbuf. For example: "1999-11-27 15:49:37,459". + * + * @param sbuf the StringBuffer to write to + */ + @Override + public StringBuffer format(final Date date, final StringBuffer sbuf, final FieldPosition fieldPosition) { + + final long now = date.getTime(); + final int millis = (int) (now % 1000); + + if ((now - millis) != lastTime || lastTimeString[0] == 0) { + // We reach this point at most once per second + // across all threads instead of each time format() + // is called. This saves considerable CPU time. + + calendar.setTime(date); + + final int start = sbuf.length(); + + final int year = calendar.get(Calendar.YEAR); + sbuf.append(year); + + String month; + switch (calendar.get(Calendar.MONTH)) { + case Calendar.JANUARY: + month = "-01-"; + break; + case Calendar.FEBRUARY: + month = "-02-"; + break; + case Calendar.MARCH: + month = "-03-"; + break; + case Calendar.APRIL: + month = "-04-"; + break; + case Calendar.MAY: + month = "-05-"; + break; + case Calendar.JUNE: + month = "-06-"; + break; + case Calendar.JULY: + month = "-07-"; + break; + case Calendar.AUGUST: + month = "-08-"; + break; + case Calendar.SEPTEMBER: + month = "-09-"; + break; + case Calendar.OCTOBER: + month = "-10-"; + break; + case Calendar.NOVEMBER: + month = "-11-"; + break; + case Calendar.DECEMBER: + month = "-12-"; + break; + default: + month = "-NA-"; + break; + } + sbuf.append(month); + + final int day = calendar.get(Calendar.DAY_OF_MONTH); + if (day < 10) { + sbuf.append('0'); + } + sbuf.append(day); + + sbuf.append(' '); + + final int hour = calendar.get(Calendar.HOUR_OF_DAY); + if (hour < 10) { + sbuf.append('0'); + } + sbuf.append(hour); + sbuf.append(':'); + + final int mins = calendar.get(Calendar.MINUTE); + if (mins < 10) { + sbuf.append('0'); + } + sbuf.append(mins); + sbuf.append(':'); + + final int secs = calendar.get(Calendar.SECOND); + if (secs < 10) { + sbuf.append('0'); + } + sbuf.append(secs); + + sbuf.append(','); + + // store the time string for next time to avoid recomputation + sbuf.getChars(start, sbuf.length(), lastTimeString, 0); + lastTime = now - millis; + } else { + sbuf.append(lastTimeString); + } + + if (millis < 100) { + sbuf.append('0'); + } + if (millis < 10) { + sbuf.append('0'); + } + + sbuf.append(millis); + return sbuf; + } + + /** + * Always returns null. + */ + @Override + public Date parse(final String s, final ParsePosition pos) { + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/Loader.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/Loader.java new file mode 100644 index 00000000000..cf5ca22eb44 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/Loader.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.net.URL; + +/** + * Loads resources (or images) from various sources. + */ +public class Loader { + + static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous."; + + static private boolean ignoreTCL; + + static { + final String ignoreTCLProp = OptionConverter.getSystemProperty("log4j.ignoreTCL", null); + if (ignoreTCLProp != null) { + ignoreTCL = OptionConverter.toBoolean(ignoreTCLProp, true); + } + } + + /** + * This method will search for resource in different places. The search order is as follows: + *
    + *

    + *

  1. Search for resource using the thread context class loader under Java2. If that fails, search for + * resource using the class loader that loaded this class (Loader). + *

    + *

    + *

  2. Try one last time with ClassLoader.getSystemResource(resource). + *

    + *
+ */ + static public URL getResource(final String resource) { + ClassLoader classLoader = null; + URL url = null; + + try { + if (!ignoreTCL) { + classLoader = getTCL(); + if (classLoader != null) { + LogLog.debug("Trying to find [" + resource + "] using context classloader " + classLoader + "."); + url = classLoader.getResource(resource); + if (url != null) { + return url; + } + } + } + + // We could not find resource. Ler us now try with the + // ClassLoader that loaded this class. + classLoader = Loader.class.getClassLoader(); + if (classLoader != null) { + LogLog.debug("Trying to find [" + resource + "] using " + classLoader + " class loader."); + url = classLoader.getResource(resource); + if (url != null) { + return url; + } + } + } catch (final Throwable t) { + // can't be InterruptedException or InterruptedIOException + // since not declared, must be error or RuntimeError. + LogLog.warn(TSTR, t); + } + + // Last ditch attempt: get the resource from the class path. It + // may be the case that clazz was loaded by the Extentsion class + // loader which the parent of the system class loader. Hence the + // code below. + LogLog.debug("Trying to find [" + resource + "] using ClassLoader.getSystemResource()."); + return ClassLoader.getSystemResource(resource); + } + + /** + * Gets a resource by delegating to getResource(String). + * + * @param resource resource name + * @param clazz class, ignored. + * @return URL to resource or null. + * @deprecated as of 1.2. + */ + @Deprecated + public static URL getResource(final String resource, final Class clazz) { + return getResource(resource); + } + + /** + * Shorthand for {@code Thread.currentThread().getContextClassLoader()}. + */ + private static ClassLoader getTCL() { + return Thread.currentThread().getContextClassLoader(); + } + + /** + * Always returns false since Java 1.x support is long gone. + * + * @return Always false. + */ + public static boolean isJava1() { + return false; + } + + /** + * Loads the specified class using the Thread contextClassLoader, if that fails try + * Class.forname. + * + * @param clazz The class to load. + * @return The Class. + * @throws ClassNotFoundException Never thrown, declared for compatibility. + */ + static public Class loadClass(final String clazz) throws ClassNotFoundException { + // Just call Class.forName(clazz) if we are instructed to ignore the TCL. + if (ignoreTCL) { + return Class.forName(clazz); + } + try { + return getTCL().loadClass(clazz); + } catch (final Throwable t) { + // ignore + } + return Class.forName(clazz); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/LogLog.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/LogLog.java new file mode 100644 index 00000000000..d40773e1082 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/LogLog.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j.helpers; + +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Logs statements from within Log4j. + * + *

+ * Log4j components cannot make Log4j logging calls. However, it is sometimes useful for the user to learn about what + * Log4j is doing. You can enable Log4j internal logging by defining the log4j.configDebug variable. + *

+ *

+ * All Log4j internal debug calls go to System.out where as internal error messages are sent to + * System.err. All internal messages are prepended with the string "log4j: ". + *

+ * + * @since 0.8.2 + */ +public class LogLog { + + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + + /** + * Makes Log4j print log4j-internal debug statements to System.out. + * + *

+ * The value of this string is {@value #DEBUG_KEY} + *

+ *

+ * Note that the search for all option names is case sensitive. + *

+ */ + public static final String DEBUG_KEY = "log4j.debug"; + + /** + * Makes Log4j components print log4j-internal debug statements to System.out. + * + *

+ * The value of this string is {@value #CONFIG_DEBUG_KEY}. + *

+ *

+ * Note that the search for all option names is case sensitive. + *

+ * + * @deprecated Use {@link #DEBUG_KEY} instead. + */ + @Deprecated + public static final String CONFIG_DEBUG_KEY = "log4j.configDebug"; + + /** + * Debug enabled Enable or disable. + */ + protected static boolean debugEnabled = false; + + /** + * In quietMode not even errors generate any output. + */ + private static boolean quietMode = false; + + static { + String key = OptionConverter.getSystemProperty(DEBUG_KEY, null); + if (key == null) { + key = OptionConverter.getSystemProperty(CONFIG_DEBUG_KEY, null); + } + if (key != null) { + debugEnabled = OptionConverter.toBoolean(key, true); + } + } + + /** + * Logs Log4j internal debug statements. + * + * @param message the message object to log. + */ + public static void debug(final String message) { + if (debugEnabled && !quietMode) { + LOGGER.debug(message); + } + } + + /** + * Logs Log4j internal debug statements. + * + * @param message the message object to log. + * @param throwable the {@code Throwable} to log, including its stack trace. + */ + public static void debug(final String message, final Throwable throwable) { + if (debugEnabled && !quietMode) { + LOGGER.debug(message, throwable); + } + } + + /** + * Logs Log4j internal error statements. + * + * @param message the message object to log. + */ + public static void error(final String message) { + if (!quietMode) { + LOGGER.error(message); + } + } + + /** + * Logs Log4j internal error statements. + * + * @param message the message object to log. + * @param throwable the {@code Throwable} to log, including its stack trace. + */ + public static void error(final String message, final Throwable throwable) { + if (!quietMode) { + LOGGER.error(message, throwable); + } + } + + /** + * Enables and disables Log4j internal logging. + * + * @param enabled Enable or disable. + */ + static public void setInternalDebugging(final boolean enabled) { + debugEnabled = enabled; + } + + /** + * In quite mode no LogLog generates strictly no output, not even for errors. + * + * @param quietMode A true for not + */ + public static void setQuietMode(final boolean quietMode) { + LogLog.quietMode = quietMode; + } + + /** + * Logs Log4j internal warning statements. + * + * @param message the message object to log. + */ + public static void warn(final String message) { + if (!quietMode) { + LOGGER.warn(message); + } + } + + /** + * Logs Log4j internal warnings. + * + * @param message the message object to log. + * @param throwable the {@code Throwable} to log, including its stack trace. + */ + public static void warn(final String message, final Throwable throwable) { + if (!quietMode) { + LOGGER.warn(message, throwable); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java new file mode 100644 index 00000000000..ba6e4596bc7 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java @@ -0,0 +1,690 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.helpers; + +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.log4j.Level; +import org.apache.log4j.Priority; +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.spi.Configurator; +import org.apache.log4j.spi.LoggerRepository; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.spi.StandardLevel; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.Strings; + +/** + * A convenience class to convert property values to specific types. + */ +public class OptionConverter { + + private static class CharMap { + final char key; + final char replacement; + + public CharMap(final char key, final char replacement) { + this.key = key; + this.replacement = replacement; + } + } + static String DELIM_START = "${"; + static char DELIM_STOP = '}'; + static int DELIM_START_LEN = 2; + static int DELIM_STOP_LEN = 1; + private static final Logger LOGGER = StatusLogger.getLogger(); + /** + * A Log4j 1.x level above or equal to this value is considered as OFF. + */ + static final int MAX_CUTOFF_LEVEL = Priority.FATAL_INT + + 100 * (StandardLevel.FATAL.intLevel() - StandardLevel.OFF.intLevel() - 1) + 1; + /** + * A Log4j 1.x level below or equal to this value is considered as ALL. + * + * Log4j 2.x ALL to TRACE interval is shorter. This is {@link Priority#ALL_INT} + * plus the difference. + */ + static final int MIN_CUTOFF_LEVEL = Priority.ALL_INT + Level.TRACE_INT + - (Priority.ALL_INT + StandardLevel.ALL.intLevel()) + StandardLevel.TRACE.intLevel(); + /** + * Cache of currently known levels. + */ + static final ConcurrentMap LEVELS = new ConcurrentHashMap<>(); + /** + * Postfix for all Log4j 2.x level names. + */ + private static final String LOG4J2_LEVEL_CLASS = org.apache.logging.log4j.Level.class.getName(); + + private static final CharMap[] charMap = new CharMap[] { + new CharMap('n', '\n'), + new CharMap('r', '\r'), + new CharMap('t', '\t'), + new CharMap('f', '\f'), + new CharMap('\b', '\b'), + new CharMap('\"', '\"'), + new CharMap('\'', '\''), + new CharMap('\\', '\\') + }; + + public static String[] concatanateArrays(final String[] l, final String[] r) { + final int len = l.length + r.length; + final String[] a = new String[len]; + + System.arraycopy(l, 0, a, 0, l.length); + System.arraycopy(r, 0, a, l.length, r.length); + + return a; + } + + static int toLog4j2Level(final int v1Level) { + // I don't believe anyone uses values much bigger than FATAL + if (v1Level >= MAX_CUTOFF_LEVEL) { + return StandardLevel.OFF.intLevel(); + } + // Linear transformation up to debug: CUTOFF_LEVEL -> OFF, DEBUG -> DEBUG + if (v1Level > Priority.DEBUG_INT) { + final int offset = Math.round((v1Level - Priority.DEBUG_INT) / 100.0f); + return StandardLevel.DEBUG.intLevel() - offset; + } + // Steeper linear transformation + if (v1Level > Level.TRACE_INT) { + final int offset = Math.round((v1Level - Level.TRACE_INT) / 50.0f); + return StandardLevel.TRACE.intLevel() - offset; + } + if (v1Level > MIN_CUTOFF_LEVEL) { + final int offset = Level.TRACE_INT - v1Level; + return StandardLevel.TRACE.intLevel() + offset; + } + return StandardLevel.ALL.intLevel(); + } + + static int toLog4j1Level(int v2Level) { + if (v2Level == StandardLevel.ALL.intLevel()) { + return Priority.ALL_INT; + } + if (v2Level > StandardLevel.TRACE.intLevel()) { + return MIN_CUTOFF_LEVEL + (StandardLevel.ALL.intLevel() - v2Level); + } + // Inflating by 50 + if (v2Level > StandardLevel.DEBUG.intLevel()) { + return Level.TRACE_INT + 50 * (StandardLevel.TRACE.intLevel() - v2Level); + } + // Inflating by 100 + if (v2Level > StandardLevel.OFF.intLevel()) { + return Priority.DEBUG_INT + 100 * (StandardLevel.DEBUG.intLevel() - v2Level); + } + return Priority.OFF_INT; + } + + static int toSyslogLevel(int v2Level) { + if (v2Level <= StandardLevel.FATAL.intLevel()) { + return 0; + } + if (v2Level <= StandardLevel.ERROR.intLevel()) { + return 3 - (3 * (StandardLevel.ERROR.intLevel() - v2Level)) + / (StandardLevel.ERROR.intLevel() - StandardLevel.FATAL.intLevel()); + } + if (v2Level <= StandardLevel.WARN.intLevel()) { + return 4; + } + if (v2Level <= StandardLevel.INFO.intLevel()) { + return 6 - (2 * (StandardLevel.INFO.intLevel() - v2Level)) + / (StandardLevel.INFO.intLevel() - StandardLevel.WARN.intLevel()); + } + return 7; + } + + public static org.apache.logging.log4j.Level createLevel(final Priority level) { + final String name = level.toString().toUpperCase() + "#" + level.getClass().getName(); + return org.apache.logging.log4j.Level.forName(name, toLog4j2Level(level.toInt())); + } + + public static org.apache.logging.log4j.Level convertLevel(final Priority level) { + return level != null ? level.getVersion2Level() : org.apache.logging.log4j.Level.ERROR; + } + + /** + * @param level + * @return + */ + public static Level convertLevel(final org.apache.logging.log4j.Level level) { + // level is standard or was created by Log4j 1.x custom level + Level actualLevel = toLevel(level.name(), null); + // level was created by Log4j 2.x + if (actualLevel == null) { + actualLevel = toLevel(LOG4J2_LEVEL_CLASS, level.name(), null); + } + return actualLevel != null ? actualLevel : Level.ERROR; + } + + public static org.apache.logging.log4j.Level convertLevel(final String level, + final org.apache.logging.log4j.Level defaultLevel) { + final Level actualLevel = toLevel(level, null); + return actualLevel != null ? actualLevel.getVersion2Level() : defaultLevel; + } + + public static String convertSpecialChars(final String s) { + char c; + final int len = s.length(); + final StringBuilder sbuf = new StringBuilder(len); + + int i = 0; + while (i < len) { + c = s.charAt(i++); + if (c == '\\') { + c = s.charAt(i++); + for (final CharMap entry : charMap) { + if (entry.key == c) { + c = entry.replacement; + } + } + } + sbuf.append(c); + } + return sbuf.toString(); + } + + /** + * Find the value corresponding to key in + * props. Then perform variable substitution on the + * found value. + * @param key The key used to locate the substitution string. + * @param props The properties to use in the substitution. + * @return The substituted string. + */ + public static String findAndSubst(final String key, final Properties props) { + final String value = props.getProperty(key); + if (value == null) { + return null; + } + + try { + return substVars(value, props); + } catch (final IllegalArgumentException e) { + LOGGER.error("Bad option value [{}].", value, e); + return value; + } + } + + + /** + * Very similar to System.getProperty except + * that the {@link SecurityException} is hidden. + * + * @param key The key to search for. + * @param def The default value to return. + * @return the string value of the system property, or the default + * value if there is no property with that key. + * @since 1.1 + */ + public static String getSystemProperty(final String key, final String def) { + try { + return System.getProperty(key, def); + } catch (final Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx + LOGGER.debug("Was not allowed to read system property \"{}\".", key); + return def; + } + } + + /** + * Instantiate an object given a class name. Check that the + * className is a subclass of + * superClass. If that test fails or the object could + * not be instantiated, then defaultValue is returned. + * + * @param className The fully qualified class name of the object to instantiate. + * @param superClass The class to which the new object should belong. + * @param defaultValue The object to return in case of non-fulfillment + * @return The created object. + */ + public static Object instantiateByClassName(final String className, final Class superClass, + final Object defaultValue) { + if (className != null) { + try { + final Object obj = LoaderUtil.newInstanceOf(className); + if (!superClass.isAssignableFrom(obj.getClass())) { + LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable", className, + superClass.getName()); + return defaultValue; + } + return obj; + } catch (final ReflectiveOperationException e) { + LOGGER.error("Could not instantiate class [" + className + "].", e); + } + } + return defaultValue; + } + + public static Object instantiateByKey(final Properties props, final String key, final Class superClass, final Object defaultValue) { + + // Get the value of the property in string form + final String className = findAndSubst(key, props); + if (className == null) { + LogLog.error("Could not find value for key " + key); + return defaultValue; + } + // Trim className to avoid trailing spaces that cause problems. + return OptionConverter.instantiateByClassName(className.trim(), superClass, defaultValue); + } + + /** + * Configure log4j given an {@link InputStream}. + *

+ * The InputStream will be interpreted by a new instance of a log4j configurator. + *

+ *

+ * All configurations steps are taken on the hierarchy passed as a parameter. + *

+ * + * @param inputStream The configuration input stream. + * @param clazz The class name, of the log4j configurator which will parse the inputStream. This must be a + * subclass of {@link Configurator}, or null. If this value is null then a default configurator of + * {@link PropertyConfigurator} is used. + * @param hierarchy The {@link LoggerRepository} to act on. + * @since 1.2.17 + */ + static public void selectAndConfigure(final InputStream inputStream, final String clazz, final LoggerRepository hierarchy) { + Configurator configurator = null; + + if (clazz != null) { + LOGGER.debug("Preferred configurator class: " + clazz); + configurator = (Configurator) instantiateByClassName(clazz, Configurator.class, null); + if (configurator == null) { + LOGGER.error("Could not instantiate configurator [" + clazz + "]."); + return; + } + } else { + configurator = new PropertyConfigurator(); + } + + configurator.doConfigure(inputStream, hierarchy); + } + + /** + * Configure log4j given a URL. + *

+ * The url must point to a file or resource which will be interpreted by a new instance of a log4j configurator. + *

+ *

+ * All configurations steps are taken on the hierarchy passed as a parameter. + *

+ * + * @param url The location of the configuration file or resource. + * @param clazz The classname, of the log4j configurator which will parse the file or resource at url. This + * must be a subclass of {@link Configurator}, or null. If this value is null then a default configurator of + * {@link PropertyConfigurator} is used, unless the filename pointed to by url ends in '.xml', in + * which case {@link org.apache.log4j.xml.DOMConfigurator} is used. + * @param hierarchy The {@link LoggerRepository} to act on. + * + * @since 1.1.4 + */ + static public void selectAndConfigure(final URL url, String clazz, final LoggerRepository hierarchy) { + Configurator configurator = null; + final String filename = url.getFile(); + + if (clazz == null && filename != null && filename.endsWith(".xml")) { + clazz = "org.apache.log4j.xml.DOMConfigurator"; + } + + if (clazz != null) { + LOGGER.debug("Preferred configurator class: " + clazz); + configurator = (Configurator) instantiateByClassName(clazz, Configurator.class, null); + if (configurator == null) { + LOGGER.error("Could not instantiate configurator [" + clazz + "]."); + return; + } + } else { + configurator = new PropertyConfigurator(); + } + + configurator.doConfigure(url, hierarchy); + } + + /** + * Perform variable substitution in string val from the + * values of keys found in the system propeties. + * + *

The variable substitution delimeters are ${ and }. + * + *

For example, if the System properties contains "key=value", then + * the call + *

+     * String s = OptionConverter.substituteVars("Value of key is ${key}.");
+     * 
+ *

+ * will set the variable s to "Value of key is value.". + * + *

If no value could be found for the specified key, then the + * props parameter is searched, if the value could not + * be found there, then substitution defaults to the empty string. + * + *

For example, if system propeties contains no value for the key + * "inexistentKey", then the call + * + *

+     * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
+     * 
+ * will set s to "Value of inexistentKey is []" + * + *

An {@link IllegalArgumentException} is thrown if + * val contains a start delimeter "${" which is not + * balanced by a stop delimeter "}".

+ * + *

Author Avy Sharell

+ * + * @param val The string on which variable substitution is performed. + * @param props The properties to use for the substitution. + * @return The substituted string. + * @throws IllegalArgumentException if val is malformed. + */ + public static String substVars(final String val, final Properties props) throws IllegalArgumentException { + return substVars(val, props, new ArrayList<>()); + } + + private static String substVars(final String val, final Properties props, final List keys) + throws IllegalArgumentException { + if (val == null) { + return null; + } + final StringBuilder sbuf = new StringBuilder(); + + int i = 0; + int j; + int k; + + while (true) { + j = val.indexOf(DELIM_START, i); + if (j == -1) { + // no more variables + if (i == 0) { // this is a simple string + return val; + } + // add the tail string which contails no variables and return the result. + sbuf.append(val.substring(i)); + return sbuf.toString(); + } + sbuf.append(val.substring(i, j)); + k = val.indexOf(DELIM_STOP, j); + if (k == -1) { + throw new IllegalArgumentException(Strings.dquote(val) + + " has no closing brace. Opening brace at position " + j + + '.'); + } + j += DELIM_START_LEN; + final String key = val.substring(j, k); + // first try in System properties + String replacement = PropertiesUtil.getProperties().getStringProperty(key, null); + // then try props parameter + if (replacement == null && props != null) { + replacement = props.getProperty(key); + } + + if (replacement != null) { + + // Do variable substitution on the replacement string + // such that we can solve "Hello ${x2}" as "Hello p1" + // the where the properties are + // x1=p1 + // x2=${x1} + if (!keys.contains(key)) { + final List usedKeys = new ArrayList<>(keys); + usedKeys.add(key); + final String recursiveReplacement = substVars(replacement, props, usedKeys); + sbuf.append(recursiveReplacement); + } else { + sbuf.append(replacement); + } + + } + i = k + DELIM_STOP_LEN; + } + } + + /** + * If value is "true", then true is + * returned. If value is "false", then + * true is returned. Otherwise, default is + * returned. + * + *

Case of value is unimportant. + * @param value The value to convert. + * @param dEfault The default value. + * @return the value of the result. + */ + public static boolean toBoolean(final String value, final boolean dEfault) { + if (value == null) { + return dEfault; + } + final String trimmedVal = value.trim(); + if ("true".equalsIgnoreCase(trimmedVal)) { + return true; + } + if ("false".equalsIgnoreCase(trimmedVal)) { + return false; + } + return dEfault; + } + + public static long toFileSize(final String value, final long defaultValue) { + if (value == null) { + return defaultValue; + } + + String s = value.trim().toUpperCase(); + long multiplier = 1; + int index; + + if ((index = s.indexOf("KB")) != -1) { + multiplier = 1024; + s = s.substring(0, index); + } else if ((index = s.indexOf("MB")) != -1) { + multiplier = 1024 * 1024; + s = s.substring(0, index); + } else if ((index = s.indexOf("GB")) != -1) { + multiplier = 1024 * 1024 * 1024; + s = s.substring(0, index); + } + if (s != null) { + try { + return Long.valueOf(s).longValue() * multiplier; + } catch (final NumberFormatException e) { + LogLog.error("[" + s + "] is not in proper int form."); + LogLog.error("[" + value + "] not in expected format.", e); + } + } + return defaultValue; + } + + public static int toInt(final String value, final int dEfault) { + if (value != null) { + final String s = value.trim(); + try { + return Integer.valueOf(s).intValue(); + } catch (final NumberFormatException e) { + LogLog.error("[" + s + "] is not in proper int form."); + e.printStackTrace(); + } + } + return dEfault; + } + + /** + * Converts a standard or custom priority level to a Level object. + *

+ * If value is of form "level#classname", then the specified class' + * toLevel method is called to process the specified level string; if no '#' + * character is present, then the default {@link org.apache.log4j.Level} class + * is used to process the level value. + *

+ * + *

+ * As a special case, if the value parameter is equal to the string + * "NULL", then the value null will be returned. + *

+ * + *

+ * As a Log4j 2.x extension, a {@code value} + * "level#org.apache.logging.log4j.Level" retrieves the corresponding custom + * Log4j 2.x level. + *

+ * + *

+ * If any error occurs while converting the value to a level, the + * defaultValue parameter, which may be null, is + * returned. + *

+ * + *

+ * Case of value is insignificant for the level, but is + * significant for the class name part, if present. + *

+ * + * @param value The value to convert. + * @param defaultValue The default value. + * @return the value of the result. + * + * @since 1.1 + */ + public static Level toLevel(String value, final Level defaultValue) { + if (value == null) { + return defaultValue; + } + + value = value.trim(); + final Level cached = LEVELS.get(value); + if (cached != null) { + return cached; + } + + final int hashIndex = value.indexOf('#'); + if (hashIndex == -1) { + if ("NULL".equalsIgnoreCase(value)) { + return null; + } + // no class name specified : use standard Level class + final Level standardLevel = Level.toLevel(value, defaultValue); + if (standardLevel != null && value.equals(standardLevel.toString())) { + LEVELS.putIfAbsent(value, standardLevel); + } + return standardLevel; + } + + final String clazz = value.substring(hashIndex + 1); + final String levelName = value.substring(0, hashIndex); + + final Level customLevel = toLevel(clazz, levelName, defaultValue); + if (customLevel != null && levelName.equals(customLevel.toString()) + && clazz.equals(customLevel.getClass().getName())) { + LEVELS.putIfAbsent(value, customLevel); + } + return customLevel; + } + + /** + * Converts a custom priority level to a Level object. + * + *

+ * If {@code clazz} has the special value "org.apache.logging.log4j.Level" a + * wrapper of the corresponding Log4j 2.x custom level object is returned. + *

+ * + * @param clazz a custom level class, + * @param levelName the name of the level, + * @param defaultValue the value to return in case an error occurs, + * @return the value of the result. + */ + public static Level toLevel(final String clazz, final String levelName, final Level defaultValue) { + + // This is degenerate case but you never know. + if ("NULL".equalsIgnoreCase(levelName)) { + return null; + } + + LOGGER.debug("toLevel" + ":class=[" + clazz + "]" + ":pri=[" + levelName + "]"); + + // Support for levels defined in Log4j2. + if (LOG4J2_LEVEL_CLASS.equals(clazz)) { + final org.apache.logging.log4j.Level v2Level = org.apache.logging.log4j.Level.getLevel(levelName.toUpperCase()); + if (v2Level != null) { + return new LevelWrapper(v2Level); + } + return defaultValue; + } + try { + final Class customLevel = LoaderUtil.loadClass(clazz); + + // get a ref to the specified class' static method + // toLevel(String, org.apache.log4j.Level) + final Class[] paramTypes = new Class[] { String.class, org.apache.log4j.Level.class }; + final java.lang.reflect.Method toLevelMethod = + customLevel.getMethod("toLevel", paramTypes); + + // now call the toLevel method, passing level string + default + final Object[] params = new Object[]{levelName, defaultValue}; + final Object o = toLevelMethod.invoke(null, params); + + return (Level) o; + } catch (final ClassNotFoundException e) { + LOGGER.warn("custom level class [" + clazz + "] not found."); + } catch (final NoSuchMethodException e) { + LOGGER.warn("custom level class [" + clazz + "]" + + " does not have a class function toLevel(String, Level)", e); + } catch (final java.lang.reflect.InvocationTargetException e) { + if (e.getTargetException() instanceof InterruptedException + || e.getTargetException() instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.warn("custom level class [" + clazz + "]" + + " could not be instantiated", e); + } catch (final ClassCastException e) { + LOGGER.warn("class [" + clazz + + "] is not a subclass of org.apache.log4j.Level", e); + } catch (final IllegalAccessException e) { + LOGGER.warn("class [" + clazz + + "] cannot be instantiated due to access restrictions", e); + } catch (final RuntimeException e) { + LOGGER.warn("class [" + clazz + "], level [" + levelName + + "] conversion failed.", e); + } + return defaultValue; + } + + /** + * OptionConverter is a static class. + */ + private OptionConverter() { + } + + private static class LevelWrapper extends Level { + + private static final long serialVersionUID = -7693936267612508528L; + + protected LevelWrapper(org.apache.logging.log4j.Level v2Level) { + super(toLog4j1Level(v2Level.intLevel()), v2Level.name(), toSyslogLevel(v2Level.intLevel()), v2Level); + } + + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/PatternConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/PatternConverter.java new file mode 100644 index 00000000000..2b46db7fee6 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/PatternConverter.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import org.apache.log4j.spi.LoggingEvent; + +/** + +

PatternConverter is an abtract class that provides the + formatting functionality that derived classes need. + +

Conversion specifiers in a conversion patterns are parsed to + individual PatternConverters. Each of which is responsible for + converting a logging event in a converter specific manner. + + @author James P. Cakalic + @author Ceki Gülcü + + @since 0.8.2 + */ +public abstract class PatternConverter { + public PatternConverter next; + int min = -1; + int max = 0x7FFFFFFF; + boolean leftAlign = false; + + protected + PatternConverter() { } + + protected + PatternConverter(FormattingInfo fi) { + min = fi.min; + max = fi.max; + leftAlign = fi.leftAlign; + } + + /** + Derived pattern converters must override this method in order to + convert conversion specifiers in the correct way. + */ + abstract + protected + String convert(LoggingEvent event); + + /** + A template method for formatting in a converter specific way. + */ + public + void format(StringBuffer sbuf, LoggingEvent e) { + String s = convert(e); + + if(s == null) { + if(0 < min) + spacePad(sbuf, min); + return; + } + + int len = s.length(); + + if(len > max) + sbuf.append(s.substring(len-max)); + else if(len < min) { + if(leftAlign) { + sbuf.append(s); + spacePad(sbuf, min-len); + } + else { + spacePad(sbuf, min-len); + sbuf.append(s); + } + } + else + sbuf.append(s); + } + + static String[] SPACES = {" ", " ", " ", " ", //1,2,4,8 spaces + " ", // 16 spaces + " " }; // 32 spaces + + /** + Fast space padding method. + */ + public + void spacePad(StringBuffer sbuf, int length) { + while(length >= 32) { + sbuf.append(SPACES[5]); + length -= 32; + } + + for(int i = 4; i >= 0; i--) { + if((length & (1< +// Igor E. Poteryaev +// Reinhard Deschler + +/** + * Most of the work of the {@link org.apache.log4j.PatternLayout} class is delegated to the PatternParser class. + * + *

+ * It is this class that parses conversion patterns and creates a chained list of {@link OptionConverter + * OptionConverters}. + * + * @author James P. Cakalic + * @author Ceki Gülcü + * @author Anders Kristensen + * + * @since 0.8.2 + */ +public class PatternParser { + + private static final char ESCAPE_CHAR = '%'; + + private static final int LITERAL_STATE = 0; + private static final int CONVERTER_STATE = 1; + private static final int DOT_STATE = 3; + private static final int MIN_STATE = 4; + private static final int MAX_STATE = 5; + + static final int FULL_LOCATION_CONVERTER = 1000; + static final int METHOD_LOCATION_CONVERTER = 1001; + static final int CLASS_LOCATION_CONVERTER = 1002; + static final int LINE_LOCATION_CONVERTER = 1003; + static final int FILE_LOCATION_CONVERTER = 1004; + + static final int RELATIVE_TIME_CONVERTER = 2000; + static final int THREAD_CONVERTER = 2001; + static final int LEVEL_CONVERTER = 2002; + static final int NDC_CONVERTER = 2003; + static final int MESSAGE_CONVERTER = 2004; + + int state; + protected StringBuffer currentLiteral = new StringBuffer(32); + protected int patternLength; + protected int i; + PatternConverter head; + PatternConverter tail; + protected FormattingInfo formattingInfo = new FormattingInfo(); + protected String pattern; + + public PatternParser(String pattern) { + this.pattern = pattern; + patternLength = pattern.length(); + state = LITERAL_STATE; + } + + private void addToList(PatternConverter pc) { + if (head == null) { + head = tail = pc; + } else { + tail.next = pc; + tail = pc; + } + } + + protected String extractOption() { + if ((i < patternLength) && (pattern.charAt(i) == '{')) { + int end = pattern.indexOf('}', i); + if (end > i) { + String r = pattern.substring(i + 1, end); + i = end + 1; + return r; + } + } + return null; + } + + /** + * The option is expected to be in decimal and positive. In case of error, zero is returned. + */ + protected int extractPrecisionOption() { + String opt = extractOption(); + int r = 0; + if (opt != null) { + try { + r = Integer.parseInt(opt); + if (r <= 0) { + LogLog.error("Precision option (" + opt + ") isn't a positive integer."); + r = 0; + } + } catch (NumberFormatException e) { + LogLog.error("Category option \"" + opt + "\" not a decimal integer.", e); + } + } + return r; + } + + public PatternConverter parse() { + char c; + i = 0; + while (i < patternLength) { + c = pattern.charAt(i++); + switch (state) { + case LITERAL_STATE: + // In literal state, the last char is always a literal. + if (i == patternLength) { + currentLiteral.append(c); + continue; + } + if (c == ESCAPE_CHAR) { + // peek at the next char. + switch (pattern.charAt(i)) { + case ESCAPE_CHAR: + currentLiteral.append(c); + i++; // move pointer + break; + case 'n': + currentLiteral.append(Layout.LINE_SEP); + i++; // move pointer + break; + default: + if (currentLiteral.length() != 0) { + addToList(new LiteralPatternConverter(currentLiteral.toString())); + // LogLog.debug("Parsed LITERAL converter: \"" + // +currentLiteral+"\"."); + } + currentLiteral.setLength(0); + currentLiteral.append(c); // append % + state = CONVERTER_STATE; + formattingInfo.reset(); + } + } else { + currentLiteral.append(c); + } + break; + case CONVERTER_STATE: + currentLiteral.append(c); + switch (c) { + case '-': + formattingInfo.leftAlign = true; + break; + case '.': + state = DOT_STATE; + break; + default: + if (c >= '0' && c <= '9') { + formattingInfo.min = c - '0'; + state = MIN_STATE; + } else + finalizeConverter(c); + } // switch + break; + case MIN_STATE: + currentLiteral.append(c); + if (c >= '0' && c <= '9') + formattingInfo.min = formattingInfo.min * 10 + (c - '0'); + else if (c == '.') + state = DOT_STATE; + else { + finalizeConverter(c); + } + break; + case DOT_STATE: + currentLiteral.append(c); + if (c >= '0' && c <= '9') { + formattingInfo.max = c - '0'; + state = MAX_STATE; + } else { + LogLog.error("Error occured in position " + i + ".\n Was expecting digit, instead got char \"" + c + "\"."); + state = LITERAL_STATE; + } + break; + case MAX_STATE: + currentLiteral.append(c); + if (c >= '0' && c <= '9') + formattingInfo.max = formattingInfo.max * 10 + (c - '0'); + else { + finalizeConverter(c); + state = LITERAL_STATE; + } + break; + } // switch + } // while + if (currentLiteral.length() != 0) { + addToList(new LiteralPatternConverter(currentLiteral.toString())); + // LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\"."); + } + return head; + } + + protected void finalizeConverter(char c) { + PatternConverter pc = null; + switch (c) { + case 'c': + pc = new CategoryPatternConverter(formattingInfo, extractPrecisionOption()); + // LogLog.debug("CATEGORY converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'C': + pc = new ClassNamePatternConverter(formattingInfo, extractPrecisionOption()); + // LogLog.debug("CLASS_NAME converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'd': + String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT; + DateFormat df; + String dOpt = extractOption(); + if (dOpt != null) + dateFormatStr = dOpt; + + if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) + df = new ISO8601DateFormat(); + else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) + df = new AbsoluteTimeDateFormat(); + else if (dateFormatStr.equalsIgnoreCase(AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) + df = new DateTimeDateFormat(); + else { + try { + df = new SimpleDateFormat(dateFormatStr); + } catch (IllegalArgumentException e) { + LogLog.error("Could not instantiate SimpleDateFormat with " + dateFormatStr, e); + df = (DateFormat) OptionConverter.instantiateByClassName("org.apache.log4j.helpers.ISO8601DateFormat", DateFormat.class, null); + } + } + pc = new DatePatternConverter(formattingInfo, df); + // LogLog.debug("DATE converter {"+dateFormatStr+"}."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'F': + pc = new LocationPatternConverter(formattingInfo, FILE_LOCATION_CONVERTER); + // LogLog.debug("File name converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'l': + pc = new LocationPatternConverter(formattingInfo, FULL_LOCATION_CONVERTER); + // LogLog.debug("Location converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'L': + pc = new LocationPatternConverter(formattingInfo, LINE_LOCATION_CONVERTER); + // LogLog.debug("LINE NUMBER converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'm': + pc = new BasicPatternConverter(formattingInfo, MESSAGE_CONVERTER); + // LogLog.debug("MESSAGE converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'M': + pc = new LocationPatternConverter(formattingInfo, METHOD_LOCATION_CONVERTER); + // LogLog.debug("METHOD converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'p': + pc = new BasicPatternConverter(formattingInfo, LEVEL_CONVERTER); + // LogLog.debug("LEVEL converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 'r': + pc = new BasicPatternConverter(formattingInfo, RELATIVE_TIME_CONVERTER); + // LogLog.debug("RELATIVE time converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + case 't': + pc = new BasicPatternConverter(formattingInfo, THREAD_CONVERTER); + // LogLog.debug("THREAD converter."); + // formattingInfo.dump(); + currentLiteral.setLength(0); + break; + /* + * case 'u': if(i < patternLength) { char cNext = pattern.charAt(i); if(cNext >= '0' && cNext <= '9') { pc = new + * UserFieldPatternConverter(formattingInfo, cNext - '0'); LogLog.debug("USER converter ["+cNext+"]."); + * formattingInfo.dump(); currentLiteral.setLength(0); i++; } else LogLog.error("Unexpected char" + * +cNext+" at position "+i); } break; + */ + case 'x': + pc = new BasicPatternConverter(formattingInfo, NDC_CONVERTER); + // LogLog.debug("NDC converter."); + currentLiteral.setLength(0); + break; + case 'X': + String xOpt = extractOption(); + pc = new MDCPatternConverter(formattingInfo, xOpt); + currentLiteral.setLength(0); + break; + default: + LogLog.error("Unexpected char [" + c + "] at position " + i + " in conversion patterrn."); + pc = new LiteralPatternConverter(currentLiteral.toString()); + currentLiteral.setLength(0); + } + + addConverter(pc); + } + + protected void addConverter(PatternConverter pc) { + currentLiteral.setLength(0); + // Add the pattern converter to the list. + addToList(pc); + // Next pattern is assumed to be a literal. + state = LITERAL_STATE; + // Reset formatting info + formattingInfo.reset(); + } + + // --------------------------------------------------------------------- + // PatternConverters + // --------------------------------------------------------------------- + + private static class BasicPatternConverter extends PatternConverter { + int type; + + BasicPatternConverter(FormattingInfo formattingInfo, int type) { + super(formattingInfo); + this.type = type; + } + + public String convert(LoggingEvent event) { + switch (type) { + case RELATIVE_TIME_CONVERTER: + return (Long.toString(event.timeStamp - LoggingEvent.getStartTime())); + case THREAD_CONVERTER: + return event.getThreadName(); + case LEVEL_CONVERTER: + return event.getLevel().toString(); + case NDC_CONVERTER: + return event.getNDC(); + case MESSAGE_CONVERTER: { + return event.getRenderedMessage(); + } + default: + return null; + } + } + } + + private static class LiteralPatternConverter extends PatternConverter { + private String literal; + + LiteralPatternConverter(String value) { + literal = value; + } + + public final void format(StringBuffer sbuf, LoggingEvent event) { + sbuf.append(literal); + } + + public String convert(LoggingEvent event) { + return literal; + } + } + + private static class DatePatternConverter extends PatternConverter { + private DateFormat df; + private Date date; + + DatePatternConverter(FormattingInfo formattingInfo, DateFormat df) { + super(formattingInfo); + date = new Date(); + this.df = df; + } + + public String convert(LoggingEvent event) { + date.setTime(event.timeStamp); + String converted = null; + try { + converted = df.format(date); + } catch (Exception ex) { + LogLog.error("Error occured while converting date.", ex); + } + return converted; + } + } + + private static class MDCPatternConverter extends PatternConverter { + private String key; + + MDCPatternConverter(FormattingInfo formattingInfo, String key) { + super(formattingInfo); + this.key = key; + } + + public String convert(LoggingEvent event) { + if (key == null) { + StringBuffer buf = new StringBuffer("{"); + Map properties = event.getProperties(); + if (properties.size() > 0) { + Object[] keys = properties.keySet().toArray(); + Arrays.sort(keys); + for (int i = 0; i < keys.length; i++) { + buf.append('{'); + buf.append(keys[i]); + buf.append(','); + buf.append(properties.get(keys[i])); + buf.append('}'); + } + } + buf.append('}'); + return buf.toString(); + } else { + Object val = event.getMDC(key); + if (val == null) { + return null; + } else { + return val.toString(); + } + } + } + } + + private class LocationPatternConverter extends PatternConverter { + int type; + + LocationPatternConverter(FormattingInfo formattingInfo, int type) { + super(formattingInfo); + this.type = type; + } + + public String convert(LoggingEvent event) { + LocationInfo locationInfo = event.getLocationInformation(); + switch (type) { + case FULL_LOCATION_CONVERTER: + return locationInfo.fullInfo; + case METHOD_LOCATION_CONVERTER: + return locationInfo.getMethodName(); + case LINE_LOCATION_CONVERTER: + return locationInfo.getLineNumber(); + case FILE_LOCATION_CONVERTER: + return locationInfo.getFileName(); + default: + return null; + } + } + } + + private static abstract class NamedPatternConverter extends PatternConverter { + int precision; + + NamedPatternConverter(FormattingInfo formattingInfo, int precision) { + super(formattingInfo); + this.precision = precision; + } + + abstract String getFullyQualifiedName(LoggingEvent event); + + public String convert(LoggingEvent event) { + String n = getFullyQualifiedName(event); + if (precision <= 0) + return n; + else { + int len = n.length(); + + // We substract 1 from 'len' when assigning to 'end' to avoid out of + // bounds exception in return r.substring(end+1, len). This can happen if + // precision is 1 and the category name ends with a dot. + int end = len - 1; + for (int i = precision; i > 0; i--) { + end = n.lastIndexOf('.', end - 1); + if (end == -1) + return n; + } + return n.substring(end + 1, len); + } + } + } + + private class ClassNamePatternConverter extends NamedPatternConverter { + + ClassNamePatternConverter(FormattingInfo formattingInfo, int precision) { + super(formattingInfo, precision); + } + + String getFullyQualifiedName(LoggingEvent event) { + return event.getLocationInformation().getClassName(); + } + } + + private class CategoryPatternConverter extends NamedPatternConverter { + + CategoryPatternConverter(FormattingInfo formattingInfo, int precision) { + super(formattingInfo, precision); + } + + String getFullyQualifiedName(LoggingEvent event) { + return event.getLoggerName(); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/QuietWriter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/QuietWriter.java new file mode 100644 index 00000000000..42120d97774 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/QuietWriter.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.io.FilterWriter; +import java.io.Writer; + +import org.apache.log4j.spi.ErrorCode; +import org.apache.log4j.spi.ErrorHandler; + + +/** + * QuietWriter does not throw exceptions when things go + * wrong. Instead, it delegates error handling to its {@link ErrorHandler}. + */ +public class QuietWriter extends FilterWriter { + + protected ErrorHandler errorHandler; + + public QuietWriter(Writer writer, ErrorHandler errorHandler) { + super(writer); + setErrorHandler(errorHandler); + } + + @Override + public void write(String string) { + if (string != null) { + try { + out.write(string); + } catch (Exception e) { + errorHandler.error("Failed to write [" + string + "].", e, + ErrorCode.WRITE_FAILURE); + } + } + } + + @Override + public void flush() { + try { + out.flush(); + } catch (Exception e) { + errorHandler.error("Failed to flush writer,", e, + ErrorCode.FLUSH_FAILURE); + } + } + + + public void setErrorHandler(ErrorHandler eh) { + if (eh == null) { + // This is a programming error on the part of the enclosing appender. + throw new IllegalArgumentException("Attempted to set null ErrorHandler."); + } + this.errorHandler = eh; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/RelativeTimeDateFormat.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/RelativeTimeDateFormat.java new file mode 100644 index 00000000000..a3fbc211d34 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/RelativeTimeDateFormat.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.ParsePosition; +import java.util.Date; + +/** + * Formats a {@link Date} by printing the number of milliseconds elapsed since construction of the format. This is the + * fastest printing DateFormat in the package. + * + * @since 0.7.5 + */ +public class RelativeTimeDateFormat extends DateFormat { + + private static final long serialVersionUID = 7055751607085611984L; + + protected final long startTime; + + public RelativeTimeDateFormat() { + this.startTime = System.currentTimeMillis(); + } + + /** + * Appends to sbuf the number of milliseconds elapsed since the start of the application. + * + * @since 0.7.5 + */ + @Override + public StringBuffer format(final Date date, final StringBuffer sbuf, final FieldPosition fieldPosition) { + // System.err.println(":"+ date.getTime() + " - " + startTime); + return sbuf.append((date.getTime() - startTime)); + } + + /** + * Always returns null. + */ + @Override + public Date parse(final String s, final ParsePosition pos) { + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java new file mode 100644 index 00000000000..8c1c92eee6a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/UtilLoggingLevel.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Level; + +/** + * An extension of the Level class that provides support for java.util.logging Levels. + */ +public class UtilLoggingLevel extends Level { + + /** + * Serialization version id. + */ + private static final long serialVersionUID = 909301162611820211L; + + /** + * Numerical value for SEVERE. + */ + public static final int SEVERE_INT = 22000; + /** + * Numerical value for WARNING. + */ + public static final int WARNING_INT = 21000; + + // INFO level defined in parent as 20000..no need to redefine here + + /** + * Numerical value for CONFIG. + */ + public static final int CONFIG_INT = 14000; + + /** + * Numerical value for FINE. + */ + public static final int FINE_INT = 13000; + + /** + * Numerical value for FINER. + */ + public static final int FINER_INT = 12000; + + /** + * Numerical value for FINEST. + */ + public static final int FINEST_INT = 11000; + + /** + * Numerical value for UNKNOWN. + */ + public static final int UNKNOWN_INT = 10000; + + /** + * SEVERE. + */ + public static final UtilLoggingLevel SEVERE = new UtilLoggingLevel(SEVERE_INT, "SEVERE", 0); + + /** + * WARNING. + */ + public static final UtilLoggingLevel WARNING = new UtilLoggingLevel(WARNING_INT, "WARNING", 4); + + /** + * INFO. + */ + // note: we've aligned the int values of the java.util.logging INFO level with log4j's level + public static final UtilLoggingLevel INFO = new UtilLoggingLevel(INFO_INT, "INFO", 5); + + /** + * CONFIG. + */ + public static final UtilLoggingLevel CONFIG = new UtilLoggingLevel(CONFIG_INT, "CONFIG", 6); + + /** + * FINE. + */ + public static final UtilLoggingLevel FINE = new UtilLoggingLevel(FINE_INT, "FINE", 7); + + /** + * FINER. + */ + public static final UtilLoggingLevel FINER = new UtilLoggingLevel(FINER_INT, "FINER", 8); + + /** + * FINEST. + */ + public static final UtilLoggingLevel FINEST = new UtilLoggingLevel(FINEST_INT, "FINEST", 9); + + /** + * Create new instance. + * + * @param level numeric value for level. + * @param levelStr symbolic name for level. + * @param syslogEquivalent Equivalent syslog severity. + */ + protected UtilLoggingLevel(final int level, final String levelStr, final int syslogEquivalent) { + super(level, levelStr, syslogEquivalent); + } + + /** + * Convert an integer passed as argument to a level. If the conversion fails, then this method returns the specified + * default. + * + * @param val numeric value. + * @param defaultLevel level to be returned if no level matches numeric value. + * @return matching level or default level. + */ + public static UtilLoggingLevel toLevel(final int val, final UtilLoggingLevel defaultLevel) { + switch (val) { + case SEVERE_INT: + return SEVERE; + + case WARNING_INT: + return WARNING; + + case INFO_INT: + return INFO; + + case CONFIG_INT: + return CONFIG; + + case FINE_INT: + return FINE; + + case FINER_INT: + return FINER; + + case FINEST_INT: + return FINEST; + + default: + return defaultLevel; + } + } + + /** + * Gets level matching numeric value. + * + * @param val numeric value. + * @return matching level or UtilLoggerLevel.FINEST if no match. + */ + public static Level toLevel(final int val) { + return toLevel(val, FINEST); + } + + /** + * Gets list of supported levels. + * + * @return list of supported levels. + */ + public static List getAllPossibleLevels() { + final ArrayList list = new ArrayList<>(); + list.add(FINE); + list.add(FINER); + list.add(FINEST); + list.add(INFO); + list.add(CONFIG); + list.add(WARNING); + list.add(SEVERE); + return list; + } + + /** + * Get level with specified symbolic name. + * + * @param s symbolic name. + * @return matching level or Level.DEBUG if no match. + */ + public static Level toLevel(final String s) { + return toLevel(s, Level.DEBUG); + } + + /** + * Get level with specified symbolic name. + * + * @param sArg symbolic name. + * @param defaultLevel level to return if no match. + * @return matching level or defaultLevel if no match. + */ + public static Level toLevel(final String sArg, final Level defaultLevel) { + if (sArg == null) { + return defaultLevel; + } + + final String s = sArg.toUpperCase(); + + if (s.equals("SEVERE")) { + return SEVERE; + } + + // if(s.equals("FINE")) return Level.FINE; + if (s.equals("WARNING")) { + return WARNING; + } + + if (s.equals("INFO")) { + return INFO; + } + + if (s.equals("CONFIG")) { + return CONFIG; + } + + if (s.equals("FINE")) { + return FINE; + } + + if (s.equals("FINER")) { + return FINER; + } + + if (s.equals("FINEST")) { + return FINEST; + } + return defaultLevel; + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AbstractDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AbstractDynamicMBean.java new file mode 100644 index 00000000000..f4543fd60ed --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AbstractDynamicMBean.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.jmx; + +import java.util.Enumeration; +import java.util.Vector; + +import javax.management.Attribute; +import javax.management.AttributeList; +import javax.management.DynamicMBean; +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.JMException; +import javax.management.MBeanRegistration; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.RuntimeOperationsException; + +import org.apache.log4j.Appender; +import org.apache.log4j.Logger; + +public abstract class AbstractDynamicMBean implements DynamicMBean, MBeanRegistration { + + /** + * Get MBean name. + * + * @param appender appender, may not be null. + * @return name. + * @since 1.2.16 + */ + static protected String getAppenderName(final Appender appender) { + String name = appender.getName(); + if (name == null || name.trim().length() == 0) { + // try to get some form of a name, because null is not allowed (exception), and empty string certainly isn't useful in + // JMX.. + name = appender.toString(); + } + return name; + } + String dClassName; + MBeanServer server; + + private final Vector mbeanList = new Vector(); + + /** + * Enables the to get the values of several attributes of the Dynamic MBean. + */ + @Override + public AttributeList getAttributes(final String[] attributeNames) { + + // Check attributeNames is not null to avoid NullPointerException later on + if (attributeNames == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("attributeNames[] cannot be null"), "Cannot invoke a getter of " + dClassName); + } + + final AttributeList resultList = new AttributeList(); + + // if attributeNames is empty, return an empty result list + if (attributeNames.length == 0) { + return resultList; + } + + // build the result attribute list + for (final String attributeName : attributeNames) { + try { + final Object value = getAttribute((String) attributeName); + resultList.add(new Attribute(attributeName, value)); + } catch (final JMException e) { + e.printStackTrace(); + } catch (final RuntimeException e) { + e.printStackTrace(); + } + } + return (resultList); + } + + protected abstract Logger getLogger(); + + @Override + public void postDeregister() { + getLogger().debug("postDeregister is called."); + } + + @Override + public void postRegister(final java.lang.Boolean registrationDone) { + } + + /** + * Performs cleanup for deregistering this MBean. Default implementation unregisters MBean instances which are + * registered using {@link #registerMBean(Object mbean, ObjectName objectName)}. + */ + @Override + public void preDeregister() { + getLogger().debug("preDeregister called."); + + final Enumeration iterator = mbeanList.elements(); + while (iterator.hasMoreElements()) { + final ObjectName name = (ObjectName) iterator.nextElement(); + try { + server.unregisterMBean(name); + } catch (final InstanceNotFoundException e) { + getLogger().warn("Missing MBean " + name.getCanonicalName()); + } catch (final MBeanRegistrationException e) { + getLogger().warn("Failed unregistering " + name.getCanonicalName()); + } + } + } + + @Override + public ObjectName preRegister(final MBeanServer server, final ObjectName name) { + getLogger().debug("preRegister called. Server=" + server + ", name=" + name); + this.server = server; + return name; + } + + /** + * Registers MBean instance in the attached server. Must NOT be called before registration of this instance. + */ + protected void registerMBean(final Object mbean, final ObjectName objectName) + throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { + server.registerMBean(mbean, objectName); + mbeanList.add(objectName); + } + + /** + * Sets the values of several attributes of the Dynamic MBean, and returns the list of attributes that have been set. + */ + @Override + public AttributeList setAttributes(final AttributeList attributes) { + + // Check attributes is not null to avoid NullPointerException later on + if (attributes == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("AttributeList attributes cannot be null"), + "Cannot invoke a setter of " + dClassName); + } + final AttributeList resultList = new AttributeList(); + + // if attributeNames is empty, nothing more to do + if (attributes.isEmpty()) { + return resultList; + } + + // for each attribute, try to set it and add to the result list if successfull + for (final Object attribute : attributes) { + final Attribute attr = (Attribute) attribute; + try { + setAttribute(attr); + final String name = attr.getName(); + final Object value = getAttribute(name); + resultList.add(new Attribute(name, value)); + } catch (final JMException e) { + e.printStackTrace(); + } catch (final RuntimeException e) { + e.printStackTrace(); + } + } + return (resultList); + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/Agent.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/Agent.java new file mode 100644 index 00000000000..e1cd3586fa2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/Agent.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.jmx; + +import java.io.InterruptedIOException; +import java.lang.reflect.InvocationTargetException; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; + +import org.apache.log4j.Logger; + +/** + * Manages an instance of com.sun.jdmk.comm.HtmlAdapterServer which was provided for demonstration purposes in the Java + * Management Extensions Reference Implementation 1.2.1. This class is provided to maintain compatibility with earlier + * versions of log4j and use in new code is discouraged. + * + * @deprecated + */ +@Deprecated +public class Agent { + + /** + * Diagnostic logger. + * + * @deprecated + */ + @Deprecated + static Logger log = Logger.getLogger(Agent.class); + + /** + * Creates a new instance of com.sun.jdmk.comm.HtmlAdapterServer using reflection. + * + * @since 1.2.16 + * @return new instance. + */ + private static Object createServer() { + Object newInstance = null; + try { + newInstance = Class.forName("com.sun.jdmk.comm.HtmlAdapterServer").newInstance(); + } catch (final ClassNotFoundException ex) { + throw new RuntimeException(ex.toString()); + } catch (final InstantiationException ex) { + throw new RuntimeException(ex.toString()); + } catch (final IllegalAccessException ex) { + throw new RuntimeException(ex.toString()); + } + return newInstance; + } + + /** + * Invokes HtmlAdapterServer.start() using reflection. + * + * @since 1.2.16 + * @param server instance of com.sun.jdmk.comm.HtmlAdapterServer. + */ + private static void startServer(final Object server) { + try { + server.getClass().getMethod("start", new Class[0]).invoke(server, new Object[0]); + } catch (final InvocationTargetException ex) { + final Throwable cause = ex.getTargetException(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause != null) { + if (cause instanceof InterruptedException || cause instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + throw new RuntimeException(cause.toString()); + } else { + throw new RuntimeException(); + } + } catch (final NoSuchMethodException ex) { + throw new RuntimeException(ex.toString()); + } catch (final IllegalAccessException ex) { + throw new RuntimeException(ex.toString()); + } + } + + /** + * Create new instance. + * + * @deprecated + */ + @Deprecated + public Agent() { + } + + /** + * Starts instance of HtmlAdapterServer. + * + * @deprecated + */ + @Deprecated + public void start() { + + final MBeanServer server = MBeanServerFactory.createMBeanServer(); + final Object html = createServer(); + + try { + log.info("Registering HtmlAdaptorServer instance."); + server.registerMBean(html, new ObjectName("Adaptor:name=html,port=8082")); + log.info("Registering HierarchyDynamicMBean instance."); + final HierarchyDynamicMBean hdm = new HierarchyDynamicMBean(); + server.registerMBean(hdm, new ObjectName("log4j:hiearchy=default")); + } catch (final JMException e) { + log.error("Problem while registering MBeans instances.", e); + return; + } catch (final RuntimeException e) { + log.error("Problem while registering MBeans instances.", e); + return; + } + startServer(html); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java new file mode 100644 index 00000000000..6374059fd14 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java @@ -0,0 +1,280 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.jmx; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.InterruptedIOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Hashtable; +import java.util.Vector; + +import javax.management.Attribute; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.JMException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.RuntimeOperationsException; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.OptionHandler; + +public class AppenderDynamicMBean extends AbstractDynamicMBean { + + // This category instance is for logging. + private static Logger cat = Logger.getLogger(AppenderDynamicMBean.class); + private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1]; + private final Vector dAttributes = new Vector(); + + private final String dClassName = this.getClass().getName(); + private final Hashtable dynamicProps = new Hashtable(5); + private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[2]; + + private final String dDescription = "This MBean acts as a management facade for log4j appenders."; + + // We wrap this appender instance. + private final Appender appender; + + public AppenderDynamicMBean(final Appender appender) throws IntrospectionException { + this.appender = appender; + buildDynamicMBeanInfo(); + } + + private void buildDynamicMBeanInfo() throws IntrospectionException { + final Constructor[] constructors = this.getClass().getConstructors(); + dConstructors[0] = new MBeanConstructorInfo("AppenderDynamicMBean(): Constructs a AppenderDynamicMBean instance", constructors[0]); + + final BeanInfo bi = Introspector.getBeanInfo(appender.getClass()); + final PropertyDescriptor[] pd = bi.getPropertyDescriptors(); + + final int size = pd.length; + + for (int i = 0; i < size; i++) { + final String name = pd[i].getName(); + final Method readMethod = pd[i].getReadMethod(); + final Method writeMethod = pd[i].getWriteMethod(); + if (readMethod != null) { + final Class returnClass = readMethod.getReturnType(); + if (isSupportedType(returnClass)) { + String returnClassName; + if (returnClass.isAssignableFrom(Priority.class)) { + returnClassName = "java.lang.String"; + } else { + returnClassName = returnClass.getName(); + } + + dAttributes.add(new MBeanAttributeInfo(name, returnClassName, "Dynamic", true, writeMethod != null, false)); + dynamicProps.put(name, new MethodUnion(readMethod, writeMethod)); + } + } + } + + MBeanParameterInfo[] params = new MBeanParameterInfo[0]; + + dOperations[0] = new MBeanOperationInfo("activateOptions", "activateOptions(): add an appender", params, "void", MBeanOperationInfo.ACTION); + + params = new MBeanParameterInfo[1]; + params[0] = new MBeanParameterInfo("layout class", "java.lang.String", "layout class"); + + dOperations[1] = new MBeanOperationInfo("setLayout", "setLayout(): add a layout", params, "void", MBeanOperationInfo.ACTION); + } + + @Override + public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException { + + // Check attributeName is not null to avoid NullPointerException later on + if (attributeName == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke a getter of " + dClassName + " with null attribute name"); + } + + cat.debug("getAttribute called with [" + attributeName + "]."); + if (attributeName.startsWith("appender=" + appender.getName() + ",layout")) { + try { + return new ObjectName("log4j:" + attributeName); + } catch (final MalformedObjectNameException e) { + cat.error("attributeName", e); + } catch (final RuntimeException e) { + cat.error("attributeName", e); + } + } + + final MethodUnion mu = (MethodUnion) dynamicProps.get(attributeName); + + // cat.debug("----name="+attributeName+", b="+b); + + if (mu != null && mu.readMethod != null) { + try { + return mu.readMethod.invoke(appender, null); + } catch (final IllegalAccessException e) { + return null; + } catch (final InvocationTargetException e) { + if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + return null; + } catch (final RuntimeException e) { + return null; + } + } + + // If attributeName has not been recognized throw an AttributeNotFoundException + throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName)); + + } + + @Override + protected Logger getLogger() { + return cat; + } + + @Override + public MBeanInfo getMBeanInfo() { + cat.debug("getMBeanInfo called."); + + final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()]; + dAttributes.toArray(attribs); + + return new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]); + } + + @Override + public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException { + + if (operationName.equals("activateOptions") && appender instanceof OptionHandler) { + final OptionHandler oh = (OptionHandler) appender; + oh.activateOptions(); + return "Options activated."; + } else if (operationName.equals("setLayout")) { + final Layout layout = (Layout) OptionConverter.instantiateByClassName((String) params[0], Layout.class, null); + appender.setLayout(layout); + registerLayoutMBean(layout); + } + return null; + } + + private boolean isSupportedType(final Class clazz) { + if (clazz.isPrimitive() || (clazz == String.class) || clazz.isAssignableFrom(Priority.class)) { + return true; + } + + return false; + + } + + @Override + public ObjectName preRegister(final MBeanServer server, final ObjectName name) { + cat.debug("preRegister called. Server=" + server + ", name=" + name); + this.server = server; + registerLayoutMBean(appender.getLayout()); + + return name; + } + + void registerLayoutMBean(final Layout layout) { + if (layout == null) { + return; + } + + final String name = getAppenderName(appender) + ",layout=" + layout.getClass().getName(); + cat.debug("Adding LayoutMBean:" + name); + ObjectName objectName = null; + try { + final LayoutDynamicMBean appenderMBean = new LayoutDynamicMBean(layout); + objectName = new ObjectName("log4j:appender=" + name); + if (!server.isRegistered(objectName)) { + registerMBean(appenderMBean, objectName); + dAttributes.add(new MBeanAttributeInfo("appender=" + name, "javax.management.ObjectName", "The " + name + " layout.", true, true, false)); + } + + } catch (final JMException e) { + cat.error("Could not add DynamicLayoutMBean for [" + name + "].", e); + } catch (final java.beans.IntrospectionException e) { + cat.error("Could not add DynamicLayoutMBean for [" + name + "].", e); + } catch (final RuntimeException e) { + cat.error("Could not add DynamicLayoutMBean for [" + name + "].", e); + } + } + + @Override + public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { + + // Check attribute is not null to avoid NullPointerException later on + if (attribute == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"), + "Cannot invoke a setter of " + dClassName + " with null attribute"); + } + final String name = attribute.getName(); + Object value = attribute.getValue(); + + if (name == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke the setter of " + dClassName + " with null attribute name"); + } + + final MethodUnion mu = (MethodUnion) dynamicProps.get(name); + + if (mu != null && mu.writeMethod != null) { + final Object[] o = new Object[1]; + + final Class[] params = mu.writeMethod.getParameterTypes(); + if (params[0] == org.apache.log4j.Priority.class) { + value = OptionConverter.toLevel((String) value, (Level) getAttribute(name)); + } + o[0] = value; + + try { + mu.writeMethod.invoke(appender, o); + + } catch (final InvocationTargetException e) { + if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + cat.error("FIXME", e); + } catch (final IllegalAccessException e) { + cat.error("FIXME", e); + } catch (final RuntimeException e) { + cat.error("FIXME", e); + } + } else if (name.endsWith(".layout")) { + + } else { + throw (new AttributeNotFoundException("Attribute " + name + " not found in " + this.getClass().getName())); + } + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/HierarchyDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/HierarchyDynamicMBean.java new file mode 100644 index 00000000000..b29ef8bd648 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/HierarchyDynamicMBean.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.jmx; + +import java.lang.reflect.Constructor; +import java.util.Vector; + +import javax.management.Attribute; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.JMException; +import javax.management.ListenerNotFoundException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.Notification; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationFilterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.RuntimeOperationsException; + +import org.apache.log4j.Appender; +import org.apache.log4j.Category; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.HierarchyEventListener; +import org.apache.log4j.spi.LoggerRepository; + +public class HierarchyDynamicMBean extends AbstractDynamicMBean implements HierarchyEventListener, NotificationBroadcaster { + + static final String ADD_APPENDER = "addAppender."; + static final String THRESHOLD = "threshold"; + + private static Logger log = Logger.getLogger(HierarchyDynamicMBean.class); + private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1]; + + private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1]; + private final Vector vAttributes = new Vector(); + private final String dClassName = this.getClass().getName(); + + private final String dDescription = "This MBean acts as a management facade for org.apache.log4j.Hierarchy."; + + private final NotificationBroadcasterSupport nbs = new NotificationBroadcasterSupport(); + + private final LoggerRepository hierarchy; + + public HierarchyDynamicMBean() { + hierarchy = LogManager.getLoggerRepository(); + buildDynamicMBeanInfo(); + } + + @Override + public void addAppenderEvent(final Category logger, final Appender appender) { + log.debug("addAppenderEvent called: logger=" + logger.getName() + ", appender=" + appender.getName()); + final Notification n = new Notification(ADD_APPENDER + logger.getName(), this, 0); + n.setUserData(appender); + log.debug("sending notification."); + nbs.sendNotification(n); + } + + ObjectName addLoggerMBean(final Logger logger) { + final String name = logger.getName(); + ObjectName objectName = null; + try { + final LoggerDynamicMBean loggerMBean = new LoggerDynamicMBean(logger); + objectName = new ObjectName("log4j", "logger", name); + + if (!server.isRegistered(objectName)) { + registerMBean(loggerMBean, objectName); + final NotificationFilterSupport nfs = new NotificationFilterSupport(); + nfs.enableType(ADD_APPENDER + logger.getName()); + log.debug("---Adding logger [" + name + "] as listener."); + nbs.addNotificationListener(loggerMBean, nfs, null); + vAttributes.add(new MBeanAttributeInfo("logger=" + name, "javax.management.ObjectName", "The " + name + " logger.", true, true, // this makes + // the object + // clickable + false)); + + } + + } catch (final JMException e) { + log.error("Could not add loggerMBean for [" + name + "].", e); + } catch (final RuntimeException e) { + log.error("Could not add loggerMBean for [" + name + "].", e); + } + return objectName; + } + + public ObjectName addLoggerMBean(final String name) { + final Logger cat = LogManager.exists(name); + + if (cat != null) { + return addLoggerMBean(cat); + } else { + return null; + } + } + + @Override + public void addNotificationListener(final NotificationListener listener, final NotificationFilter filter, final java.lang.Object handback) { + nbs.addNotificationListener(listener, filter, handback); + } + + private void buildDynamicMBeanInfo() { + final Constructor[] constructors = this.getClass().getConstructors(); + dConstructors[0] = new MBeanConstructorInfo("HierarchyDynamicMBean(): Constructs a HierarchyDynamicMBean instance", constructors[0]); + + vAttributes.add(new MBeanAttributeInfo(THRESHOLD, "java.lang.String", "The \"threshold\" state of the hiearchy.", true, true, false)); + + final MBeanParameterInfo[] params = new MBeanParameterInfo[1]; + params[0] = new MBeanParameterInfo("name", "java.lang.String", "Create a logger MBean"); + dOperations[0] = new MBeanOperationInfo("addLoggerMBean", "addLoggerMBean(): add a loggerMBean", params, "javax.management.ObjectName", + MBeanOperationInfo.ACTION); + } + + @Override + public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException { + + // Check attributeName is not null to avoid NullPointerException later on + if (attributeName == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke a getter of " + dClassName + " with null attribute name"); + } + + log.debug("Called getAttribute with [" + attributeName + "]."); + + // Check for a recognized attributeName and call the corresponding getter + if (attributeName.equals(THRESHOLD)) { + return hierarchy.getThreshold(); + } else if (attributeName.startsWith("logger")) { + final int k = attributeName.indexOf("%3D"); + String val = attributeName; + if (k > 0) { + val = attributeName.substring(0, k) + '=' + attributeName.substring(k + 3); + } + try { + return new ObjectName("log4j:" + val); + } catch (final JMException e) { + log.error("Could not create ObjectName" + val); + } catch (final RuntimeException e) { + log.error("Could not create ObjectName" + val); + } + } + + // If attributeName has not been recognized throw an AttributeNotFoundException + throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName)); + + } + + @Override + protected Logger getLogger() { + return log; + } + + @Override + public MBeanInfo getMBeanInfo() { + // cat.debug("getMBeanInfo called."); + + final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[vAttributes.size()]; + vAttributes.toArray(attribs); + + return new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]); + } + + @Override + public MBeanNotificationInfo[] getNotificationInfo() { + return nbs.getNotificationInfo(); + } + + @Override + public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException { + + if (operationName == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Operation name cannot be null"), + "Cannot invoke a null operation in " + dClassName); + } + // Check for a recognized operation name and call the corresponding operation + + if (operationName.equals("addLoggerMBean")) { + return addLoggerMBean((String) params[0]); + } else { + throw new ReflectionException(new NoSuchMethodException(operationName), "Cannot find the operation " + operationName + " in " + dClassName); + } + + } + + @Override + public void postRegister(final java.lang.Boolean registrationDone) { + log.debug("postRegister is called."); + hierarchy.addHierarchyEventListener(this); + final Logger root = hierarchy.getRootLogger(); + addLoggerMBean(root); + } + + @Override + public void removeAppenderEvent(final Category cat, final Appender appender) { + log.debug("removeAppenderCalled: logger=" + cat.getName() + ", appender=" + appender.getName()); + } + + @Override + public void removeNotificationListener(final NotificationListener listener) throws ListenerNotFoundException { + nbs.removeNotificationListener(listener); + } + + @Override + public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { + + // Check attribute is not null to avoid NullPointerException later on + if (attribute == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"), + "Cannot invoke a setter of " + dClassName + " with null attribute"); + } + final String name = attribute.getName(); + final Object value = attribute.getValue(); + + if (name == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke the setter of " + dClassName + " with null attribute name"); + } + + if (name.equals(THRESHOLD)) { + final Level l = OptionConverter.toLevel((String) value, hierarchy.getThreshold()); + hierarchy.setThreshold(l); + } + + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LayoutDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LayoutDynamicMBean.java new file mode 100644 index 00000000000..68e9ebf4d56 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LayoutDynamicMBean.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.jmx; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.InterruptedIOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Hashtable; +import java.util.Vector; + +import javax.management.Attribute; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.ReflectionException; +import javax.management.RuntimeOperationsException; + +import org.apache.log4j.Layout; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.OptionHandler; + +public class LayoutDynamicMBean extends AbstractDynamicMBean { + + // This category instance is for logging. + private static Logger cat = Logger.getLogger(LayoutDynamicMBean.class); + private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1]; + private final Vector dAttributes = new Vector(); + + private final String dClassName = this.getClass().getName(); + private final Hashtable dynamicProps = new Hashtable(5); + private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1]; + + private final String dDescription = "This MBean acts as a management facade for log4j layouts."; + + // We wrap this layout instance. + private final Layout layout; + + public LayoutDynamicMBean(final Layout layout) throws IntrospectionException { + this.layout = layout; + buildDynamicMBeanInfo(); + } + + private void buildDynamicMBeanInfo() throws IntrospectionException { + final Constructor[] constructors = this.getClass().getConstructors(); + dConstructors[0] = new MBeanConstructorInfo("LayoutDynamicMBean(): Constructs a LayoutDynamicMBean instance", constructors[0]); + + final BeanInfo bi = Introspector.getBeanInfo(layout.getClass()); + final PropertyDescriptor[] pd = bi.getPropertyDescriptors(); + + final int size = pd.length; + + for (int i = 0; i < size; i++) { + final String name = pd[i].getName(); + final Method readMethod = pd[i].getReadMethod(); + final Method writeMethod = pd[i].getWriteMethod(); + if (readMethod != null) { + final Class returnClass = readMethod.getReturnType(); + if (isSupportedType(returnClass)) { + String returnClassName; + if (returnClass.isAssignableFrom(Level.class)) { + returnClassName = "java.lang.String"; + } else { + returnClassName = returnClass.getName(); + } + + dAttributes.add(new MBeanAttributeInfo(name, returnClassName, "Dynamic", true, writeMethod != null, false)); + dynamicProps.put(name, new MethodUnion(readMethod, writeMethod)); + } + } + } + + final MBeanParameterInfo[] params = new MBeanParameterInfo[0]; + + dOperations[0] = new MBeanOperationInfo("activateOptions", "activateOptions(): add an layout", params, "void", MBeanOperationInfo.ACTION); + } + + @Override + public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException { + + // Check attributeName is not null to avoid NullPointerException later on + if (attributeName == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke a getter of " + dClassName + " with null attribute name"); + } + + final MethodUnion mu = (MethodUnion) dynamicProps.get(attributeName); + + cat.debug("----name=" + attributeName + ", mu=" + mu); + + if (mu != null && mu.readMethod != null) { + try { + return mu.readMethod.invoke(layout, null); + } catch (final InvocationTargetException e) { + if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + return null; + } catch (final IllegalAccessException e) { + return null; + } catch (final RuntimeException e) { + return null; + } + } + + // If attributeName has not been recognized throw an AttributeNotFoundException + throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName)); + + } + + @Override + protected Logger getLogger() { + return cat; + } + + @Override + public MBeanInfo getMBeanInfo() { + cat.debug("getMBeanInfo called."); + + final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()]; + dAttributes.toArray(attribs); + + return new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]); + } + + @Override + public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException { + + if (operationName.equals("activateOptions") && layout instanceof OptionHandler) { + final OptionHandler oh = (OptionHandler) layout; + oh.activateOptions(); + return "Options activated."; + } + return null; + } + + private boolean isSupportedType(final Class clazz) { + if (clazz.isPrimitive() || (clazz == String.class) || clazz.isAssignableFrom(Level.class)) { + return true; + } + + return false; + } + + @Override + public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { + + // Check attribute is not null to avoid NullPointerException later on + if (attribute == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"), + "Cannot invoke a setter of " + dClassName + " with null attribute"); + } + final String name = attribute.getName(); + Object value = attribute.getValue(); + + if (name == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke the setter of " + dClassName + " with null attribute name"); + } + + final MethodUnion mu = (MethodUnion) dynamicProps.get(name); + + if (mu != null && mu.writeMethod != null) { + final Object[] o = new Object[1]; + + final Class[] params = mu.writeMethod.getParameterTypes(); + if (params[0] == org.apache.log4j.Priority.class) { + value = OptionConverter.toLevel((String) value, (Level) getAttribute(name)); + } + o[0] = value; + + try { + mu.writeMethod.invoke(layout, o); + + } catch (final InvocationTargetException e) { + if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + cat.error("FIXME", e); + } catch (final IllegalAccessException e) { + cat.error("FIXME", e); + } catch (final RuntimeException e) { + cat.error("FIXME", e); + } + } else { + throw (new AttributeNotFoundException("Attribute " + name + " not found in " + this.getClass().getName())); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java new file mode 100644 index 00000000000..4ae0e569146 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.jmx; + +import java.lang.reflect.Constructor; +import java.util.Enumeration; +import java.util.Vector; + +import javax.management.Attribute; +import javax.management.AttributeNotFoundException; +import javax.management.InvalidAttributeValueException; +import javax.management.JMException; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.MalformedObjectNameException; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.ReflectionException; +import javax.management.RuntimeOperationsException; + +import org.apache.log4j.Appender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.helpers.OptionConverter; + +public class LoggerDynamicMBean extends AbstractDynamicMBean implements NotificationListener { + + // This Logger instance is for logging. + private static Logger cat = Logger.getLogger(LoggerDynamicMBean.class); + private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1]; + + private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1]; + private final Vector dAttributes = new Vector(); + + private final String dClassName = this.getClass().getName(); + + private final String dDescription = "This MBean acts as a management facade for a org.apache.log4j.Logger instance."; + + // We wrap this Logger instance. + private final Logger logger; + + public LoggerDynamicMBean(final Logger logger) { + this.logger = logger; + buildDynamicMBeanInfo(); + } + + void addAppender(final String appenderClass, final String appenderName) { + cat.debug("addAppender called with " + appenderClass + ", " + appenderName); + final Appender appender = (Appender) OptionConverter.instantiateByClassName(appenderClass, org.apache.log4j.Appender.class, null); + appender.setName(appenderName); + logger.addAppender(appender); + + // appenderMBeanRegistration(); + + } + + void appenderMBeanRegistration() { + final Enumeration enumeration = logger.getAllAppenders(); + while (enumeration.hasMoreElements()) { + final Appender appender = (Appender) enumeration.nextElement(); + registerAppenderMBean(appender); + } + } + + private void buildDynamicMBeanInfo() { + final Constructor[] constructors = this.getClass().getConstructors(); + dConstructors[0] = new MBeanConstructorInfo("HierarchyDynamicMBean(): Constructs a HierarchyDynamicMBean instance", constructors[0]); + + dAttributes.add(new MBeanAttributeInfo("name", "java.lang.String", "The name of this Logger.", true, false, false)); + + dAttributes.add(new MBeanAttributeInfo("priority", "java.lang.String", "The priority of this logger.", true, true, false)); + + final MBeanParameterInfo[] params = new MBeanParameterInfo[2]; + params[0] = new MBeanParameterInfo("class name", "java.lang.String", "add an appender to this logger"); + params[1] = new MBeanParameterInfo("appender name", "java.lang.String", "name of the appender"); + + dOperations[0] = new MBeanOperationInfo("addAppender", "addAppender(): add an appender", params, "void", MBeanOperationInfo.ACTION); + } + + @Override + public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException { + + // Check attributeName is not null to avoid NullPointerException later on + if (attributeName == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke a getter of " + dClassName + " with null attribute name"); + } + + // Check for a recognized attributeName and call the corresponding getter + if (attributeName.equals("name")) { + return logger.getName(); + } else if (attributeName.equals("priority")) { + final Level l = logger.getLevel(); + if (l == null) { + return null; + } else { + return l.toString(); + } + } else if (attributeName.startsWith("appender=")) { + try { + return new ObjectName("log4j:" + attributeName); + } catch (final MalformedObjectNameException e) { + cat.error("Could not create ObjectName" + attributeName); + } catch (final RuntimeException e) { + cat.error("Could not create ObjectName" + attributeName); + } + } + + // If attributeName has not been recognized throw an AttributeNotFoundException + throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName)); + + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + public MBeanInfo getMBeanInfo() { + // cat.debug("getMBeanInfo called."); + + final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()]; + dAttributes.toArray(attribs); + + final MBeanInfo mb = new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]); + // cat.debug("getMBeanInfo exit."); + return mb; + } + + @Override + public void handleNotification(final Notification notification, final Object handback) { + cat.debug("Received notification: " + notification.getType()); + registerAppenderMBean((Appender) notification.getUserData()); + + } + + @Override + public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException { + + if (operationName.equals("addAppender")) { + addAppender((String) params[0], (String) params[1]); + return "Hello world."; + } + + return null; + } + + @Override + public void postRegister(final java.lang.Boolean registrationDone) { + appenderMBeanRegistration(); + } + + void registerAppenderMBean(final Appender appender) { + final String name = getAppenderName(appender); + cat.debug("Adding AppenderMBean for appender named " + name); + ObjectName objectName = null; + try { + final AppenderDynamicMBean appenderMBean = new AppenderDynamicMBean(appender); + objectName = new ObjectName("log4j", "appender", name); + if (!server.isRegistered(objectName)) { + registerMBean(appenderMBean, objectName); + dAttributes.add(new MBeanAttributeInfo("appender=" + name, "javax.management.ObjectName", "The " + name + " appender.", true, true, false)); + } + + } catch (final JMException e) { + cat.error("Could not add appenderMBean for [" + name + "].", e); + } catch (final java.beans.IntrospectionException e) { + cat.error("Could not add appenderMBean for [" + name + "].", e); + } catch (final RuntimeException e) { + cat.error("Could not add appenderMBean for [" + name + "].", e); + } + } + + @Override + public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { + + // Check attribute is not null to avoid NullPointerException later on + if (attribute == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"), + "Cannot invoke a setter of " + dClassName + " with null attribute"); + } + final String name = attribute.getName(); + final Object value = attribute.getValue(); + + if (name == null) { + throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"), + "Cannot invoke the setter of " + dClassName + " with null attribute name"); + } + + if (name.equals("priority")) { + if (value instanceof String) { + final String s = (String) value; + Level p = logger.getLevel(); + if (s.equalsIgnoreCase("NULL")) { + p = null; + } else { + p = OptionConverter.toLevel(s, p); + } + logger.setLevel(p); + } + } else { + throw (new AttributeNotFoundException("Attribute " + name + " not found in " + this.getClass().getName())); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/MethodUnion.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/MethodUnion.java new file mode 100644 index 00000000000..4e57aec8ccc --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/MethodUnion.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.jmx; + +import java.lang.reflect.Method; + +class MethodUnion { + + Method readMethod; + Method writeMethod; + + MethodUnion(final Method readMethod, final Method writeMethod) { + this.readMethod = readMethod; + this.writeMethod = writeMethod; + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/package-info.java new file mode 100644 index 00000000000..6eec1006861 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * This package lets you manage log4j settings using JMX. It is unfortunately not of production quality. + */ +package org.apache.log4j.jmx; diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java new file mode 100644 index 00000000000..1a504492b5a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.layout; + +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.StringLayout; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.layout.AbstractStringLayout; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.net.Priority; +import org.apache.logging.log4j.core.pattern.DatePatternConverter; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.util.Chars; + +/** + * Port of the layout used by SyslogAppender in Log4j 1.x. Provided for + * compatibility with existing Log4j 1 configurations. + * + * Originally developed by Ceki Gülcü and Anders Kristensen. + */ +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin +public final class Log4j1SyslogLayout extends AbstractStringLayout { + + /** + * Builds a SyslogLayout. + *

The main arguments are

+ *
    + *
  • facility: The Facility is used to try to classify the message.
  • + *
  • includeNewLine: If true a newline will be appended to the result.
  • + *
  • escapeNL: Pattern to use for replacing newlines.
  • + *
  • charset: The character set.
  • + *
+ * @param the builder type + */ + public static class Builder> extends AbstractStringLayout.Builder + implements org.apache.logging.log4j.core.util.Builder { + + public Builder() { + setCharset(StandardCharsets.UTF_8); + } + + @PluginBuilderAttribute + private Facility facility = Facility.USER; + + @PluginBuilderAttribute + private boolean facilityPrinting; + + @PluginBuilderAttribute + private boolean header; + + @PluginElement("Layout") + private Layout messageLayout; + + @Override + public Log4j1SyslogLayout build() { + if (messageLayout != null && !(messageLayout instanceof StringLayout)) { + LOGGER.error("Log4j1SyslogLayout: the message layout must be a StringLayout."); + return null; + } + return new Log4j1SyslogLayout(facility, facilityPrinting, header, (StringLayout) messageLayout, getCharset()); + } + + public Facility getFacility() { + return facility; + } + + public boolean isFacilityPrinting() { + return facilityPrinting; + } + + public boolean isHeader() { + return header; + } + + public Layout getMessageLayout() { + return messageLayout; + } + + public B setFacility(final Facility facility) { + this.facility = facility; + return asBuilder(); + } + + public B setFacilityPrinting(final boolean facilityPrinting) { + this.facilityPrinting = facilityPrinting; + return asBuilder(); + } + + public B setHeader(final boolean header) { + this.header = header; + return asBuilder(); + } + + public B setMessageLayout(final Layout messageLayout) { + this.messageLayout = messageLayout; + return asBuilder(); + } + } + + @PluginBuilderFactory + public static > B newBuilder() { + return new Builder().asBuilder(); + } + + /** + * Host name used to identify messages from this appender. + */ + private static final String localHostname = NetUtils.getLocalHostname(); + + private final Facility facility; + private final boolean facilityPrinting; + private final boolean header; + private final StringLayout messageLayout; + + /** + * Date format used if header = true. + */ + private static final String[] dateFormatOptions = {"MMM dd HH:mm:ss", null, "en"}; + private final LogEventPatternConverter dateConverter = DatePatternConverter.newInstance(dateFormatOptions); + + + private Log4j1SyslogLayout(final Facility facility, final boolean facilityPrinting, final boolean header, + final StringLayout messageLayout, final Charset charset) { + super(charset); + this.facility = facility; + this.facilityPrinting = facilityPrinting; + this.header = header; + this.messageLayout = messageLayout; + } + + /** + * Formats a {@link LogEvent} in conformance with the BSD Log record format. + * + * @param event The LogEvent + * @return the event formatted as a String. + */ + @Override + public String toSerializable(final LogEvent event) { + // The messageLayout also uses the thread-bound StringBuilder, + // so we generate the message first + final String message = messageLayout != null ? messageLayout.toSerializable(event) + : event.getMessage().getFormattedMessage(); + final StringBuilder buf = getStringBuilder(); + + buf.append('<'); + buf.append(Priority.getPriority(facility, event.getLevel())); + buf.append('>'); + + if (header) { + final int index = buf.length() + 4; + dateConverter.format(event, buf); + // RFC 3164 says leading space, not leading zero on days 1-9 + if (buf.charAt(index) == '0') { + buf.setCharAt(index, Chars.SPACE); + } + + buf.append(Chars.SPACE); + buf.append(localHostname); + buf.append(Chars.SPACE); + } + + if (facilityPrinting) { + buf.append(facility != null ? facility.name().toLowerCase() : "user").append(':'); + } + + buf.append(message); + // TODO: splitting message into 1024 byte chunks? + return buf.toString(); + } + + /** + * Gets this SyslogLayout's content format. Specified by: + *
    + *
  • Key: "structured" Value: "false"
  • + *
  • Key: "dateFormat" Value: "MMM dd HH:mm:ss"
  • + *
  • Key: "format" Value: "<LEVEL>TIMESTAMP PROP(HOSTNAME) MESSAGE"
  • + *
  • Key: "formatType" Value: "logfilepatternreceiver" (format uses the keywords supported by + * LogFilePatternReceiver)
  • + *
+ * + * @return Map of content format keys supporting SyslogLayout + */ + @Override + public Map getContentFormat() { + final Map result = new HashMap<>(); + result.put("structured", "false"); + result.put("formatType", "logfilepatternreceiver"); + result.put("dateFormat", dateFormatOptions[0]); + if (header) { + result.put("format", "TIMESTAMP PROP(HOSTNAME) MESSAGE"); + } else { + result.put("format", "MESSAGE"); + } + return result; + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java index 9522b9e9e66..4eb629c62a7 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java @@ -16,32 +16,36 @@ */ package org.apache.log4j.layout; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.List; - import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.layout.AbstractStringLayout; import org.apache.logging.log4j.core.layout.ByteBufferDestination; import org.apache.logging.log4j.core.util.Transform; -import org.apache.logging.log4j.util.BiConsumer; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.Strings; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + /** * Port of XMLLayout in Log4j 1.x. Provided for compatibility with existing Log4j 1 configurations. * * Originally developed by Ceki Gülcü, Mathias Bogaert. */ -@Plugin(name = "Log4j1XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin public final class Log4j1XmlLayout extends AbstractStringLayout { + /** We yield to the \r\n heresy. */ + private static final String EOL = "\r\n"; + private final boolean locationInfo; private final boolean properties; @@ -84,8 +88,6 @@ public String toSerializable(final LogEvent event) { } private void formatTo(final LogEvent event, final StringBuilder buf) { - // We yield to the \r\n heresy. - buf.append("\r\n"); + buf.append("\">"); + buf.append(EOL); buf.append("\r\n"); + buf.append("]]>"); + buf.append(EOL); final List ndc = event.getContextStack().asList(); if (!ndc.isEmpty()) { buf.append("\r\n"); + buf.append("]]>"); + buf.append(EOL); } @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - final Throwable thrown = event.getThrown(); + final Throwable thrown = event.getThrown(); if (thrown != null) { buf.append("\r\n"); + buf.append("]]>"); + buf.append(EOL); } if (locationInfo) { @@ -129,7 +135,8 @@ private void formatTo(final LogEvent event, final StringBuilder buf) { buf.append(Transform.escapeHtmlTags(source.getFileName())); buf.append("\" line=\""); buf.append(source.getLineNumber()); - buf.append("\"/>\r\n"); + buf.append("\"/>"); + buf.append(EOL); } } @@ -137,23 +144,24 @@ private void formatTo(final LogEvent event, final StringBuilder buf) { final ReadOnlyStringMap contextMap = event.getContextData(); if (!contextMap.isEmpty()) { buf.append("\r\n"); - contextMap.forEach(new BiConsumer() { - @Override - public void accept(final String key, final String val) { - if (val != null) { - buf.append("\r\n"); - } + contextMap.forEach((key, val) -> { + if (val != null) { + buf.append(""); + buf.append(EOL); } }); - buf.append("\r\n"); + buf.append(""); + buf.append(EOL); } } - buf.append("\r\n\r\n"); + buf.append(""); + buf.append(EOL); + buf.append(EOL); } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.java b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.java new file mode 100644 index 00000000000..3e5dc2c515d --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.legacy.core; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.spi.LoggerContext; + +/** + * Delegates to {@code Logger} methods implemented by {@code log4j-core} if appropriate. + */ +public final class CategoryUtil { + + private static org.apache.logging.log4j.core.Logger asCore(final Logger logger) { + return (org.apache.logging.log4j.core.Logger) logger; + } + + private static T get(final Logger logger, final Supplier run, final T defaultValue) { + return isCore(logger) ? run.get() : defaultValue; + } + + /** + * Gets the appenders attached directly to this logger. + * + * @param logger The target logger. + * @return A Map containing the Appender's name as the key and the Appender as the value. + */ + public static Map getAppenders(final Logger logger) { + return get(logger, () -> getDirectAppenders(logger), Collections.emptyMap()); + } + + private static Map getDirectAppenders(final Logger logger) { + return CategoryUtil.getExactLoggerConfig(logger) + .map(LoggerConfig::getAppenders) + .orElse(Collections.emptyMap()); + } + + private static Optional getExactLoggerConfig(final Logger logger) { + return Optional.of(asCore(logger).get()).filter(lc -> logger.getName().equals(lc.getName())); + } + + /** + * Delegates to {@link org.apache.logging.log4j.core.Logger#getFilters()} if appropriate. + * + * @param logger The target logger. + * @return An Iterator over all the Filters associated with the Logger. + */ + public static Iterator getFilters(final Logger logger) { + return get(logger, asCore(logger)::getFilters, null); + } + + /** + * Delegates to {@link org.apache.logging.log4j.core.Logger#getContext()} if appropriate. + * + * @param logger The target logger. + * @return the LoggerContext. + */ + public static LoggerContext getLoggerContext(final Logger logger) { + return get(logger, asCore(logger)::getContext, null); + } + + /** + * Delegates to {@link org.apache.logging.log4j.core.Logger#getParent()} if appropriate. + * + * @param logger The target logger. + * @return The parent Logger. + */ + public static Logger getParent(final Logger logger) { + return get(logger, asCore(logger)::getParent, null); + } + + /** + * Delegates to {@link org.apache.logging.log4j.core.Logger#isAdditive()} if appropriate. + * + * @param logger The target logger. + * @return true if the associated LoggerConfig is additive, false otherwise. + */ + public static boolean isAdditive(final Logger logger) { + return get(logger, asCore(logger)::isAdditive, false); + } + + private static boolean isCore(final Logger logger) { + return logger instanceof org.apache.logging.log4j.core.Logger; + } + + /** + * Delegates to {@link org.apache.logging.log4j.core.Logger#setAdditive(boolean)} if appropriate. + * + * @param logger The target logger. + * @param additive Boolean value to indicate whether the Logger is additive or not. + */ + public static void setAdditivity(final Logger logger, final boolean additive) { + if (isCore(logger)) { + asCore(logger).setAdditive(additive); + } + } + + /** + * Delegates to {@link org.apache.logging.log4j.core.Logger#setLevel(Level)} if appropriate. + * + * @param logger The target logger. + * @param level The Level to use on this Logger, may be null. + */ + public static void setLevel(final Logger logger, final Level level) { + if (isCore(logger)) { + asCore(logger).setLevel(level); + } + } + + /** + * Adds an appender to the logger. This method requires a check for the presence + * of Log4j Core or it will cause a {@code ClassNotFoundException}. + * + * @param logger The target logger. + * @param appender A Log4j2 appender. + */ + public static void addAppender(final Logger logger, final Appender appender) { + if (appender instanceof AppenderAdapter.Adapter) { + appender.start(); + } + asCore(logger).addAppender(appender); + } + + /** + * Sends the event to all appenders directly connected with the logger. This + * method requires a check for the presence of Log4j Core or it will cause a + * {@code ClassNotFoundException}. + * + * @param logger The target logger. + * @param event The event to send. + */ + public static void log(final Logger logger, final LogEvent event) { + getExactLoggerConfig(logger).ifPresent(lc -> lc.log(event)); + } + + private CategoryUtil() { + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/ContextUtil.java b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/ContextUtil.java new file mode 100644 index 00000000000..89809809c02 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/ContextUtil.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.legacy.core; + +import org.apache.logging.log4j.spi.LoggerContext; + +/** + * Delegates to {@code LoggerContext} methods implemented by {@code log4j-core} if appropriate. + */ +public final class ContextUtil { + + /** + * Delegates to {@link org.apache.logging.log4j.core.LoggerContext#reconfigure()} if appropriate. + * + * @param loggerContext The target logger context. + */ + public static void reconfigure(LoggerContext loggerContext) { + if (loggerContext instanceof org.apache.logging.log4j.core.LoggerContext) { + ((org.apache.logging.log4j.core.LoggerContext) loggerContext).reconfigure(); + } + } + + /** + * Delegates to {@link org.apache.logging.log4j.core.LoggerContext#close()} if appropriate. + * + * @param loggerContext The target logger context. + */ + public static void shutdown(LoggerContext loggerContext) { + if (loggerContext instanceof org.apache.logging.log4j.core.LoggerContext) { + ((org.apache.logging.log4j.core.LoggerContext) loggerContext).close(); + } + } + + private ContextUtil() { + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/or/DefaultRenderer.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/DefaultRenderer.java new file mode 100644 index 00000000000..e8f632181e1 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/DefaultRenderer.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.or; + +/** + * The default ObjectRenderer renders objects by calling their {@code toString()} method. + * + * @since 1.0 + */ +class DefaultRenderer implements ObjectRenderer { + + DefaultRenderer() { + } + + /** + * Render the object passed as parameter by calling its {@code toString()} method. + */ + public String doRender(final Object o) { + try { + return o.toString(); + } catch (Exception ex) { + return ex.toString(); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/or/ObjectRenderer.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/ObjectRenderer.java new file mode 100644 index 00000000000..4e627c24acb --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/ObjectRenderer.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.or; + +/** + * Converts objects to Strings. + */ +public interface ObjectRenderer { + /** + * Render the object passed as parameter as a String. + * @param o The object to render. + * @return The String representation of the object. + */ + String doRender(Object o); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/or/RendererMap.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/RendererMap.java new file mode 100644 index 00000000000..b70b1a56751 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/RendererMap.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.or; + +import java.util.Hashtable; + +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.RendererSupport; +import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Map class objects to an {@link ObjectRenderer}. + * + * @author Ceki Gülcü + * @since version 1.0 + */ +public class RendererMap { + + Hashtable map; + + static ObjectRenderer defaultRenderer = new DefaultRenderer(); + + public RendererMap() { + map = new Hashtable(); + } + + /** + * Add a renderer to a hierarchy passed as parameter. + */ + static public void addRenderer(RendererSupport repository, String renderedClassName, String renderingClassName) { + StatusLogger.getLogger().debug("Rendering class: [" + renderingClassName + "], Rendered class: [" + renderedClassName + "]."); + ObjectRenderer renderer = (ObjectRenderer) OptionConverter.instantiateByClassName(renderingClassName, ObjectRenderer.class, null); + if (renderer == null) { + StatusLogger.getLogger().error("Could not instantiate renderer [" + renderingClassName + "]."); + return; + } + try { + Class renderedClass = Loader.loadClass(renderedClassName); + repository.setRenderer(renderedClass, renderer); + } catch (ClassNotFoundException e) { + StatusLogger.getLogger().error("Could not find class [" + renderedClassName + "].", e); + } + } + + /** + * Find the appropriate renderer for the class type of the o parameter. This is accomplished by calling the + * {@link #get(Class)} method. Once a renderer is found, it is applied on the object o and the result is + * returned as a {@link String}. + */ + public String findAndRender(Object o) { + if (o == null) + return null; + else + return get(o.getClass()).doRender(o); + } + + /** + * Syntactic sugar method that calls {@link #get(Class)} with the class of the object parameter. + */ + public ObjectRenderer get(Object o) { + if (o == null) + return null; + else + return get(o.getClass()); + } + + /** + * Search the parents of clazz for a renderer. The renderer closest in the hierarchy will be returned. If + * no renderers could be found, then the default renderer is returned. + * + *

+ * The search first looks for a renderer configured for clazz. If a renderer could not be found, then the + * search continues by looking at all the interfaces implemented by clazz including the super-interfaces of + * each interface. If a renderer cannot be found, then the search looks for a renderer defined for the parent + * (superclass) of clazz. If that fails, then all the interfaces implemented by the parent of + * clazz are searched and so on. + * + *

+ * For example, if A0, A1, A2 are classes and X0, X1, X2, Y0, Y1 are interfaces where A2 extends A1 which in turn + * extends A0 and similarly X2 extends X1 which extends X0 and Y1 extends Y0. Let us also assume that A1 implements the + * Y0 interface and that A2 implements the X2 interface. + * + *

+ * The table below shows the results returned by the get(A2.class) method depending on the renderers added + * to the map. + * + *

+ * + * + * + * + * + * + * + * + * + *
Added renderersValue returned by get(A2.class)
A0Renderer + * A0Renderer + * + *
A0Renderer, A1Renderer + * A1Renderer + * + *
X0Renderer + * X0Renderer + * + *
A1Renderer, X0Renderer + * X0Renderer + * + *
+ * + *

+ * This search algorithm is not the most natural, although it is particularly easy to implement. Future log4j versions + * may implement a more intuitive search algorithm. However, the present algorithm should be acceptable in the + * vast majority of circumstances. + * + */ + public ObjectRenderer get(Class clazz) { + // System.out.println("\nget: "+clazz); + ObjectRenderer r = null; + for (Class c = clazz; c != null; c = c.getSuperclass()) { + // System.out.println("Searching for class: "+c); + r = (ObjectRenderer) map.get(c); + if (r != null) { + return r; + } + r = searchInterfaces(c); + if (r != null) + return r; + } + return defaultRenderer; + } + + ObjectRenderer searchInterfaces(Class c) { + // System.out.println("Searching interfaces of class: "+c); + + ObjectRenderer r = (ObjectRenderer) map.get(c); + if (r != null) { + return r; + } + Class[] ia = c.getInterfaces(); + for (int i = 0; i < ia.length; i++) { + r = searchInterfaces(ia[i]); + if (r != null) + return r; + } + return null; + } + + public ObjectRenderer getDefaultRenderer() { + return defaultRenderer; + } + + public void clear() { + map.clear(); + } + + /** + * Register an {@link ObjectRenderer} for clazz. + */ + public void put(Class clazz, ObjectRenderer or) { + map.put(clazz, or); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.java new file mode 100644 index 00000000000..959d927e890 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.or; + +import org.apache.log4j.Layout; + +/** + */ +public class ThreadGroupRenderer implements ObjectRenderer { + + @Override + public + String doRender(Object obj) { + if(obj instanceof ThreadGroup) { + StringBuilder sb = new StringBuilder(); + ThreadGroup threadGroup = (ThreadGroup) obj; + sb.append("java.lang.ThreadGroup[name="); + sb.append(threadGroup.getName()); + sb.append(", maxpri="); + sb.append(threadGroup.getMaxPriority()); + sb.append("]"); + Thread[] threads = new Thread[threadGroup.activeCount()]; + threadGroup.enumerate(threads); + for (Thread thread : threads) { + sb.append(Layout.LINE_SEP); + sb.append(" Thread=["); + sb.append(thread.getName()); + sb.append(","); + sb.append(thread.getPriority()); + sb.append(","); + sb.append(thread.isDaemon()); + sb.append("]"); + } + return sb.toString(); + } + try { + // this is the best we can do + return obj.toString(); + } catch(Exception ex) { + return ex.toString(); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/or/jms/MessageRenderer.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/jms/MessageRenderer.java new file mode 100644 index 00000000000..14e1594be96 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/jms/MessageRenderer.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.or.jms; + +import javax.jms.DeliveryMode; +import javax.jms.JMSException; +import javax.jms.Message; + +import org.apache.log4j.or.ObjectRenderer; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Log4j 1.x JMS Message Renderer + */ +public class MessageRenderer implements ObjectRenderer { + private static final Logger LOGGER = StatusLogger.getLogger(); + + /** + Render a {@link javax.jms.Message}. + */ + @Override + public + String doRender(Object obj) { + if (obj instanceof Message) { + StringBuilder sb = new StringBuilder(); + Message message = (Message) obj; + try { + sb.append("DeliveryMode="); + switch(message.getJMSDeliveryMode()) { + case DeliveryMode.NON_PERSISTENT : + sb.append("NON_PERSISTENT"); + break; + case DeliveryMode.PERSISTENT : + sb.append("PERSISTENT"); + break; + default: sb.append("UNKNOWN"); + } + sb.append(", CorrelationID="); + sb.append(message.getJMSCorrelationID()); + + sb.append(", Destination="); + sb.append(message.getJMSDestination()); + + sb.append(", Expiration="); + sb.append(message.getJMSExpiration()); + + sb.append(", MessageID="); + sb.append(message.getJMSMessageID()); + + sb.append(", Priority="); + sb.append(message.getJMSPriority()); + + sb.append(", Redelivered="); + sb.append(message.getJMSRedelivered()); + + sb.append(", ReplyTo="); + sb.append(message.getJMSReplyTo()); + + sb.append(", Timestamp="); + sb.append(message.getJMSTimestamp()); + + sb.append(", Type="); + sb.append(message.getJMSType()); + + } catch(JMSException e) { + LOGGER.error("Could not parse Message.", e); + } + return sb.toString(); + } + return obj.toString(); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/FormattingInfo.java b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/FormattingInfo.java new file mode 100644 index 00000000000..d8faaf415e1 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/FormattingInfo.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.pattern; + +/** + * Modifies the output of a pattern converter for a specified minimum and maximum width and alignment. + */ +public final class FormattingInfo { + /** + * Array of spaces. + */ + private static final char[] SPACES = new char[] {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}; + + /** + * Default instance. + */ + private static final FormattingInfo DEFAULT = new FormattingInfo(false, 0, Integer.MAX_VALUE); + + /** + * Gets default instance. + * + * @return default instance. + */ + public static FormattingInfo getDefault() { + return DEFAULT; + } + + /** + * Minimum length. + */ + private final int minLength; + + /** + * Maximum length. + */ + private final int maxLength; + + /** + * Alignment. + */ + private final boolean leftAlign; + + /** + * Creates new instance. + * + * @param leftAlign left align if true. + * @param minLength minimum length. + * @param maxLength maximum length. + */ + public FormattingInfo(final boolean leftAlign, final int minLength, final int maxLength) { + this.leftAlign = leftAlign; + this.minLength = minLength; + this.maxLength = maxLength; + } + + /** + * Adjust the content of the buffer based on the specified lengths and alignment. + * + * @param fieldStart start of field in buffer. + * @param buffer buffer to be modified. + */ + public void format(final int fieldStart, final StringBuffer buffer) { + final int rawLength = buffer.length() - fieldStart; + + if (rawLength > maxLength) { + buffer.delete(fieldStart, buffer.length() - maxLength); + } else if (rawLength < minLength) { + if (leftAlign) { + final int fieldEnd = buffer.length(); + buffer.setLength(fieldStart + minLength); + + for (int i = fieldEnd; i < buffer.length(); i++) { + buffer.setCharAt(i, ' '); + } + } else { + int padLength = minLength - rawLength; + + for (; padLength > 8; padLength -= 8) { + buffer.insert(fieldStart, SPACES); + } + + buffer.insert(fieldStart, SPACES, 0, padLength); + } + } + } + + /** + * Get maximum length. + * + * @return maximum length. + */ + public int getMaxLength() { + return maxLength; + } + + /** + * Get minimum length. + * + * @return minimum length. + */ + public int getMinLength() { + return minLength; + } + + /** + * Determine if left aligned. + * + * @return true if left aligned. + */ + public boolean isLeftAligned() { + return leftAlign; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1LevelPatternConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1LevelPatternConverter.java new file mode 100644 index 00000000000..3be8fb9e1e8 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1LevelPatternConverter.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.pattern; + +import org.apache.log4j.helpers.OptionConverter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; + +/** + * Outputs the Log4j 1.x level name. + */ +@Namespace(PatternConverter.CATEGORY) +@Plugin("Log4j1LevelPatternConverter") +@ConverterKeys({ "v1Level" }) +public class Log4j1LevelPatternConverter extends LogEventPatternConverter { + + private static final Log4j1LevelPatternConverter INSTANCE = new Log4j1LevelPatternConverter(); + + public static Log4j1LevelPatternConverter newInstance(final String[] options) { + return INSTANCE; + } + + private Log4j1LevelPatternConverter() { + super("Log4j1Level", "v1Level"); + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + toAppendTo.append(OptionConverter.convertLevel(event.getLevel()).toString()); + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java index b4ae0c5e93b..f5e08ad48c5 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java @@ -17,10 +17,11 @@ package org.apache.log4j.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.pattern.ConverterKeys; import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.TriConsumer; /** @@ -28,7 +29,8 @@ * output the entire contents of the properties, or to output the value of a specific key * within the property bundle when this pattern converter has the option set. */ -@Plugin(name = "Log4j1MdcPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("Log4j1MdcPatternConverter") @ConverterKeys({ "properties" }) public final class Log4j1MdcPatternConverter extends LogEventPatternConverter { /** @@ -79,10 +81,5 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) { } } - private static TriConsumer APPEND_EACH = new TriConsumer() { - @Override - public void accept(final String key, final Object value, final StringBuilder toAppendTo) { - toAppendTo.append('{').append(key).append(',').append(value).append('}'); - } - }; + private static TriConsumer APPEND_EACH = (key, value, toAppendTo) -> toAppendTo.append('{').append(key).append(',').append(value).append('}'); } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java index 405db0093c8..d6c3e02ff1c 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java @@ -17,10 +17,11 @@ package org.apache.log4j.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.pattern.ConverterKeys; import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.Strings; import java.util.List; @@ -29,7 +30,8 @@ /** * Returns the event's NDC in a StringBuilder. */ -@Plugin(name = "Log4j1NdcPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("Log4j1NdcPatternConverter") @ConverterKeys({ "ndc" }) public final class Log4j1NdcPatternConverter extends LogEventPatternConverter { /** diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/NameAbbreviator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/NameAbbreviator.java new file mode 100644 index 00000000000..8905ecbffd2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/NameAbbreviator.java @@ -0,0 +1,345 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.pattern; + +import java.util.ArrayList; +import java.util.List; + +/** + * NameAbbreviator generates abbreviated logger and class names. + */ +public abstract class NameAbbreviator { + + /** + * Abbreviator that drops starting path elements. + */ + private static class DropElementAbbreviator extends NameAbbreviator { + /** + * Maximum number of path elements to output. + */ + private final int count; + + /** + * Create new instance. + * + * @param count maximum number of path elements to output. + */ + public DropElementAbbreviator(final int count) { + this.count = count; + } + + /** + * Abbreviate name. + * + * @param buf buffer to append abbreviation. + * @param nameStart start of name to abbreviate. + */ + @Override + public void abbreviate(final int nameStart, final StringBuffer buf) { + int i = count; + for (int pos = buf.indexOf(".", nameStart); pos != -1; pos = buf.indexOf(".", pos + 1)) { + if (--i == 0) { + buf.delete(nameStart, pos + 1); + break; + } + } + } + } + + /** + * Abbreviator that drops starting path elements. + */ + private static class MaxElementAbbreviator extends NameAbbreviator { + /** + * Maximum number of path elements to output. + */ + private final int count; + + /** + * Create new instance. + * + * @param count maximum number of path elements to output. + */ + public MaxElementAbbreviator(final int count) { + this.count = count; + } + + /** + * Abbreviate name. + * + * @param buf buffer to append abbreviation. + * @param nameStart start of name to abbreviate. + */ + @Override + public void abbreviate(final int nameStart, final StringBuffer buf) { + // We substract 1 from 'len' when assigning to 'end' to avoid out of + // bounds exception in return r.substring(end+1, len). This can happen if + // precision is 1 and the category name ends with a dot. + int end = buf.length() - 1; + + final String bufString = buf.toString(); + for (int i = count; i > 0; i--) { + end = bufString.lastIndexOf(".", end - 1); + + if ((end == -1) || (end < nameStart)) { + return; + } + } + + buf.delete(nameStart, end + 1); + } + } + + /** + * Abbreviator that simply appends full name to buffer. + */ + private static class NOPAbbreviator extends NameAbbreviator { + /** + * Constructor. + */ + public NOPAbbreviator() { + } + + /** + * {@inheritDoc} + */ + @Override + public void abbreviate(final int nameStart, final StringBuffer buf) { + } + } + + /** + * Pattern abbreviator. + * + * + */ + private static class PatternAbbreviator extends NameAbbreviator { + /** + * Element abbreviation patterns. + */ + private final PatternAbbreviatorFragment[] fragments; + + /** + * Create PatternAbbreviator. + * + * @param fragments element abbreviation patterns. + */ + public PatternAbbreviator(final List fragments) { + if (fragments.size() == 0) { + throw new IllegalArgumentException("fragments must have at least one element"); + } + + this.fragments = new PatternAbbreviatorFragment[fragments.size()]; + fragments.toArray(this.fragments); + } + + /** + * Abbreviate name. + * + * @param buf buffer that abbreviated name is appended. + * @param nameStart start of name. + */ + @Override + public void abbreviate(final int nameStart, final StringBuffer buf) { + // + // all non-terminal patterns are executed once + // + int pos = nameStart; + + for (int i = 0; (i < (fragments.length - 1)) && (pos < buf.length()); i++) { + pos = fragments[i].abbreviate(buf, pos); + } + + // + // last pattern in executed repeatedly + // + final PatternAbbreviatorFragment terminalFragment = fragments[fragments.length - 1]; + + while ((pos < buf.length()) && (pos >= 0)) { + pos = terminalFragment.abbreviate(buf, pos); + } + } + } + + /** + * Fragment of an pattern abbreviator. + * + */ + private static class PatternAbbreviatorFragment { + /** + * Count of initial characters of element to output. + */ + private final int charCount; + + /** + * Character used to represent dropped characters. '\0' indicates no representation of dropped characters. + */ + private final char ellipsis; + + /** + * Creates a PatternAbbreviatorFragment. + * + * @param charCount number of initial characters to preserve. + * @param ellipsis character to represent elimination of characters, '\0' if no ellipsis is desired. + */ + public PatternAbbreviatorFragment(final int charCount, final char ellipsis) { + this.charCount = charCount; + this.ellipsis = ellipsis; + } + + /** + * Abbreviate element of name. + * + * @param buf buffer to receive element. + * @param startPos starting index of name element. + * @return starting index of next element. + */ + public int abbreviate(final StringBuffer buf, final int startPos) { + int nextDot = buf.toString().indexOf(".", startPos); + + if (nextDot != -1) { + if ((nextDot - startPos) > charCount) { + buf.delete(startPos + charCount, nextDot); + nextDot = startPos + charCount; + + if (ellipsis != '\0') { + buf.insert(nextDot, ellipsis); + nextDot++; + } + } + + nextDot++; + } + + return nextDot; + } + } + + /** + * Default (no abbreviation) abbreviator. + */ + private static final NameAbbreviator DEFAULT = new NOPAbbreviator(); + + /** + * Gets an abbreviator. + * + * For example, "%logger{2}" will output only 2 elements of the logger name, %logger{-2} will drop 2 elements from the + * logger name, "%logger{1.}" will output only the first character of the non-final elements in the name, + * "%logger{1~.2~} will output the first character of the first element, two characters of the second and subsequent + * elements and will use a tilde to indicate abbreviated characters. + * + * @param pattern abbreviation pattern. + * @return abbreviator, will not be null. + */ + public static NameAbbreviator getAbbreviator(final String pattern) { + if (pattern.length() > 0) { + // if pattern is just spaces and numbers then + // use MaxElementAbbreviator + final String trimmed = pattern.trim(); + + if (trimmed.length() == 0) { + return DEFAULT; + } + + int i = 0; + if (trimmed.length() > 0) { + if (trimmed.charAt(0) == '-') { + i++; + } + for (; (i < trimmed.length()) && (trimmed.charAt(i) >= '0') && (trimmed.charAt(i) <= '9'); i++) { + } + } + + // + // if all blanks and digits + // + if (i == trimmed.length()) { + final int elements = Integer.parseInt(trimmed); + if (elements >= 0) { + return new MaxElementAbbreviator(elements); + } else { + return new DropElementAbbreviator(-elements); + } + } + + final ArrayList fragments = new ArrayList(5); + char ellipsis; + int charCount; + int pos = 0; + + while ((pos < trimmed.length()) && (pos >= 0)) { + int ellipsisPos = pos; + + if (trimmed.charAt(pos) == '*') { + charCount = Integer.MAX_VALUE; + ellipsisPos++; + } else { + if ((trimmed.charAt(pos) >= '0') && (trimmed.charAt(pos) <= '9')) { + charCount = trimmed.charAt(pos) - '0'; + ellipsisPos++; + } else { + charCount = 0; + } + } + + ellipsis = '\0'; + + if (ellipsisPos < trimmed.length()) { + ellipsis = trimmed.charAt(ellipsisPos); + + if (ellipsis == '.') { + ellipsis = '\0'; + } + } + + fragments.add(new PatternAbbreviatorFragment(charCount, ellipsis)); + pos = trimmed.indexOf(".", pos); + + if (pos == -1) { + break; + } + + pos++; + } + + return new PatternAbbreviator(fragments); + } + + // + // no matching abbreviation, return defaultAbbreviator + // + return DEFAULT; + } + + /** + * Gets default abbreviator. + * + * @return default abbreviator. + */ + public static NameAbbreviator getDefaultAbbreviator() { + return DEFAULT; + } + + /** + * Abbreviates a name in a StringBuffer. + * + * @param nameStart starting position of name in buf. + * @param buf buffer, may not be null. + */ + public abstract void abbreviate(final int nameStart, final StringBuffer buf); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java new file mode 100644 index 00000000000..f16ed3803bd --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.rewrite; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.bridge.LogEventAdapter; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LocationInfo; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.SortedArrayStringMap; + +/** + * This policy rewrites events where the message of the + * original event implements java.util.Map. + * All other events are passed through unmodified. + * If the map contains a "message" entry, the value will be + * used as the message for the rewritten event. The rewritten + * event will have a property set that is the combination of the + * original property set and the other members of the message map. + * If both the original property set and the message map + * contain the same entry, the value from the message map + * will overwrite the original property set. + *

+ * The combination of the RewriteAppender and this policy + * performs the same actions as the MapFilter from log4j 1.3. + *

+ */ +public class MapRewritePolicy implements RewritePolicy { + /** + * {@inheritDoc} + */ + @Override + public LoggingEvent rewrite(final LoggingEvent source) { + Object msg = source.getMessage(); + if (msg instanceof MapMessage || msg instanceof Map) { + Map props = source.getProperties() != null ? new HashMap<>(source.getProperties()) + : new HashMap<>(); + @SuppressWarnings("unchecked") + Map eventProps = msg instanceof Map ? (Map) msg : ((MapMessage) msg).getData(); + // + // if the map sent in the logging request + // has "message" entry, use that as the message body + // otherwise, use the entire map. + // + Message newMessage = null; + Object newMsg = eventProps.get("message"); + if (newMsg != null) { + newMessage = new SimpleMessage(newMsg.toString()); + for (Map.Entry entry : eventProps.entrySet()) { + if (!("message".equals(entry.getKey()))) { + props.put(entry.getKey(), entry.getValue().toString()); + } + } + } else { + return source; + } + + LogEvent event; + if (source instanceof LogEventAdapter) { + event = new Log4jLogEvent.Builder(((LogEventAdapter) source).getEvent()) + .setMessage(newMessage) + .setContextData(new SortedArrayStringMap(props)) + .build(); + } else { + LocationInfo info = source.getLocationInformation(); + StackTraceElement element = new StackTraceElement(info.getClassName(), info.getMethodName(), + info.getFileName(), Integer.parseInt(info.getLineNumber())); + Thread thread = getThread(source.getThreadName()); + long threadId = thread != null ? thread.getId() : 0; + int threadPriority = thread != null ? thread.getPriority() : 0; + event = Log4jLogEvent.newBuilder() + .setContextData(new SortedArrayStringMap(props)) + .setLevel(OptionConverter.convertLevel(source.getLevel())) + .setLoggerFqcn(source.getFQNOfLoggerClass()) + .setMarker(null) + .setMessage(newMessage) + .setSource(element) + .setLoggerName(source.getLoggerName()) + .setThreadName(source.getThreadName()) + .setThreadId(threadId) + .setThreadPriority(threadPriority) + .setThrown(source.getThrowableInformation().getThrowable()) + .setTimeMillis(source.getTimeStamp()) + .setNanoTime(0) + .setThrownProxy(null) + .build(); + } + return new LogEventAdapter(event); + } + return source; + + } + + private Thread getThread(String name) { + for (Thread thread : Thread.getAllStackTraces().keySet()) { + if (thread.getName().equals(name)) { + return thread; + } + } + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java new file mode 100644 index 00000000000..a84deacb4d6 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.rewrite; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import org.apache.log4j.bridge.LogEventAdapter; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LocationInfo; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.SortedArrayStringMap; + +/** + * This policy rewrites events by adding + * a user-specified list of properties to the event. + * Existing properties are not modified. + *

+ * The combination of the RewriteAppender and this policy + * performs the same actions as the PropertyFilter from log4j 1.3. + *

+ */ +public class PropertyRewritePolicy implements RewritePolicy { + private Map properties = Collections.EMPTY_MAP; + + public PropertyRewritePolicy() { + } + + /** + * Set a string representing the property name/value pairs. + *

+ * Form: + *

+ *
+     * propname1=propvalue1,propname2=propvalue2
+     * 
+ * + * @param properties The properties. + */ + public void setProperties(String properties) { + Map newMap = new HashMap<>(); + StringTokenizer pairs = new StringTokenizer(properties, ","); + while (pairs.hasMoreTokens()) { + StringTokenizer entry = new StringTokenizer(pairs.nextToken(), "="); + newMap.put(entry.nextElement().toString().trim(), entry.nextElement().toString().trim()); + } + synchronized (this) { + this.properties = newMap; + } + } + + /** + * {@inheritDoc} + */ + @Override + public LoggingEvent rewrite(final LoggingEvent source) { + if (!properties.isEmpty()) { + Map rewriteProps = source.getProperties() != null ? new HashMap<>(source.getProperties()) + : new HashMap<>(); + for (Map.Entry entry : properties.entrySet()) { + if (!rewriteProps.containsKey(entry.getKey())) { + rewriteProps.put(entry.getKey(), entry.getValue()); + } + } + LogEvent event; + if (source instanceof LogEventAdapter) { + event = new Log4jLogEvent.Builder(((LogEventAdapter) source).getEvent()) + .setContextData(new SortedArrayStringMap(rewriteProps)) + .build(); + } else { + LocationInfo info = source.getLocationInformation(); + StackTraceElement element = new StackTraceElement(info.getClassName(), info.getMethodName(), + info.getFileName(), Integer.parseInt(info.getLineNumber())); + Thread thread = getThread(source.getThreadName()); + long threadId = thread != null ? thread.getId() : 0; + int threadPriority = thread != null ? thread.getPriority() : 0; + event = Log4jLogEvent.newBuilder() + .setContextData(new SortedArrayStringMap(rewriteProps)) + .setLevel(OptionConverter.convertLevel(source.getLevel())) + .setLoggerFqcn(source.getFQNOfLoggerClass()) + .setMarker(null) + .setMessage(new SimpleMessage(source.getRenderedMessage())) + .setSource(element) + .setLoggerName(source.getLoggerName()) + .setThreadName(source.getThreadName()) + .setThreadId(threadId) + .setThreadPriority(threadPriority) + .setThrown(source.getThrowableInformation().getThrowable()) + .setTimeMillis(source.getTimeStamp()) + .setNanoTime(0) + .setThrownProxy(null) + .build(); + } + return new LogEventAdapter(event); + } + return source; + } + + private Thread getThread(String name) { + for (Thread thread : Thread.getAllStackTraces().keySet()) { + if (thread.getName().equals(name)) { + return thread; + } + } + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java new file mode 100644 index 00000000000..9570218e787 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.rewrite; + +import org.apache.log4j.spi.LoggingEvent; + +/** + * This interface is implemented to provide a rewrite + * strategy for RewriteAppender. RewriteAppender will + * call the rewrite method with a source logging event. + * The strategy may return that event, create a new event + * or return null to suppress the logging request. + */ +public interface RewritePolicy { + /** + * Rewrite a logging event. + * @param source a logging event that may be returned or + * used to create a new logging event. + * @return a logging event or null to suppress processing. + */ + LoggingEvent rewrite(final LoggingEvent source); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/AppenderAttachable.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/AppenderAttachable.java new file mode 100644 index 00000000000..261c5bb84a9 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/AppenderAttachable.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + +import java.util.Enumeration; + +import org.apache.log4j.Appender; + +/** + * Interface for attaching appenders to objects. + */ +public interface AppenderAttachable { + + /** + * Add an appender. + * @param newAppender The Appender to add. + */ + void addAppender(Appender newAppender); + + /** + * Get all previously added appenders as an Enumeration. + * @return The Enumeration of the Appenders. + */ + Enumeration getAllAppenders(); + + /** + * Get an appender by name. + * @param name The name of the Appender. + * @return The Appender. + */ + Appender getAppender(String name); + + + /** + * Returns true if the specified appender is in list of + * attached, false otherwise. + * @param appender The Appender to check. + * @return true if the Appender is attached. + * + * @since 1.2 + */ + boolean isAttached(Appender appender); + + /** + * Remove all previously added appenders. + */ + void removeAllAppenders(); + + + /** + * Remove the appender passed as parameter from the list of appenders. + * @param appender The Appender to remove. + */ + void removeAppender(Appender appender); + + + /** + * Remove the appender with the name passed as parameter from the + * list of appenders. + * @param name The name of the Appender to remove. + */ + void removeAppender(String name); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Configurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Configurator.java new file mode 100644 index 00000000000..15b9c6eda9a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Configurator.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.spi; + +import java.io.InputStream; +import java.net.URL; + +/** + * Log4j 1.x Configurator interface. + */ +public interface Configurator { + + /** + * Special level value signifying inherited behavior. The current value of this string constant is inherited. + * {@link #NULL} is a synonym. + */ + public static final String INHERITED = "inherited"; + + /** + * Special level signifying inherited behavior, same as {@link #INHERITED}. The current value of this string constant + * is null. + */ + public static final String NULL = "null"; + + /** + * Interpret a resource pointed by a InputStream and set up log4j accordingly. + * + * The configuration is done relative to the hierarchy parameter. + * + * @param inputStream The InputStream to parse + * @param loggerRepository The hierarchy to operation upon. + * + * @since 1.2.17 + */ + void doConfigure(InputStream inputStream, final LoggerRepository loggerRepository); + + /** + * Interpret a resource pointed by a URL and set up log4j accordingly. + * + * The configuration is done relative to the hierarchy parameter. + * + * @param url The URL to parse + * @param loggerRepository The hierarchy to operation upon. + */ + void doConfigure(URL url, final LoggerRepository loggerRepository); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/DefaultRepositorySelector.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/DefaultRepositorySelector.java new file mode 100644 index 00000000000..e4313732677 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/DefaultRepositorySelector.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + +public class DefaultRepositorySelector implements RepositorySelector { + + final LoggerRepository repository; + + public DefaultRepositorySelector(final LoggerRepository repository) { + this.repository = repository; + } + + @Override + public LoggerRepository getLoggerRepository() { + return repository; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorCode.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorCode.java new file mode 100644 index 00000000000..752b077a849 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorCode.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + + +/** + This interface defines commonly encountered error codes. + */ +public interface ErrorCode { + + public final int GENERIC_FAILURE = 0; + public final int WRITE_FAILURE = 1; + public final int FLUSH_FAILURE = 2; + public final int CLOSE_FAILURE = 3; + public final int FILE_OPEN_FAILURE = 4; + public final int MISSING_LAYOUT = 5; + public final int ADDRESS_PARSE_FAILURE = 6; +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorHandler.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorHandler.java index 2e6410391b7..f3b11f95169 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorHandler.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorHandler.java @@ -49,7 +49,7 @@ public interface ErrorHandler { /** * Equivalent to the {@link #error(String, Exception, int, - * LoggingEvent)} with the the event parameter set to + * LoggingEvent)} with the event parameter set to * null. * * @param message The message associated with the error. diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java index 9bba50cbcf8..91917abeae9 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java @@ -21,6 +21,16 @@ */ public abstract class Filter { + static { + boolean temp; + try { + temp = Class.forName("org.apache.logging.log4j.core.Filter") != null; + } catch (Exception | LinkageError e) { + temp = false; + } + isCorePresent = temp; + } + /** * The log event must be dropped immediately without consulting * with the remaining filters, if any, in the chain. @@ -47,11 +57,14 @@ public abstract class Filter { @Deprecated public Filter next; + private static final boolean isCorePresent; + /** * Usually filters options become active when set. We provide a * default do-nothing implementation for convenience. */ public void activateOptions() { + // noop } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LocationInfo.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LocationInfo.java new file mode 100644 index 00000000000..4b20f2deb0f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LocationInfo.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.spi; + +import java.io.Serializable; +import java.util.Objects; + +/** + * The internal representation of caller location information. + * + * @since 0.8.3 + */ +public class LocationInfo implements Serializable { + + /** + * When location information is not available the constant NA is returned. Current value of this string + * constant is ?. + */ + public final static String NA = "?"; + + static final long serialVersionUID = -1325822038990805636L; + + private final StackTraceElement stackTraceElement; + + public String fullInfo; + + /** + * Constructs a new instance. + */ + public LocationInfo(final StackTraceElement stackTraceElement) { + this.stackTraceElement = Objects.requireNonNull(stackTraceElement, "stackTraceElement"); + this.fullInfo = stackTraceElement.toString(); + } + + /** + * Constructs a new instance. + * + * @param file source file name + * @param declaringClass class name + * @param methodName method + * @param line source line number + * + * @since 1.2.15 + */ + public LocationInfo(final String file, final String declaringClass, final String methodName, final String line) { + this(new StackTraceElement(declaringClass, methodName, file, Integer.parseInt(line))); + } + + /** + * Constructs a new instance. + */ + public LocationInfo(final Throwable throwable, final String fqnOfCallingClass) { + String declaringClass = null, methodName = null, file = null, line = null; + if (throwable != null && fqnOfCallingClass != null) { + final StackTraceElement[] elements = throwable.getStackTrace(); + String prevClass = NA; + for (int i = elements.length - 1; i >= 0; i--) { + final String thisClass = elements[i].getClassName(); + if (fqnOfCallingClass.equals(thisClass)) { + final int caller = i + 1; + if (caller < elements.length) { + declaringClass = prevClass; + methodName = elements[caller].getMethodName(); + file = elements[caller].getFileName(); + if (file == null) { + file = NA; + } + final int lineNo = elements[caller].getLineNumber(); + if (lineNo < 0) { + line = NA; + } else { + line = String.valueOf(lineNo); + } + final StringBuilder builder = new StringBuilder(); + builder.append(declaringClass); + builder.append("."); + builder.append(methodName); + builder.append("("); + builder.append(file); + builder.append(":"); + builder.append(line); + builder.append(")"); + this.fullInfo = builder.toString(); + } + break; + } + prevClass = thisClass; + } + } + this.stackTraceElement = new StackTraceElement(declaringClass, methodName, file, Integer.parseInt(line)); + this.fullInfo = stackTraceElement.toString(); + } + + /** + * Gets the fully qualified class name of the caller making the logging request. + */ + public String getClassName() { + return stackTraceElement.getClassName(); + } + + /** + * Gets the file name of the caller. + */ + public String getFileName() { + return stackTraceElement.getFileName(); + } + + /** + * Gets the line number of the caller. + */ + public String getLineNumber() { + return Integer.toString(stackTraceElement.getLineNumber()); + } + + /** + * Gets the method name of the caller. + */ + public String getMethodName() { + return stackTraceElement.getMethodName(); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerFactory.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerFactory.java index e2f37080572..69c30db9dd4 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerFactory.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggerFactory.java @@ -19,7 +19,6 @@ import org.apache.log4j.Logger; /** - * * Implement this interface to create new instances of Logger or a sub-class of Logger. * *

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggingEvent.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggingEvent.java index 5f4b1727b90..73dd528efca 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggingEvent.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggingEvent.java @@ -16,8 +16,216 @@ */ package org.apache.log4j.spi; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Category; +import org.apache.log4j.Level; +import org.apache.log4j.Priority; +import org.apache.log4j.bridge.LogEventAdapter; + /** - * No-op version of Log4j 1.2 LoggingEvent. + * No-op version of Log4j 1.2 LoggingEvent. This class is not directly used by Log4j 1.x clients but is used by + * the Log4j 2 LogEvent adapter to be compatible with Log4j 1.x components. */ public class LoggingEvent { + + /** + Returns the time when the application started, in milliseconds + elapsed since 01.01.1970. + @return the JVM start time. + */ + public static long getStartTime() { + return LogEventAdapter.getStartTime(); + } + + /** + * The number of milliseconds elapsed from 1/1/1970 until logging event was created. + */ + public final long timeStamp; + + /** + * Constructs a new instance. + */ + public LoggingEvent() { + timeStamp = System.currentTimeMillis(); + } + + /** + * Create new instance. + * + * @since 1.2.15 + * @param fqnOfCategoryClass Fully qualified class name of Logger implementation. + * @param logger The logger generating this event. + * @param timeStamp the timestamp of this logging event + * @param level The level of this event. + * @param message The message of this event. + * @param threadName thread name + * @param throwable The throwable of this event. + * @param ndc Nested diagnostic context + * @param info Location info + * @param properties MDC properties + */ + public LoggingEvent(final String fqnOfCategoryClass, final Category logger, final long timeStamp, final Level level, final Object message, + final String threadName, final ThrowableInformation throwable, final String ndc, final LocationInfo info, final Map properties) { + this.timeStamp = timeStamp; + } + + /** + * Instantiate a LoggingEvent from the supplied parameters. + * + *

+ * Except {@link #timeStamp} all the other fields of LoggingEvent are filled when actually needed. + *

+ * + * @param logger The logger generating this event. + * @param timeStamp the timestamp of this logging event + * @param level The level of this event. + * @param message The message of this event. + * @param throwable The throwable of this event. + */ + public LoggingEvent(String fqnOfCategoryClass, Category logger, long timeStamp, Priority level, Object message, Throwable throwable) { + this.timeStamp = timeStamp; + } + + /** + * Instantiate a LoggingEvent from the supplied parameters. + * + *

+ * Except {@link #timeStamp} all the other fields of LoggingEvent are filled when actually needed. + *

+ * + * @param logger The logger generating this event. + * @param level The level of this event. + * @param message The message of this event. + * @param throwable The throwable of this event. + */ + public LoggingEvent(String fqnOfCategoryClass, Category logger, Priority level, Object message, Throwable throwable) { + timeStamp = System.currentTimeMillis(); + } + + public String getFQNOfLoggerClass() { + return null; + } + + /** + * Return the level of this event. Use this form instead of directly + * accessing the level field. + * @return Always returns null. + */ + public Level getLevel() { + return null; + } + + /** + Set the location information for this logging event. The collected + information is cached for future use. + @return Always returns null. + */ + public LocationInfo getLocationInformation() { + return null; + } + + /** + * Gets the logger of the event. + * Use should be restricted to cloning events. + * @return Always returns null. + * @since 1.2.15 + */ + public Category getLogger() { + return null; + } + + /** + * Return the name of the logger. Use this form instead of directly + * accessing the categoryName field. + * @return Always returns null. + */ + public String getLoggerName() { + return null; + } + + public + Object getMDC(String key) { + return null; + } + + /** + Obtain a copy of this thread's MDC prior to serialization or + asynchronous logging. + */ + public void getMDCCopy() { + } + + /** + Return the message for this logging event. + +

Before serialization, the returned object is the message + passed by the user to generate the logging event. After + serialization, the returned value equals the String form of the + message possibly after object rendering. + @return Always returns null. + @since 1.1 */ + public + Object getMessage() { + return null; + } + + public + String getNDC() { + return null; + } + + public Map getProperties() { + return null; + } + + public String getProperty(final String key) { + return null; + } + + public Set getPropertyKeySet() { + return null; + } + + public String getRenderedMessage() { + return null; + } + + public String getThreadName() { + return null; + } + + /** + Returns the throwable information contained within this + event. May be null if there is no such information. + +

Note that the {@link Throwable} object contained within a + {@link ThrowableInformation} does not survive serialization. + @return Always returns null. + @since 1.1 */ + public ThrowableInformation getThrowableInformation() { + return null; + } + + /** + Return this event's throwable's string[] representation. + @return Always returns null. + */ + public String[] getThrowableStrRep() { + return null; + } + + public long getTimeStamp() { + return 0; + } + + public Object removeProperty(String propName) { + return null; + } + + public void setProperty(final String propName, + final String propValue) { + + } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLogger.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLogger.java new file mode 100644 index 00000000000..0c61358783f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLogger.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.spi; + +import java.util.Enumeration; +import java.util.ResourceBundle; +import java.util.Vector; + +import org.apache.log4j.Appender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; + +/** + * No-operation implementation of Logger used by NOPLoggerRepository. + * + * @since 1.2.15 + */ +public final class NOPLogger extends Logger { + + /** + * Create instance of Logger. + * + * @param repo repository, may not be null. + * @param name name, may not be null, use "root" for root logger. + */ + public NOPLogger(final NOPLoggerRepository repo, final String name) { + super(name); + this.repository = repo; + this.level = Level.OFF; + this.parent = this; + } + + /** {@inheritDoc} */ + @Override + public void addAppender(final Appender newAppender) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void assertLog(final boolean assertion, final String msg) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void callAppenders(final LoggingEvent event) { + // NOP + } + + void closeNestedAppenders() { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void debug(final Object message) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void debug(final Object message, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void error(final Object message) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void error(final Object message, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void fatal(final Object message) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void fatal(final Object message, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public Enumeration getAllAppenders() { + return new Vector<>().elements(); + } + + /** {@inheritDoc} */ + @Override + public Appender getAppender(final String name) { + return null; + } + + /** {@inheritDoc} */ + @Override + public Priority getChainedPriority() { + return getEffectiveLevel(); + } + + /** {@inheritDoc} */ + @Override + public Level getEffectiveLevel() { + return Level.OFF; + } + + /** {@inheritDoc} */ + @Override + public ResourceBundle getResourceBundle() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void info(final Object message) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void info(final Object message, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public boolean isAttached(final Appender appender) { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isDebugEnabled() { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isEnabledFor(final Priority level) { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isInfoEnabled() { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isTraceEnabled() { + return false; + } + + /** {@inheritDoc} */ + @Override + public void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void l7dlog(final Priority priority, final String key, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void log(final Priority priority, final Object message) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void log(final Priority priority, final Object message, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void log(final String callerFQCN, final Priority level, final Object message, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void removeAllAppenders() { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void removeAppender(final Appender appender) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void removeAppender(final String name) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void setLevel(final Level level) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void setPriority(final Priority priority) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void setResourceBundle(final ResourceBundle bundle) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void trace(final Object message) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void trace(final Object message, final Throwable t) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void warn(final Object message) { + // NOP + } + + /** {@inheritDoc} */ + @Override + public void warn(final Object message, final Throwable t) { + // NOP + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLoggerRepository.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLoggerRepository.java new file mode 100644 index 00000000000..f64996e9aad --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/NOPLoggerRepository.java @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.spi; + +import java.util.Enumeration; +import java.util.Vector; + +import org.apache.log4j.Appender; +import org.apache.log4j.Category; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +/** + * No-operation implementation of LoggerRepository which is used when LogManager.repositorySelector is erroneously + * nulled during class reloading. + * + * @since 1.2.15 + */ +public final class NOPLoggerRepository implements LoggerRepository { + + /** + * {@inheritDoc} + */ + @Override + public void addHierarchyEventListener(final HierarchyEventListener listener) { + // NOP + } + + /** + * {@inheritDoc} + */ + @Override + public void emitNoAppenderWarning(final Category cat) { + // NOP + } + + /** + * {@inheritDoc} + */ + @Override + public Logger exists(final String name) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void fireAddAppenderEvent(final Category logger, final Appender appender) { + // NOP + } + + /** + * {@inheritDoc} + */ + @Override + public Enumeration getCurrentCategories() { + return getCurrentLoggers(); + } + + /** + * {@inheritDoc} + */ + @Override + public Enumeration getCurrentLoggers() { + return new Vector<>().elements(); + } + + /** + * {@inheritDoc} + */ + @Override + public Logger getLogger(final String name) { + return new NOPLogger(this, name); + } + + /** + * {@inheritDoc} + */ + @Override + public Logger getLogger(final String name, final LoggerFactory factory) { + return new NOPLogger(this, name); + } + + /** + * {@inheritDoc} + */ + @Override + public Logger getRootLogger() { + return new NOPLogger(this, "root"); + } + + /** + * {@inheritDoc} + */ + @Override + public Level getThreshold() { + return Level.OFF; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDisabled(final int level) { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void resetConfiguration() { + // NOP + } + + /** + * {@inheritDoc} + */ + @Override + public void setThreshold(final Level level) { + // NOP + } + + /** + * {@inheritDoc} + */ + @Override + public void setThreshold(final String val) { + // NOP + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() { + // NOP + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RendererSupport.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RendererSupport.java new file mode 100644 index 00000000000..0a0b7401668 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RendererSupport.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + +import org.apache.log4j.or.ObjectRenderer; +import org.apache.log4j.or.RendererMap; + +public interface RendererSupport { + + public RendererMap getRendererMap(); + + public void setRenderer(Class renderedClass, ObjectRenderer renderer); + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RepositorySelector.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RepositorySelector.java index 95666594a85..86434a5381a 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RepositorySelector.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RepositorySelector.java @@ -17,26 +17,26 @@ package org.apache.log4j.spi; /** - - The LogManager uses one (and only one) - RepositorySelector implementation to select the - {@link org.apache.log4j.spi.LoggerRepository} for a particular application context. - -

It is the responsibility of the RepositorySelector - implementation to track the application context. Log4j makes no - assumptions about the application context or on its management. - -

See also {@link org.apache.log4j.LogManager LogManager}. - - @since 1.2 - + * + * The LogManager uses one (and only one) RepositorySelector implementation to select the + * {@link LoggerRepository} for a particular application context. + * + *

+ * It is the responsibility of the RepositorySelector implementation to track the application context. + * Log4j makes no assumptions about the application context or on its management. + *

+ * + *

+ * See also {@link org.apache.log4j.LogManager LogManager}. + * + * @since 1.2 */ public interface RepositorySelector { /** - * Returns a {@link org.apache.log4j.spi.LoggerRepository} depending on the - * context. Implementers must make sure that a valid (non-null) + * Gets a {@link LoggerRepository} depending on the context. Implementers must make sure that a valid (non-null) * LoggerRepository is returned. + * * @return a LoggerRepository. */ LoggerRepository getLoggerRepository(); diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RootLogger.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RootLogger.java new file mode 100644 index 00000000000..79db4915b50 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/RootLogger.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.helpers.LogLog; + +/** + * RootLogger sits at the top of the logger hierarchy. It is a regular logger except that it provides several guarantees. + *

+ * First, it cannot be assigned a null level. Second, since root logger cannot have a parent, the + * {@link #getChainedLevel} method always returns the value of the level field without walking the hierarchy. + *

+ */ +public final class RootLogger extends Logger { + + /** + * The root logger names itself as "root". However, the root logger cannot be retrieved by name. + */ + public RootLogger(Level level) { + // The Log4j 1 root logger name is "root". + // The Log4j 2 root logger name is "". + super("root"); + setLevel(level); + } + + /** + * Gets the assigned level value without walking the logger hierarchy. + */ + public final Level getChainedLevel() { + return getLevel(); + } + + /** + * Sets the log level. + * + * Setting a null value to the level of the root logger may have catastrophic results. We prevent this here. + * + * @since 0.8.3 + */ + public final void setLevel(Level level) { + if (level == null) { + LogLog.error("You have tried to set a null level to root.", new Throwable()); + } else { + super.setLevel(level); + } + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableInformation.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableInformation.java new file mode 100644 index 00000000000..6726e0017e7 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableInformation.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.spi; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.List; + +import org.apache.log4j.Category; +import org.apache.logging.log4j.util.Strings; + +/** + * Log4j's internal representation of throwables. + */ +public class ThrowableInformation implements Serializable { + + static final long serialVersionUID = -4748765566864322735L; + + private transient Throwable throwable; + private transient Category category; + private String[] rep; + private static final Method TO_STRING_LIST; + + static { + Method method = null; + try { + final Class throwables = Class.forName("org.apache.logging.log4j.core.util.Throwables"); + method = throwables.getMethod("toStringList", Throwable.class); + } catch (ClassNotFoundException | NoSuchMethodException ex) { + // Ignore the exception if Log4j-core is not present. + } + TO_STRING_LIST = method; + } + + /** + * Constructs new instance. + * + * @since 1.2.15 + * @param r String representation of throwable. + */ + public ThrowableInformation(final String[] r) { + this.rep = r != null ? r.clone() : null; + } + + /** + * Constructs new instance. + */ + public ThrowableInformation(final Throwable throwable) { + this.throwable = throwable; + } + + /** + * Constructs a new instance. + * + * @param throwable throwable, may not be null. + * @param category category used to obtain ThrowableRenderer, may be null. + * @since 1.2.16 + */ + public ThrowableInformation(final Throwable throwable, final Category category) { + this(throwable); + this.category = category; + this.rep = null; + } + + public Throwable getThrowable() { + return throwable; + } + + public synchronized String[] getThrowableStrRep() { + if (TO_STRING_LIST != null && throwable != null) { + try { + @SuppressWarnings("unchecked") + final List elements = (List) TO_STRING_LIST.invoke(null, throwable); + if (elements != null) { + return elements.toArray(Strings.EMPTY_ARRAY); + } + } catch (final ReflectiveOperationException ex) { + // Ignore the exception. + } + } + return rep; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableRenderer.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableRenderer.java new file mode 100644 index 00000000000..e2e87a156b2 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableRenderer.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.spi; + +/** + * Implemented by classes that render instances of java.lang.Throwable (exceptions and errors) into a string + * representation. + * + * @since 1.2.16 + */ +public interface ThrowableRenderer { + + /** + * Render Throwable. + * + * @param t throwable, may not be null. + * @return String representation. + */ + public String[] doRender(Throwable t); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableRendererSupport.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableRendererSupport.java new file mode 100644 index 00000000000..37b17e04519 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableRendererSupport.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.spi; + +/** + * Implemented by logger repositories that support configurable rendering of Throwables. + * + * @since 1.2.16 + */ +public interface ThrowableRendererSupport { + /** + * Get throwable renderer. + * + * @return throwable renderer, may be null. + */ + ThrowableRenderer getThrowableRenderer(); + + /** + * Set throwable renderer. + * + * @param renderer renderer, may be null. + */ + void setThrowableRenderer(ThrowableRenderer renderer); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/TriggeringEventEvaluator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/TriggeringEventEvaluator.java new file mode 100644 index 00000000000..96e86afdcdd --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/TriggeringEventEvaluator.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.spi; + +/** + * Implementors decide when to perform an appender specific action. + * + *

+ * For example, the {@code org.apache.log4j.net.SMTPAppender} sends an email when the + * {@link #isTriggeringEvent(LoggingEvent)} method returns {@code true} and adds the event to an internal buffer when + * the returned result is {@code false}. + *

+ * + * @since version 1.0 + */ +public interface TriggeringEventEvaluator { + + /** + * Tests if this the triggering event. + * + * @param event The vent to test. + * @return Whether this the triggering event. + */ + public boolean isTriggeringEvent(LoggingEvent event); +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/varia/DenyAllFilter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/DenyAllFilter.java new file mode 100644 index 00000000000..4646bdaf4f1 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/DenyAllFilter.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.varia; + +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Denies all logging events. + * + *

+ * You can add this filter to the end of a filter chain to switch from the default "accept all unless instructed + * otherwise" filtering behavior to a "deny all unless instructed otherwise" behavior. + *

+ * + * @since 0.9.0 + */ +public class DenyAllFilter extends Filter { + + /** + * Always returns the integer constant {@link Filter#DENY} regardless of the {@link LoggingEvent} parameter. + * + * @param event The LoggingEvent to filter. + * @return Always returns {@link Filter#DENY}. + */ + @Override + public int decide(final LoggingEvent event) { + return Filter.DENY; + } + + /** + * Returns null as there are no options. + * + * @deprecated We now use JavaBeans introspection to configure components. Options strings are no longer needed. + */ + @Deprecated + public String[] getOptionStrings() { + return null; + } + + /** + * No options to set. + * + * @deprecated Use the setter method for the option directly instead of the generic setOption method. + */ + @Deprecated + public void setOption(final String key, final String value) { + // noop + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/varia/FallbackErrorHandler.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/FallbackErrorHandler.java new file mode 100644 index 00000000000..e2a18985067 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/FallbackErrorHandler.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.varia; + +import java.io.InterruptedIOException; +import java.util.Vector; + +import org.apache.log4j.Appender; +import org.apache.log4j.Logger; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.LoggingEvent; + +/** + * An ErrorHandler with a secondary appender. This secondary appender takes over if the primary appender fails for + * whatever reason. + * + *

+ * The error message is printed on System.err, and logged in the new secondary appender. + *

+ */ +public class FallbackErrorHandler implements ErrorHandler { + + Appender backup; + Appender primary; + Vector loggers; + + public FallbackErrorHandler() { + // noop + } + + /** + * No options to activate. + */ + public void activateOptions() { + // noop + } + + /** + * Print a the error message passed as parameter on System.err. + */ + @Override + public void error(final String message) { + // if(firstTime) { + // LogLog.error(message); + // firstTime = false; + // } + } + + /** + * Prints the message and the stack trace of the exception on System.err. + */ + @Override + public void error(final String message, final Exception e, final int errorCode) { + error(message, e, errorCode, null); + } + + /** + * Prints the message and the stack trace of the exception on System.err. + */ + @Override + public void error(final String message, final Exception e, final int errorCode, final LoggingEvent event) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.debug("FB: The following error reported: " + message, e); + LogLog.debug("FB: INITIATING FALLBACK PROCEDURE."); + if (loggers != null) { + for (int i = 0; i < loggers.size(); i++) { + final Logger l = (Logger) loggers.elementAt(i); + LogLog.debug("FB: Searching for [" + primary.getName() + "] in logger [" + l.getName() + "]."); + LogLog.debug("FB: Replacing [" + primary.getName() + "] by [" + backup.getName() + "] in logger [" + l.getName() + "]."); + l.removeAppender(primary); + LogLog.debug("FB: Adding appender [" + backup.getName() + "] to logger " + l.getName()); + l.addAppender(backup); + } + } + } + + /** + * The appender to which this error handler is attached. + */ + @Override + public void setAppender(final Appender primary) { + LogLog.debug("FB: Setting primary appender to [" + primary.getName() + "]."); + this.primary = primary; + } + + /** + * Set the backup appender. + */ + @Override + public void setBackupAppender(final Appender backup) { + LogLog.debug("FB: Setting backup appender to [" + backup.getName() + "]."); + this.backup = backup; + } + + /** + * Adds the logger passed as parameter to the list of loggers that we need to search for in case of appender + * failure. + */ + @Override + public void setLogger(final Logger logger) { + LogLog.debug("FB: Adding logger [" + logger.getName() + "]."); + if (loggers == null) { + loggers = new Vector(); + } + loggers.addElement(logger); + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelMatchFilter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelMatchFilter.java new file mode 100644 index 00000000000..c7693e41028 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelMatchFilter.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.varia; + +import org.apache.log4j.Level; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Simple filter based on level matching. + * + *

+ * The filter admits two options LevelToMatch and AcceptOnMatch. If there is an exact match between the + * value of the LevelToMatch option and the level of the {@link LoggingEvent}, then the {@link #decide} method + * returns {@link Filter#ACCEPT} in case the AcceptOnMatch option value is set to true, if it is + * false then {@link Filter#DENY} is returned. If there is no match, {@link Filter#NEUTRAL} is returned. + *

+ * + * @since 1.2 + */ +public class LevelMatchFilter extends Filter { + + /** + * Do we return ACCEPT when a match occurs. Default is true. + */ + boolean acceptOnMatch = true; + + /** + */ + Level levelToMatch; + + /** + * Return the decision of this filter. + * + * Returns {@link Filter#NEUTRAL} if the LevelToMatch option is not set or if there is not match. Otherwise, if + * there is a match, then the returned decision is {@link Filter#ACCEPT} if the AcceptOnMatch property is set to + * true. The returned decision is {@link Filter#DENY} if the AcceptOnMatch property is set to false. + * + */ + @Override + public int decide(final LoggingEvent event) { + if (this.levelToMatch == null) { + return Filter.NEUTRAL; + } + + boolean matchOccured = false; + if (this.levelToMatch.equals(event.getLevel())) { + matchOccured = true; + } + + if (matchOccured) { + if (this.acceptOnMatch) { + return Filter.ACCEPT; + } + return Filter.DENY; + } + return Filter.NEUTRAL; + } + + public boolean getAcceptOnMatch() { + return acceptOnMatch; + } + + public String getLevelToMatch() { + return levelToMatch == null ? null : levelToMatch.toString(); + } + + public void setAcceptOnMatch(final boolean acceptOnMatch) { + this.acceptOnMatch = acceptOnMatch; + } + + public void setLevelToMatch(final String level) { + levelToMatch = OptionConverter.toLevel(level, null); + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelRangeFilter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelRangeFilter.java new file mode 100644 index 00000000000..fdd5c10ec45 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/LevelRangeFilter.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.varia; + +import org.apache.log4j.Level; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * This is a very simple filter based on level matching, which can be used to reject messages with priorities outside a + * certain range. + *

+ * The filter admits three options LevelMin, LevelMax and AcceptOnMatch. + *

+ *

+ * If the level of the {@link LoggingEvent} is not between Min and Max (inclusive), then {@link Filter#DENY} is + * returned. + *

+ *

+ * If the Logging event level is within the specified range, then if AcceptOnMatch is true, {@link Filter#ACCEPT} + * is returned, and if AcceptOnMatch is false, {@link Filter#NEUTRAL} is returned. + *

+ *

+ * If LevelMinw is not defined, then there is no minimum acceptable level (ie a level is never rejected for + * being too "low"/unimportant). If LevelMax is not defined, then there is no maximum acceptable level (ie + * a level is never rejected for beeing too "high"/important). + *

+ *

+ * Refer to the {@link org.apache.log4j.AppenderSkeleton#setThreshold setThreshold} method available to all + * appenders extending {@link org.apache.log4j.AppenderSkeleton} for a more convenient way to filter out events by + * level. + *

+ */ +public class LevelRangeFilter extends Filter { + + /** + * Do we return ACCEPT when a match occurs. Default is false, so that later filters get run by default + */ + boolean acceptOnMatch; + + Level levelMin; + Level levelMax; + + /** + * Return the decision of this filter. + */ + @Override + public int decide(final LoggingEvent event) { + if (this.levelMin != null) { + if (!event.getLevel().isGreaterOrEqual(levelMin)) { + // level of event is less than minimum + return Filter.DENY; + } + } + + if (this.levelMax != null) { + if (event.getLevel().toInt() > levelMax.toInt()) { + // level of event is greater than maximum + // Alas, there is no Level.isGreater method. and using + // a combo of isGreaterOrEqual && !Equal seems worse than + // checking the int values of the level objects.. + return Filter.DENY; + } + } + + if (acceptOnMatch) { + // this filter set up to bypass later filters and always return + // accept if level in range + return Filter.ACCEPT; + } + // event is ok for this filter; allow later filters to have a look.. + return Filter.NEUTRAL; + } + + /** + * Get the value of the AcceptOnMatch option. + */ + public boolean getAcceptOnMatch() { + return acceptOnMatch; + } + + /** + * Get the value of the LevelMax option. + */ + public Level getLevelMax() { + return levelMax; + } + + /** + * Get the value of the LevelMin option. + */ + public Level getLevelMin() { + return levelMin; + } + + /** + * Set the AcceptOnMatch option. + */ + public void setAcceptOnMatch(final boolean acceptOnMatch) { + this.acceptOnMatch = acceptOnMatch; + } + + /** + * Set the LevelMax option. + */ + public void setLevelMax(final Level levelMax) { + this.levelMax = levelMax; + } + + /** + * Set the LevelMin option. + */ + public void setLevelMin(final Level levelMin) { + this.levelMin = levelMin; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/varia/NullAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/NullAppender.java new file mode 100644 index 00000000000..cf6647523e4 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/NullAppender.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.varia; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.spi.LoggingEvent; + +/** + * A NullAppender never outputs a message to any device. + */ +public class NullAppender extends AppenderSkeleton { + + private static final NullAppender INSTANCE = new NullAppender(); + + /** + * Whenever you can, use this method to retreive an instance instead of instantiating a new one with new. + */ + public static NullAppender getNullAppender() { + return INSTANCE; + } + + public NullAppender() { + // noop + } + + /** + * There are no options to acticate. + */ + @Override + public void activateOptions() { + // noop + } + + /** + * Does not do anything. + */ + @Override + protected void append(final LoggingEvent event) { + // noop + } + + @Override + public void close() { + // noop + } + + /** + * Does not do anything. + */ + @Override + public void doAppend(final LoggingEvent event) { + // noop + } + + /** + * Whenever you can, use this method to retreive an instance instead of instantiating a new one with new. + * + * @deprecated Use getNullAppender instead. getInstance should have been static. + */ + @Deprecated + public NullAppender getInstance() { + return INSTANCE; + } + + /** + * NullAppenders do not need a layout. + */ + @Override + public boolean requiresLayout() { + return false; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/varia/ReloadingPropertyConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/ReloadingPropertyConfigurator.java new file mode 100644 index 00000000000..a59d92ca67a --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/ReloadingPropertyConfigurator.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.varia; + +import java.io.InputStream; +import java.net.URL; + +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.spi.Configurator; +import org.apache.log4j.spi.LoggerRepository; + +public class ReloadingPropertyConfigurator implements Configurator { + + PropertyConfigurator delegate = new PropertyConfigurator(); + + public ReloadingPropertyConfigurator() { + } + + /** + * @since 1.2.17 + */ + @Override + public void doConfigure(final InputStream inputStream, final LoggerRepository repository) { + // noop + } + + @Override + public void doConfigure(final URL url, final LoggerRepository repository) { + // noop + } + +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/varia/StringMatchFilter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/StringMatchFilter.java new file mode 100644 index 00000000000..c84dbce39d1 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/varia/StringMatchFilter.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.varia; + +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Simple filter based on string matching. + * + *

+ * The filter admits two options StringToMatch and AcceptOnMatch. If there is a match between the value of + * the StringToMatch option and the message of the {@link org.apache.log4j.spi.LoggingEvent}, then the + * {@link #decide(LoggingEvent)} method returns {@link org.apache.log4j.spi.Filter#ACCEPT} if the AcceptOnMatch + * option value is true, if it is false then {@link org.apache.log4j.spi.Filter#DENY} is returned. If there is no match, + * {@link org.apache.log4j.spi.Filter#NEUTRAL} is returned. + *

+ * + * @since 0.9.0 + */ +public class StringMatchFilter extends Filter { + + /** + * @deprecated Options are now handled using the JavaBeans paradigm. This constant is not longer needed and will be + * removed in the near term. + */ + @Deprecated + public static final String STRING_TO_MATCH_OPTION = "StringToMatch"; + + /** + * @deprecated Options are now handled using the JavaBeans paradigm. This constant is not longer needed and will be + * removed in the near term. + */ + @Deprecated + public static final String ACCEPT_ON_MATCH_OPTION = "AcceptOnMatch"; + + boolean acceptOnMatch = true; + String stringToMatch; + + /** + * Returns {@link Filter#NEUTRAL} is there is no string match. + */ + @Override + public int decide(final LoggingEvent event) { + final String msg = event.getRenderedMessage(); + if (msg == null || stringToMatch == null) { + return Filter.NEUTRAL; + } + if (msg.indexOf(stringToMatch) == -1) { + return Filter.NEUTRAL; + } + return acceptOnMatch ? Filter.ACCEPT : Filter.DENY; + } + + public boolean getAcceptOnMatch() { + return acceptOnMatch; + } + + /** + * @deprecated We now use JavaBeans introspection to configure components. Options strings are no longer needed. + */ + @Deprecated + public String[] getOptionStrings() { + return new String[] {STRING_TO_MATCH_OPTION, ACCEPT_ON_MATCH_OPTION}; + } + + public String getStringToMatch() { + return stringToMatch; + } + + public void setAcceptOnMatch(final boolean acceptOnMatch) { + this.acceptOnMatch = acceptOnMatch; + } + + /** + * @deprecated Use the setter method for the option directly instead of the generic setOption method. + */ + @Deprecated + public void setOption(final String key, final String value) { + if (key.equalsIgnoreCase(STRING_TO_MATCH_OPTION)) { + stringToMatch = value; + } else if (key.equalsIgnoreCase(ACCEPT_ON_MATCH_OPTION)) { + acceptOnMatch = OptionConverter.toBoolean(value, acceptOnMatch); + } + } + + public void setStringToMatch(final String s) { + stringToMatch = s; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java index 04a45551e03..b0cbb582b1f 100644 --- a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java @@ -16,65 +16,141 @@ */ package org.apache.log4j.xml; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.io.StringWriter; import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Properties; import javax.xml.parsers.FactoryConfigurationError; +import org.apache.log4j.LogManager; import org.apache.log4j.config.PropertySetter; +import org.apache.log4j.helpers.OptionConverter; import org.apache.log4j.spi.LoggerRepository; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.net.UrlConnectionFactory; +import org.apache.logging.log4j.core.util.IOUtils; import org.w3c.dom.Element; /** + * Use this class to initialize the log4j environment using a DOM tree. * + *

+ * The DTD is specified in log4j.dtd. + * + *

+ * Sometimes it is useful to see how log4j is reading configuration files. You can enable log4j internal logging by + * defining the log4j.debug variable on the java command line. Alternatively, set the debug + * attribute in the log4j:configuration element. As in + * + *

+<log4j:configuration debug="true" xmlns:log4j="http://jakarta.apache.org/log4j/">
+...
+</log4j:configuration>
+ * 
+ * + *

+ * There are sample XML files included in the package. + * + * @since 0.8.3 */ public class DOMConfigurator { - public void doConfigure(final String filename, final LoggerRepository repository) { + public static void configure(final Element element) { } - public void doConfigure(final URL url, final LoggerRepository repository) { + public static void configure(final String fileName) throws FactoryConfigurationError { + final Path path = Paths.get(fileName); + try (final InputStream inputStream = Files.newInputStream(path)) { + final ConfigurationSource source = new ConfigurationSource(inputStream, path); + final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + Configuration configuration; + configuration = new XmlConfigurationFactory().getConfiguration(context, source); + LogManager.getRootLogger().removeAllAppenders(); + Configurator.reconfigure(configuration); + } catch (final IOException e) { + throw new FactoryConfigurationError(e); + } } - public void doConfigure(final InputStream inputStream, final LoggerRepository repository) - throws FactoryConfigurationError { + public static void configure(final URL url) throws FactoryConfigurationError { + new DOMConfigurator().doConfigure(url, LogManager.getLoggerRepository()); } - public void doConfigure(final Reader reader, final LoggerRepository repository) - throws FactoryConfigurationError { + public static void configureAndWatch(final String fileName) { + // TODO Watch + configure(fileName); } - public void doConfigure(final Element element, final LoggerRepository repository) { + public static void configureAndWatch(final String fileName, final long delay) { + XMLWatchdog xdog = new XMLWatchdog(fileName); + xdog.setDelay(delay); + xdog.start(); } - public static void configure(final Element element) { + public static Object parseElement(final Element element, final Properties props, @SuppressWarnings("rawtypes") final Class expectedClass) { + return null; } - public static void configureAndWatch(final String configFilename) { + public static void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) { + } - public static void configureAndWatch(final String configFilename, final long delay) { + public static String subst(final String value, final Properties props) { + return OptionConverter.substVars(value, props); } - public static void configure(final String filename) throws FactoryConfigurationError { + private void doConfigure(final ConfigurationSource source) { + final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + Configuration configuration; + configuration = new XmlConfigurationFactory().getConfiguration(context, source); + Configurator.reconfigure(configuration); } - public static void configure(final URL url) throws FactoryConfigurationError { + public void doConfigure(final Element element, final LoggerRepository repository) { } - public static String subst(final String value, final Properties props) { - return value; + public void doConfigure(final InputStream inputStream, final LoggerRepository repository) throws FactoryConfigurationError { + try { + doConfigure(new ConfigurationSource(inputStream)); + } catch (final IOException e) { + throw new FactoryConfigurationError(e); + } } - public static void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) { + public void doConfigure(final Reader reader, final LoggerRepository repository) throws FactoryConfigurationError { + try { + final StringWriter sw = new StringWriter(); + IOUtils.copy(reader, sw); + doConfigure(new ConfigurationSource(new ByteArrayInputStream(sw.toString().getBytes(StandardCharsets.UTF_8)))); + } catch (final IOException e) { + throw new FactoryConfigurationError(e); + } + } + public void doConfigure(final String fileName, final LoggerRepository repository) { + configure(fileName); } - public static Object parseElement(final Element element, final Properties props, - @SuppressWarnings("rawtypes") final Class expectedClass) - throws Exception { - return null; + public void doConfigure(final URL url, final LoggerRepository repository) { + try { + final URLConnection connection = UrlConnectionFactory.createConnection(url); + try (InputStream inputStream = connection.getInputStream()) { + doConfigure(new ConfigurationSource(inputStream, url)); + } + } catch (final IOException e) { + throw new FactoryConfigurationError(e); + } } } diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java new file mode 100644 index 00000000000..f4cb122ee7c --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.xml; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Constants; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; + +/** + * An {@link EntityResolver} specifically designed to return + * log4j.dtd which is embedded within the log4j jar + * file. + */ +public class Log4jEntityResolver implements EntityResolver { + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final String PUBLIC_ID = "-//APACHE//DTD LOG4J 1.2//EN"; + + @Override + public InputSource resolveEntity(String publicId, String systemId) { + if (systemId.endsWith("log4j.dtd") || PUBLIC_ID.equals(publicId)) { + Class clazz = getClass(); + InputStream in = clazz.getResourceAsStream("/org/apache/log4j/xml/log4j.dtd"); + if (in == null) { + LOGGER.warn("Could not find [log4j.dtd] using [{}] class loader, parsed without DTD.", + clazz.getClassLoader()); + in = new ByteArrayInputStream(Constants.EMPTY_BYTE_ARRAY); + } + return new InputSource(in); + } + return null; + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/UnrecognizedElementHandler.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/UnrecognizedElementHandler.java new file mode 100644 index 00000000000..81e290b2d1f --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/UnrecognizedElementHandler.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.xml; + +import java.util.Properties; + +import org.w3c.dom.Element; + +/** + * When implemented by an object configured by DOMConfigurator, + * the handle method will be called when an unrecognized child + * element is encountered. Unrecognized child elements of + * the log4j:configuration element will be dispatched to + * the logger repository if it supports this interface. + * + * @since 1.2.15 + */ +public interface UnrecognizedElementHandler { + /** + * Called to inform a configured object when + * an unrecognized child element is encountered. + * @param element element, may not be null. + * @param props properties in force, may be null. + * @return true if configured object recognized the element + * @throws Exception throw an exception to prevent activation + * of the configured object. + */ + boolean parseUnrecognizedElement(Element element, Properties props) throws Exception; +} \ No newline at end of file diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XMLWatchdog.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XMLWatchdog.java new file mode 100644 index 00000000000..753bd8b9dbd --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XMLWatchdog.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j.xml; + +import org.apache.log4j.LogManager; +import org.apache.log4j.helpers.FileWatchdog; +import org.apache.log4j.spi.LoggerRepository; + +class XMLWatchdog extends FileWatchdog { + + XMLWatchdog(final String filename) { + super(filename); + } + + /** + * Calls {@link DOMConfigurator#doConfigure(String, LoggerRepository)} with the filename to reconfigure Log4j. + */ + @Override + public void doOnChange() { + new DOMConfigurator().doConfigure(filename, LogManager.getLoggerRepository()); + } +} \ No newline at end of file diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java new file mode 100644 index 00000000000..07eb38db8a9 --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java @@ -0,0 +1,823 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.xml; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.stream.IntStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.FactoryConfigurationError; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.Level; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.log4j.config.PropertySetter; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.rewrite.RewritePolicy; +import org.apache.log4j.spi.AppenderAttachable; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.status.StatusConfiguration; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Class Description goes here. + */ +public class XmlConfiguration extends Log4j1Configuration { + + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + private static final String CONFIGURATION_TAG = "log4j:configuration"; + private static final String OLD_CONFIGURATION_TAG = "configuration"; + private static final String RENDERER_TAG = "renderer"; + private static final String APPENDER_TAG = "appender"; + public static final String PARAM_TAG = "param"; + public static final String LAYOUT_TAG = "layout"; + private static final String CATEGORY = "category"; + private static final String LOGGER_ELEMENT = "logger"; + private static final String CATEGORY_FACTORY_TAG = "categoryFactory"; + private static final String LOGGER_FACTORY_TAG = "loggerFactory"; + public static final String NAME_ATTR = "name"; + private static final String CLASS_ATTR = "class"; + public static final String VALUE_ATTR = "value"; + private static final String ROOT_TAG = "root"; + private static final String LEVEL_TAG = "level"; + private static final String PRIORITY_TAG = "priority"; + public static final String FILTER_TAG = "filter"; + private static final String ERROR_HANDLER_TAG = "errorHandler"; + public static final String REF_ATTR = "ref"; + private static final String ADDITIVITY_ATTR = "additivity"; + private static final String CONFIG_DEBUG_ATTR = "configDebug"; + private static final String INTERNAL_DEBUG_ATTR = "debug"; + private static final String THRESHOLD_ATTR = "threshold"; + private static final String EMPTY_STR = ""; + private static final Class[] ONE_STRING_PARAM = new Class[] { String.class }; + private static final String dbfKey = "javax.xml.parsers.DocumentBuilderFactory"; + private static final String THROWABLE_RENDERER_TAG = "throwableRenderer"; + + public static final long DEFAULT_DELAY = 60000; + + /** + * File name prefix for test configurations. + */ + protected static final String TEST_PREFIX = "log4j-test"; + + /** + * File name prefix for standard configurations. + */ + protected static final String DEFAULT_PREFIX = "log4j"; + + // key: appenderName, value: appender + private final Map appenderMap; + + private final Properties props = null; + + public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, + int monitorIntervalSeconds) { + super(loggerContext, source, monitorIntervalSeconds); + appenderMap = new HashMap<>(); + } + + public void addAppenderIfAbsent(Appender appender) { + appenderMap.putIfAbsent(appender.getName(), appender); + } + + /** + * Configures log4j by reading in a log4j.dtd compliant XML + * configuration file. + */ + @Override + public void doConfigure() throws FactoryConfigurationError { + ConfigurationSource source = getConfigurationSource(); + ParseAction action = new ParseAction() { + @Override + public Document parse(final DocumentBuilder parser) throws SAXException, IOException { + @SuppressWarnings("resource") // The ConfigurationSource and its caller manages the InputStream. + InputSource inputSource = new InputSource(source.getInputStream()); + inputSource.setSystemId("dummy://log4j.dtd"); + return parser.parse(inputSource); + } + + @Override + public String toString() { + return getConfigurationSource().getLocation(); + } + }; + doConfigure(action); + } + + private void doConfigure(final ParseAction action) throws FactoryConfigurationError { + DocumentBuilderFactory dbf; + try { + LOGGER.debug("System property is : {}", OptionConverter.getSystemProperty(dbfKey, null)); + dbf = DocumentBuilderFactory.newInstance(); + LOGGER.debug("Standard DocumentBuilderFactory search succeeded."); + LOGGER.debug("DocumentBuilderFactory is: " + dbf.getClass().getName()); + } catch (FactoryConfigurationError fce) { + Exception e = fce.getException(); + LOGGER.debug("Could not instantiate a DocumentBuilderFactory.", e); + throw fce; + } + + try { + dbf.setValidating(true); + + DocumentBuilder docBuilder = dbf.newDocumentBuilder(); + + docBuilder.setErrorHandler(new SAXErrorHandler()); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + + Document doc = action.parse(docBuilder); + parse(doc.getDocumentElement()); + } catch (Exception e) { + if (e instanceof InterruptedException || e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + // I know this is miserable... + LOGGER.error("Could not parse " + action.toString() + ".", e); + } + } + + @Override + public Configuration reconfigure() { + try { + final ConfigurationSource source = getConfigurationSource().resetInputStream(); + if (source == null) { + return null; + } + final XmlConfigurationFactory factory = new XmlConfigurationFactory(); + final XmlConfiguration config = + (XmlConfiguration) factory.getConfiguration(getLoggerContext(), source); + return config == null || config.getState() != State.INITIALIZING ? null : config; + } catch (final IOException ex) { + LOGGER.error("Cannot locate file {}: {}", getConfigurationSource(), ex); + } + return null; + } + + /** + * Delegates unrecognized content to created instance if it supports UnrecognizedElementParser. + * + * @param instance instance, may be null. + * @param element element, may not be null. + * @param props properties + * @throws IOException thrown if configuration of owner object should be abandoned. + */ + private void parseUnrecognizedElement(final Object instance, final Element element, + final Properties props) throws Exception { + boolean recognized = false; + if (instance instanceof UnrecognizedElementHandler) { + recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement( + element, props); + } + if (!recognized) { + LOGGER.warn("Unrecognized element {}", element.getNodeName()); + } + } + + /** + * Delegates unrecognized content to created instance if + * it supports UnrecognizedElementParser and catches and + * logs any exception. + * + * @param instance instance, may be null. + * @param element element, may not be null. + * @param props properties + * @since 1.2.15 + */ + private void quietParseUnrecognizedElement(final Object instance, + final Element element, + final Properties props) { + try { + parseUnrecognizedElement(instance, element, props); + } catch (Exception ex) { + if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Error in extension content: ", ex); + } + } + + /** + * Substitutes property value for any references in expression. + * + * @param value value from configuration file, may contain + * literal text, property references or both + * @param props properties. + * @return evaluated expression, may still contain expressions + * if unable to expand. + */ + public String subst(final String value, final Properties props) { + try { + return OptionConverter.substVars(value, props); + } catch (IllegalArgumentException e) { + LOGGER.warn("Could not perform variable substitution.", e); + return value; + } + } + + /** + * Sets a parameter based from configuration file content. + * + * @param elem param element, may not be null. + * @param propSetter property setter, may not be null. + * @param props properties + * @since 1.2.15 + */ + public void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) { + String name = subst(elem.getAttribute("name"), props); + String value = (elem.getAttribute("value")); + value = subst(OptionConverter.convertSpecialChars(value), props); + propSetter.setProperty(name, value); + } + + /** + * Creates an object and processes any nested param elements + * but does not call activateOptions. If the class also supports + * UnrecognizedElementParser, the parseUnrecognizedElement method + * will be call for any child elements other than param. + * + * @param element element, may not be null. + * @param props properties + * @param expectedClass interface or class expected to be implemented + * by created class + * @return created class or null. + * @throws Exception thrown if the contain object should be abandoned. + * @since 1.2.15 + */ + public Object parseElement(final Element element, final Properties props, + @SuppressWarnings("rawtypes") final Class expectedClass) throws Exception { + String clazz = subst(element.getAttribute("class"), props); + Object instance = OptionConverter.instantiateByClassName(clazz, + expectedClass, null); + + if (instance != null) { + PropertySetter propSetter = new PropertySetter(instance); + NodeList children = element.getChildNodes(); + final int length = children.getLength(); + + for (int loop = 0; loop < length; loop++) { + Node currentNode = children.item(loop); + if (currentNode.getNodeType() == Node.ELEMENT_NODE) { + Element currentElement = (Element) currentNode; + String tagName = currentElement.getTagName(); + if (tagName.equals("param")) { + setParameter(currentElement, propSetter, props); + } else { + parseUnrecognizedElement(instance, currentElement, props); + } + } + } + return instance; + } + return null; + } + + /** + * Used internally to parse appenders by IDREF name. + */ + private Appender findAppenderByName(Document doc, String appenderName) { + Appender appender = appenderMap.get(appenderName); + + if (appender != null) { + return appender; + } + // Endre's hack: + Element element = null; + NodeList list = doc.getElementsByTagName("appender"); + for (int t = 0; t < list.getLength(); t++) { + Node node = list.item(t); + NamedNodeMap map = node.getAttributes(); + Node attrNode = map.getNamedItem("name"); + if (appenderName.equals(attrNode.getNodeValue())) { + element = (Element) node; + break; + } + } + // Hack finished. + + if (element == null) { + + LOGGER.error("No appender named [{}] could be found.", appenderName); + return null; + } + appender = parseAppender(element); + if (appender != null) { + appenderMap.put(appenderName, appender); + } + return appender; + } + + /** + * Used internally to parse appenders by IDREF element. + * @param appenderRef The Appender Reference Element. + * @return The Appender. + */ + public Appender findAppenderByReference(Element appenderRef) { + String appenderName = subst(appenderRef.getAttribute(REF_ATTR)); + Document doc = appenderRef.getOwnerDocument(); + return findAppenderByName(doc, appenderName); + } + + /** + * Used internally to parse an appender element. + * @param appenderElement The Appender Element. + * @return The Appender. + */ + public Appender parseAppender(Element appenderElement) { + String className = subst(appenderElement.getAttribute(CLASS_ATTR)); + LOGGER.debug("Class name: [" + className + ']'); + Appender appender = manager.parseAppender(className, appenderElement, this); + if (appender == null) { + appender = buildAppender(className, appenderElement); + } + return appender; + } + + private Appender buildAppender(String className, Element appenderElement) { + try { + Appender appender = LoaderUtil.newInstanceOf(className); + PropertySetter propSetter = new PropertySetter(appender); + + appender.setName(subst(appenderElement.getAttribute(NAME_ATTR))); + final AtomicReference filterChain = new AtomicReference<>(); + forEachElement(appenderElement.getChildNodes(), currentElement -> { + // Parse appender parameters + switch (currentElement.getTagName()) { + case PARAM_TAG: + setParameter(currentElement, propSetter); + break; + case LAYOUT_TAG: + appender.setLayout(parseLayout(currentElement)); + break; + case FILTER_TAG: + addFilter(filterChain, currentElement); + break; + case ERROR_HANDLER_TAG: + parseErrorHandler(currentElement, appender); + break; + case APPENDER_REF_TAG: + String refName = subst(currentElement.getAttribute(REF_ATTR)); + if (appender instanceof AppenderAttachable) { + AppenderAttachable aa = (AppenderAttachable) appender; + Appender child = findAppenderByReference(currentElement); + LOGGER.debug("Attaching appender named [{}] to appender named [{}].", refName, + appender.getName()); + aa.addAppender(child); + } else { + LOGGER.error("Requesting attachment of appender named [{}] to appender named [{}]" + + "which does not implement org.apache.log4j.spi.AppenderAttachable.", + refName, appender.getName()); + } + break; + default: + try { + parseUnrecognizedElement(appender, currentElement, props); + } catch (Exception ex) { + throw new ConsumerException(ex); + } + } + }); + final Filter head = filterChain.get(); + if (head != null) { + appender.addFilter(head); + } + propSetter.activate(); + return appender; + } catch (ConsumerException ex) { + Throwable t = ex.getCause(); + if (t instanceof InterruptedException || t instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Could not create an Appender. Reported error follows.", t); + } catch (Exception oops) { + if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Could not create an Appender. Reported error follows.", oops); + } + return null; + } + + public RewritePolicy parseRewritePolicy(Element rewritePolicyElement) { + String className = subst(rewritePolicyElement.getAttribute(CLASS_ATTR)); + LOGGER.debug("Class name: [" + className + ']'); + RewritePolicy policy = manager.parseRewritePolicy(className, rewritePolicyElement, this); + if (policy == null) { + policy = buildRewritePolicy(className, rewritePolicyElement); + } + return policy; + } + + private RewritePolicy buildRewritePolicy(String className, Element element) { + try { + RewritePolicy policy = LoaderUtil.newInstanceOf(className); + PropertySetter propSetter = new PropertySetter(policy); + + forEachElement(element.getChildNodes(), currentElement -> { + if (currentElement.getTagName().equalsIgnoreCase(PARAM_TAG)) { + setParameter(currentElement, propSetter); + } + }); + propSetter.activate(); + return policy; + } catch (ConsumerException ex) { + Throwable t = ex.getCause(); + if (t instanceof InterruptedException || t instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Could not create an RewritePolicy. Reported error follows.", t); + } catch (Exception oops) { + if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Could not create an RewritePolicy. Reported error follows.", oops); + } + return null; + } + + /** + * Used internally to parse an {@link ErrorHandler} element. + */ + private void parseErrorHandler(Element element, Appender appender) { + ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName( + subst(element.getAttribute(CLASS_ATTR)), + ErrorHandler.class, + null); + + if (eh != null) { + eh.setAppender(appender); + + PropertySetter propSetter = new PropertySetter(eh); + forEachElement(element.getChildNodes(), currentElement -> { + String tagName = currentElement.getTagName(); + if (tagName.equals(PARAM_TAG)) { + setParameter(currentElement, propSetter); + } + }); + propSetter.activate(); + appender.setErrorHandler(eh); + } + } + + /** + * Used internally to parse a filter element. + * @param filterElement The Filter Element. + * @return The Filter. + */ + public void addFilter(final AtomicReference ref, final Element filterElement) { + final Filter value = parseFilters(filterElement); + ref.accumulateAndGet(value, FilterAdapter::addFilter); + } + + /** + * Used internally to parse a filter element. + */ + public Filter parseFilters(Element filterElement) { + String className = subst(filterElement.getAttribute(CLASS_ATTR)); + LOGGER.debug("Class name: [" + className + ']'); + Filter filter = manager.parseFilter(className, filterElement, this); + if (filter == null) { + filter = buildFilter(className, filterElement); + } + return filter; + } + + private Filter buildFilter(final String className, final Element filterElement) { + try { + Filter filter = LoaderUtil.newInstanceOf(className); + PropertySetter propSetter = new PropertySetter(filter); + + forEachElement(filterElement.getChildNodes(), currentElement -> { + // Parse appender parameters + switch (currentElement.getTagName()) { + case PARAM_TAG : + setParameter(currentElement, propSetter); + break; + } + }); + propSetter.activate(); + return filter; + } catch (ConsumerException ex) { + Throwable t = ex.getCause(); + if (t instanceof InterruptedException || t instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Could not create an Filter. Reported error follows.", t); + } catch (Exception oops) { + if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Could not create an Filter. Reported error follows.", oops); + } + return null; + } + + /** + * Used internally to parse an category element. + */ + private void parseCategory(Element loggerElement) { + // Create a new org.apache.log4j.Category object from the element. + String catName = subst(loggerElement.getAttribute(NAME_ATTR)); + boolean additivity = OptionConverter.toBoolean(subst(loggerElement.getAttribute(ADDITIVITY_ATTR)), true); + LoggerConfig loggerConfig = getLogger(catName); + if (loggerConfig == null) { + loggerConfig = new LoggerConfig(catName, org.apache.logging.log4j.Level.ERROR, additivity); + addLogger(catName, loggerConfig); + } else { + loggerConfig.setAdditive(additivity); + } + parseChildrenOfLoggerElement(loggerElement, loggerConfig, false); + } + + /** + * Used internally to parse the root category element. + */ + private void parseRoot(Element rootElement) { + LoggerConfig root = getRootLogger(); + parseChildrenOfLoggerElement(rootElement, root, true); + } + + /** + * Used internally to parse the children of a LoggerConfig element. + */ + private void parseChildrenOfLoggerElement(Element catElement, LoggerConfig loggerConfig, boolean isRoot) { + + final PropertySetter propSetter = new PropertySetter(loggerConfig); + loggerConfig.getAppenderRefs().clear(); + forEachElement(catElement.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case APPENDER_REF_TAG: { + Appender appender = findAppenderByReference(currentElement); + String refName = subst(currentElement.getAttribute(REF_ATTR)); + if (appender != null) { + LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", refName, + loggerConfig.getName()); + loggerConfig.addAppender(getAppender(refName), null, null); + } else { + LOGGER.debug("Appender named [{}] not found.", refName); + } + break; + } + case LEVEL_TAG: case PRIORITY_TAG: { + parseLevel(currentElement, loggerConfig, isRoot); + break; + } + case PARAM_TAG: { + setParameter(currentElement, propSetter); + break; + } + default: { + quietParseUnrecognizedElement(loggerConfig, currentElement, props); + } + } + }); + propSetter.activate(); + } + + /** + * Used internally to parse a layout element. + * @param layoutElement The Layout Element. + * @return The Layout. + */ + public Layout parseLayout(Element layoutElement) { + String className = subst(layoutElement.getAttribute(CLASS_ATTR)); + LOGGER.debug("Parsing layout of class: \"{}\"", className); + Layout layout = manager.parseLayout(className, layoutElement, this); + if (layout == null) { + layout = buildLayout(className, layoutElement); + } + return layout; + } + + private Layout buildLayout(String className, Element layout_element) { + try { + Layout layout = LoaderUtil.newInstanceOf(className); + PropertySetter propSetter = new PropertySetter(layout); + forEachElement(layout_element.getChildNodes(), currentElement -> { + String tagName = currentElement.getTagName(); + if (tagName.equals(PARAM_TAG)) { + setParameter(currentElement, propSetter); + } else { + try { + parseUnrecognizedElement(layout, currentElement, props); + } catch (Exception ex) { + throw new ConsumerException(ex); + } + } + }); + + propSetter.activate(); + return layout; + } catch (Exception e) { + Throwable cause = e.getCause(); + if (e instanceof InterruptedException + || e instanceof InterruptedIOException + || cause instanceof InterruptedException + || cause instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LOGGER.error("Could not create the Layout. Reported error follows.", e); + } + return null; + } + + /** + * Used internally to parse a level element. + */ + private void parseLevel(Element element, LoggerConfig logger, boolean isRoot) { + String catName = logger.getName(); + if (isRoot) { + catName = "root"; + } + + String priStr = subst(element.getAttribute(VALUE_ATTR)); + LOGGER.debug("Level value for {} is [{}].", catName, priStr); + + if (INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) { + if (isRoot) { + LOGGER.error("Root level cannot be inherited. Ignoring directive."); + } else { + logger.setLevel(null); + } + } else { + String className = subst(element.getAttribute(CLASS_ATTR)); + final Level level; + if (EMPTY_STR.equals(className)) { + level = OptionConverter.toLevel(priStr, DEFAULT_LEVEL); + } else { + level = OptionConverter.toLevel(className, priStr, DEFAULT_LEVEL); + } + logger.setLevel(level != null ? level.getVersion2Level() : null); + } + LOGGER.debug("{} level set to {}", catName, logger.getLevel()); + } + + private void setParameter(Element element, PropertySetter propSetter) { + String name = subst(element.getAttribute(NAME_ATTR)); + String value = element.getAttribute(VALUE_ATTR); + value = subst(OptionConverter.convertSpecialChars(value)); + propSetter.setProperty(name, value); + } + + /** + * Used internally to configure the log4j framework by parsing a DOM + * tree of XML elements based on log4j.dtd. + */ + private void parse(Element element) { + String rootElementName = element.getTagName(); + + if (!rootElementName.equals(CONFIGURATION_TAG)) { + if (rootElementName.equals(OLD_CONFIGURATION_TAG)) { + LOGGER.warn("The <" + OLD_CONFIGURATION_TAG + "> element has been deprecated."); + LOGGER.warn("Use the <" + CONFIGURATION_TAG + "> element instead."); + } else { + LOGGER.error("DOM element is - not a <" + CONFIGURATION_TAG + "> element."); + return; + } + } + + + String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR)); + + LOGGER.debug("debug attribute= \"" + debugAttrib + "\"."); + // if the log4j.dtd is not specified in the XML file, then the + // "debug" attribute is returned as the empty string. + String status = "error"; + if (!debugAttrib.equals("") && !debugAttrib.equals("null")) { + status = OptionConverter.toBoolean(debugAttrib, true) ? "debug" : "error"; + } else { + LOGGER.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute."); + } + + String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR)); + if (!confDebug.equals("") && !confDebug.equals("null")) { + LOGGER.warn("The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated."); + LOGGER.warn("Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead."); + status = OptionConverter.toBoolean(confDebug, true) ? "debug" : "error"; + } + + final StatusConfiguration statusConfig = new StatusConfiguration().setStatus(status); + statusConfig.initialize(); + + final String threshold = subst(element.getAttribute(THRESHOLD_ATTR)); + if (threshold != null) { + final org.apache.logging.log4j.Level level = OptionConverter.convertLevel(threshold.trim(), + org.apache.logging.log4j.Level.ALL); + addFilter(ThresholdFilter.createFilter(level, Result.NEUTRAL, Result.DENY)); + } + + forEachElement(element.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case CATEGORY: + case LOGGER_ELEMENT: + parseCategory(currentElement); + break; + case ROOT_TAG: + parseRoot(currentElement); + break; + case RENDERER_TAG: + LOGGER.warn("Log4j 1 renderers are not supported by Log4j 2 and will be ignored."); + break; + case THROWABLE_RENDERER_TAG: + LOGGER.warn("Log4j 1 throwable renderers are not supported by Log4j 2 and will be ignored."); + break; + case CATEGORY_FACTORY_TAG: + case LOGGER_FACTORY_TAG: + LOGGER.warn("Log4j 1 logger factories are not supported by Log4j 2 and will be ignored."); + break; + case APPENDER_TAG: + Appender appender = parseAppender(currentElement); + appenderMap.put(appender.getName(), appender); + addAppender(AppenderAdapter.adapt(appender)); + break; + default: + quietParseUnrecognizedElement(null, currentElement, props); + } + }); + } + + private String subst(final String value) { + return getStrSubstitutor().replace(value); + } + + public static void forEachElement(NodeList list, Consumer consumer) { + IntStream.range(0, list.getLength()).mapToObj(list::item) + .filter(node -> node.getNodeType() == Node.ELEMENT_NODE) + .forEach(node -> consumer.accept((Element) node)); + } + + private interface ParseAction { + Document parse(final DocumentBuilder parser) throws SAXException, IOException; + } + + private static class SAXErrorHandler implements org.xml.sax.ErrorHandler { + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + @Override + public void error(final SAXParseException ex) { + emitMessage("Continuable parsing error ", ex); + } + + @Override + public void fatalError(final SAXParseException ex) { + emitMessage("Fatal parsing error ", ex); + } + + @Override + public void warning(final SAXParseException ex) { + emitMessage("Parsing warning ", ex); + } + + private static void emitMessage(final String msg, final SAXParseException ex) { + LOGGER.warn("{} {} and column {}", msg, ex.getLineNumber(), ex.getColumnNumber()); + LOGGER.warn(ex.getMessage(), ex.getException()); + } + } + + private static class ConsumerException extends RuntimeException { + + ConsumerException(Exception ex) { + super(ex); + } + } +} diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java new file mode 100644 index 00000000000..dfe157684dd --- /dev/null +++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.xml; + +import org.apache.log4j.config.Log4j1Configuration; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Order; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * Constructs a Configuration usable in Log4j 2 from a Log4j 1 configuration file. + */ +@Namespace(ConfigurationFactory.NAMESPACE) +@Plugin("Log4j1XmlConfigurationFactory") +@Order(2) +public class XmlConfigurationFactory extends ConfigurationFactory { + + public static final String FILE_EXTENSION = ".xml"; + + /** + * File name prefix for test configurations. + */ + protected static final String TEST_PREFIX = "log4j-test"; + + /** + * File name prefix for standard configurations. + */ + protected static final String DEFAULT_PREFIX = "log4j"; + + @Override + protected String[] getSupportedTypes() { + if (!PropertiesUtil.getProperties().getBooleanProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL, Boolean.FALSE)) { + return null; + } + return new String[] { FILE_EXTENSION }; + } + + @Override + public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) { + int interval = PropertiesUtil.getProperties().getIntegerProperty(Log4j1Configuration.MONITOR_INTERVAL, 0); + return new XmlConfiguration(loggerContext, source, interval); + } + + @Override + protected String getTestPrefix() { + return TEST_PREFIX; + } + + @Override + protected String getDefaultPrefix() { + return DEFAULT_PREFIX; + } + + @Override + protected String getVersion() { + return LOG4J1_VERSION; + } +} diff --git a/log4j-1.2-api/src/main/resources/org/apache/log4j/xml/log4j.dtd b/log4j-1.2-api/src/main/resources/org/apache/log4j/xml/log4j.dtd new file mode 100644 index 00000000000..f8e433a50e6 --- /dev/null +++ b/log4j-1.2-api/src/main/resources/org/apache/log4j/xml/log4j.dtd @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/site/markdown/index.md b/log4j-1.2-api/src/site/markdown/index.md index a80e6873f6d..4eb2916a3c3 100644 --- a/log4j-1.2-api/src/site/markdown/index.md +++ b/log4j-1.2-api/src/site/markdown/index.md @@ -18,12 +18,26 @@ # Log4j 1.2 Bridge -The Log4j 1.2 Bridge allows applications coded to use Log4j 1.2 API to use -Log4j 2 instead. +The Log4j 1.2 Bridge allows applications coded to use Log4j 1.2 API to use Log4j 2 instead. ## Requirements -The Log4j 1.2 bridge is dependent on the Log4j 2 API and implementation. +The Log4j 1.2 bridge is dependent on the Log4j 2 API. The following Log4j 1.x methods will behave differently when +the Log4j 2 Core module is included then when it is not: + +| Method | Without log4j-core | With log4j-core | +| ----------------------------- | ------------------ | ------------------------------------ | +| Category.getParent() | Returns null | Returns parent logger | +| Category.setLevel() | NoOp | Sets Logger Level | +| Category.setPriority() | NoOp | Sets Logger Level | +| Category.getAdditivity() | Returns false | Returns Logger's additivity setting | +| Category.setAdditivity() | NoOp | Sets additivity of LoggerConfig | +| Category.getResourceBundle() | NoOp | Returns the resource bundle associated with the Logger | +| BasicConfigurator.configure() | NoOp | Reconfigures Log4j 2 | + +If log4j-core is not present location information will not be accurate in calls using the Log4j 1.2 API. The config +package which attempts to convert Log4j 1.x configurations to Log4j 2 is not supported without Log4j 2. + For more information, see [Runtime Dependencies](../runtime-dependencies.html). ## Usage diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfigurationFactory.java b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfigurationFactory.java index d231d82346c..ac9778e097f 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfigurationFactory.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfigurationFactory.java @@ -16,8 +16,6 @@ */ package org.apache.log4j; -import java.net.URI; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; @@ -26,6 +24,8 @@ import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.LoggerConfig; +import java.net.URI; + /** * */ @@ -46,9 +46,7 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final S return new BasicConfiguration(loggerContext); } - public class BasicConfiguration extends AbstractConfiguration { - - private static final long serialVersionUID = -2716784321395089563L; + public static class BasicConfiguration extends AbstractConfiguration { private static final String DEFAULT_LEVEL = "org.apache.logging.log4j.level"; diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java new file mode 100644 index 00000000000..d14dcf9c220 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.varia.NullAppender; +import org.junit.jupiter.api.Test; + +/** + * Test {@link BasicConfigurator}. + */ +public class BasicConfiguratorTest { + + @Test + public void testConfigure() { + // TODO More... + BasicConfigurator.configure(); + } + + @Test + public void testResetConfiguration() { + // TODO More... + BasicConfigurator.resetConfiguration(); + } + + @Test + public void testConfigureAppender() { + BasicConfigurator.configure(null); + // TODO More... + } + + @Test + public void testConfigureConsoleAppender() { + // TODO What to do? Map to Log4j 2 Appender deeper in the code? + BasicConfigurator.configure(new ConsoleAppender()); + } + + @Test + public void testConfigureNullAppender() { + // The NullAppender name is null and we do not want an NPE when the name is used as a key in a ConcurrentHashMap. + BasicConfigurator.configure(NullAppender.getNullAppender()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CallerInformationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CallerInformationTest.java index a5b30907f07..fd35e4a9c76 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/CallerInformationTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CallerInformationTest.java @@ -20,8 +20,8 @@ import java.util.List; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.appender.ListAppender; import org.junit.ClassRule; import org.junit.Test; @@ -34,7 +34,7 @@ public class CallerInformationTest { public static final LoggerContextRule ctx = new LoggerContextRule(CONFIG); @Test - public void testClassLogger() throws Exception { + public void testClassLogger() { final ListAppender app = ctx.getListAppender("Class").clear(); final Logger logger = Logger.getLogger("ClassLogger"); logger.info("Ignored message contents."); @@ -48,7 +48,7 @@ public void testClassLogger() throws Exception { } @Test - public void testMethodLogger() throws Exception { + public void testMethodLogger() { final ListAppender app = ctx.getListAppender("Method").clear(); final Logger logger = Logger.getLogger("MethodLogger"); logger.info("More messages."); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java index f6711933b8d..3d46a78643b 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CategoryTest.java @@ -1,71 +1,83 @@ /* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 + * The ASF licenses this file to You under the Apache license, Version 2.0 * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * See the license for the specific language governing permissions and + * limitations under the license. */ - package org.apache.log4j; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Enumeration; import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.spi.LoggingEvent; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.Cast; +import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.Strings; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; /** * Tests of Category. */ public class CategoryTest { - static ConfigurationFactory cf = new BasicConfigurationFactory(); - - private static ListAppender appender = new ListAppender("List"); + private static final String VERSION1_APPENDER_NAME = "Version1List"; + private static final String VERSION2_APPENDER_NAME = "List"; + private static ListAppender appender = new ListAppender(VERSION2_APPENDER_NAME); + private static org.apache.log4j.ListAppender version1Appender = new org.apache.log4j.ListAppender(); - @BeforeClass + @BeforeAll public static void setupClass() { appender.start(); - ConfigurationFactory.setConfigurationFactory(cf); - LoggerContext.getContext().reconfigure(); + version1Appender.setName(VERSION1_APPENDER_NAME); + System.setProperty(ConfigurationFactory.CONFIGURATION_FACTORY_PROPERTY, BasicConfigurationFactory.class.getName()); } - @AfterClass + @AfterAll public static void cleanupClass() { - ConfigurationFactory.removeConfigurationFactory(cf); appender.stop(); + System.clearProperty(ConfigurationFactory.CONFIGURATION_FACTORY_PROPERTY); } - @Before + @BeforeEach public void before() { appender.clear(); } - + + @Test + public void testExist() { + assertNull(Category.exists("Does not exist for sure")); + } + /** * Tests Category.forcedLog. */ @@ -74,27 +86,66 @@ public void before() { public void testForcedLog() { final MockCategory category = new MockCategory("org.example.foo"); category.setAdditivity(false); - category.getLogger().addAppender(appender); + ((org.apache.logging.log4j.core.Logger) category.getLogger()).addAppender(appender); + // Logging a String category.info("Hello, World"); - final List list = appender.getEvents(); + List list = appender.getEvents(); int events = list.size(); - assertTrue("Number of events should be 1, was " + events, events == 1); + assertEquals(1, events, "Number of events should be 1, was " + events); LogEvent event = list.get(0); Message msg = event.getMessage(); - assertNotNull("No message", msg); - assertTrue("Incorrect Message type", msg instanceof ObjectMessage); + assertNotNull(msg, "No message"); + // LOG4J2-3080: use message type consistently + assertTrue(msg instanceof SimpleMessage, "Incorrect Message type"); + assertEquals("Hello, World", msg.getFormat()); + appender.clear(); + // Logging a String map + category.info(Collections.singletonMap("hello", "world")); + list = appender.getEvents(); + events = list.size(); + assertTrue(events == 1, "Number of events should be 1, was " + events); + event = list.get(0); + msg = event.getMessage(); + assertNotNull(msg, "No message"); + assertTrue(msg instanceof MapMessage, "Incorrect Message type"); Object[] objects = msg.getParameters(); - assertTrue("Incorrect Object type", objects[0] instanceof String); + assertEquals("world", objects[0]); appender.clear(); - category.log(Priority.INFO, "Hello, World"); + // Logging a generic map + category.info(Collections.singletonMap(1234L, "world")); + list = appender.getEvents(); + events = list.size(); + assertTrue(events == 1, "Number of events should be 1, was " + events); + event = list.get(0); + msg = event.getMessage(); + assertNotNull(msg, "No message"); + assertTrue(msg instanceof MapMessage, "Incorrect Message type"); + objects = msg.getParameters(); + assertEquals("world", objects[0]); + appender.clear(); + // Logging an object + final Object obj = new Object(); + category.info(obj); + list = appender.getEvents(); events = list.size(); - assertTrue("Number of events should be 1, was " + events, events == 1); + assertTrue(events == 1, "Number of events should be 1, was " + events); event = list.get(0); msg = event.getMessage(); - assertNotNull("No message", msg); - assertTrue("Incorrect Message type", msg instanceof ObjectMessage); + assertNotNull(msg, "No message"); + assertTrue(msg instanceof ObjectMessage, "Incorrect Message type"); objects = msg.getParameters(); - assertTrue("Incorrect Object type", objects[0] instanceof String); + assertEquals(obj, objects[0]); + appender.clear(); + + category.log(Priority.INFO, "Hello, World"); + list = appender.getEvents(); + events = list.size(); + assertTrue(events == 1, "Number of events should be 1, was " + events); + event = list.get(0); + msg = event.getMessage(); + assertNotNull(msg, "No message"); + assertTrue(msg instanceof SimpleMessage, "Incorrect Message type"); + assertEquals("Hello, World", msg.getFormat()); appender.clear(); } @@ -106,7 +157,7 @@ public void testForcedLog() { @Test public void testGetChainedPriorityReturnType() throws Exception { final Method method = Category.class.getMethod("getChainedPriority", (Class[]) null); - assertTrue(method.getReturnType() == Priority.class); + assertSame(method.getReturnType(), Priority.class); } /** @@ -118,7 +169,7 @@ public void testL7dlog() { logger.setLevel(Level.ERROR); final Priority debug = Level.DEBUG; logger.l7dlog(debug, "Hello, World", null); - assertTrue(appender.getEvents().size() == 0); + assertTrue(appender.getEvents().isEmpty()); } /** @@ -129,8 +180,8 @@ public void testL7dlog4Param() { final Logger logger = Logger.getLogger("org.example.foo"); logger.setLevel(Level.ERROR); final Priority debug = Level.DEBUG; - logger.l7dlog(debug, "Hello, World", new Object[0], null); - assertTrue(appender.getEvents().size() == 0); + logger.l7dlog(debug, "Hello, World", Constants.EMPTY_OBJECT_ARRAY, null); + assertTrue(appender.getEvents().isEmpty()); } /** @@ -148,8 +199,8 @@ public void testExistingLog4j2Logger() { final Priority debug = Level.DEBUG; // the next line will throw an exception if the LogManager loggers // aren't supported by 1.2 Logger/Category - logger.l7dlog(debug, "Hello, World", new Object[0], null); - assertTrue(appender.getEvents().size() == 0); + logger.l7dlog(debug, "Hello, World", Constants.EMPTY_OBJECT_ARRAY, null); + assertTrue(appender.getEvents().isEmpty()); } /** @@ -165,22 +216,192 @@ public void testSetPriority() { logger.setPriority(debug); } + /** + * Tests setPriority(Priority). + * + * @deprecated + */ + @Deprecated + @Test + public void testSetPriorityNull() { + Logger.getLogger("org.example.foo").setPriority(null); + } + @Test public void testClassName() { final Category category = Category.getInstance("TestCategory"); - final Layout layout = PatternLayout.newBuilder().withPattern("%d %p %C{1.} [%t] %m%n").build(); + final Layout layout = PatternLayout.newBuilder().setPattern("%d %p %C{1.} [%t] %m%n").build(); final ListAppender appender = new ListAppender("List2", null, layout, false, false); appender.start(); category.setAdditivity(false); - category.getLogger().addAppender(appender); + ((org.apache.logging.log4j.core.Logger) category.getLogger()).addAppender(appender); category.error("Test Message"); final List msgs = appender.getMessages(); - assertTrue("Incorrect number of messages. Expected 1 got " + msgs.size(), msgs.size() == 1); + assertEquals(1, msgs.size(), "Incorrect number of messages. Expected 1 got " + msgs.size()); final String msg = msgs.get(0); appender.clear(); final String threadName = Thread.currentThread().getName(); final String expected = "ERROR o.a.l.CategoryTest [" + threadName + "] Test Message" + Strings.LINE_SEPARATOR; - assertTrue("Incorrect message " + Strings.dquote(msg) + " expected " + Strings.dquote(expected), msg.endsWith(expected)); + assertTrue(msg.endsWith(expected), + "Incorrect message " + Strings.dquote(msg) + " expected " + Strings.dquote(expected)); + } + + @Test + public void testStringLog() { + final String payload = "payload"; + testMessageImplementation( + payload, + SimpleMessage.class, + message -> assertEquals(message.getFormattedMessage(), payload)); + } + + @Test + public void testCharSequenceLog() { + final CharSequence payload = new CharSequence() { + + @Override + public int length() { + return 3; + } + + @Override + public char charAt(final int index) { + return "abc".charAt(index); + } + + @Override + public CharSequence subSequence(final int start, final int end) { + return "abc".subSequence(start, end); + } + + @Override + public String toString() { + return "abc"; + } + + }; + testMessageImplementation( + payload, + SimpleMessage.class, + message -> assertEquals(message.getFormattedMessage(), payload.toString())); + } + + @Test + public void testMapLog() { + final String key = "key"; + final Object value = 0xDEADBEEF; + final Map payload = Collections.singletonMap(key, value); + testMessageImplementation( + payload, + MapMessage.class, + message -> assertEquals(message.getData(), payload)); + } + + @Test + public void testObjectLog() { + final Object payload = new Object(); + testMessageImplementation( + payload, + ObjectMessage.class, + message -> assertEquals(message.getParameter(), payload)); + } + + private void testMessageImplementation( + final Object messagePayload, + final Class expectedMessageClass, + final Consumer messageTester) { + + // Setup the logger and the appender. + final Category category = Category.getInstance("TestCategory"); + final org.apache.logging.log4j.core.Logger logger = + (org.apache.logging.log4j.core.Logger) category.getLogger(); + logger.addAppender(appender); + + // Log the message payload. + category.info(messagePayload); + + // Verify collected log events. + final List events = appender.getEvents(); + assertEquals(1, events.size(), "was expecting a single event"); + final LogEvent logEvent = events.get(0); + + // Verify the collected message. + final Message message = logEvent.getMessage(); + final Class actualMessageClass = message.getClass(); + assertTrue(expectedMessageClass.isAssignableFrom(actualMessageClass), + "was expecting message to be instance of " + expectedMessageClass + ", found: " + actualMessageClass); + final M typedMessage = Cast.cast(message); + messageTester.accept(typedMessage); + + } + + @Test + public void testAddAppender() { + try { + final Logger rootLogger = LogManager.getRootLogger(); + int count = version1Appender.getEvents().size(); + rootLogger.addAppender(version1Appender); + final Logger logger = LogManager.getLogger(CategoryTest.class); + final org.apache.log4j.ListAppender appender = new org.apache.log4j.ListAppender(); + appender.setName("appender2"); + logger.addAppender(appender); + // Root logger + rootLogger.info("testAddLogger"); + assertEquals(++count, version1Appender.getEvents().size(), "adding at root works"); + assertEquals(0, appender.getEvents().size(), "adding at child works"); + // Another logger + logger.info("testAddLogger2"); + assertEquals(++count, version1Appender.getEvents().size(), "adding at root works"); + assertEquals(1, appender.getEvents().size(), "adding at child works"); + // Call appenders + final LoggingEvent event = new LoggingEvent(); + logger.callAppenders(event); + assertEquals(++count, version1Appender.getEvents().size(), "callAppenders"); + assertEquals(2, appender.getEvents().size(), "callAppenders"); + } finally { + LogManager.resetConfiguration(); + } + } + + @Test + public void testGetAppender() { + try { + final Logger rootLogger = LogManager.getRootLogger(); + final org.apache.logging.log4j.core.Logger v2RootLogger = (org.apache.logging.log4j.core.Logger) rootLogger + .getLogger(); + v2RootLogger.addAppender(AppenderAdapter.adapt(version1Appender)); + v2RootLogger.addAppender(appender); + final List rootAppenders = Collections.list(rootLogger.getAllAppenders()); + assertEquals(1, rootAppenders.size(), "only v1 appenders"); + assertTrue(rootAppenders.get(0) instanceof org.apache.log4j.ListAppender, "appender is a v1 ListAppender"); + assertEquals(VERSION1_APPENDER_NAME, rootLogger.getAppender(VERSION1_APPENDER_NAME).getName(), + "explicitly named appender"); + Appender v2ListAppender = rootLogger.getAppender(VERSION2_APPENDER_NAME); + assertTrue(v2ListAppender instanceof AppenderWrapper, "explicitly named appender"); + assertTrue(((AppenderWrapper) v2ListAppender).getAppender() instanceof ListAppender, + "appender is a v2 ListAppender"); + + final Logger logger = LogManager.getLogger(CategoryTest.class); + final org.apache.logging.log4j.core.Logger v2Logger = (org.apache.logging.log4j.core.Logger) logger + .getLogger(); + final org.apache.log4j.ListAppender loggerAppender = new org.apache.log4j.ListAppender(); + loggerAppender.setName("appender2"); + v2Logger.addAppender(AppenderAdapter.adapt(loggerAppender)); + final List appenders = Collections.list(logger.getAllAppenders()); + assertEquals(1, appenders.size(), "no parent appenders"); + assertEquals(loggerAppender, appenders.get(0)); + assertNull(logger.getAppender(VERSION1_APPENDER_NAME), "no parent appenders"); + assertNull(logger.getAppender(VERSION2_APPENDER_NAME), "no parent appenders"); + + final Logger childLogger = LogManager.getLogger(CategoryTest.class.getName() + ".child"); + final Enumeration childAppenders = childLogger.getAllAppenders(); + assertFalse(childAppenders.hasMoreElements(), "no parent appenders"); + assertNull(childLogger.getAppender("appender2"), "no parent appenders"); + assertNull(childLogger.getAppender(VERSION1_APPENDER_NAME), "no parent appenders"); + assertNull(childLogger.getAppender(VERSION2_APPENDER_NAME), "no parent appenders"); + } finally { + LogManager.resetConfiguration(); + } } /** diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/ConsoleAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/ConsoleAppenderTest.java new file mode 100644 index 00000000000..00a8d00f758 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/ConsoleAppenderTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Used to test Log4j 1 support. + */ +public class ConsoleAppenderTest { + + private ConsoleAppender consoleAppender; + + @BeforeEach + public void beforeEach() { + consoleAppender = new ConsoleAppender(); + } + + @Test + public void testFollow() { + // Only really care that it compiles, behavior is secondary at this level. + consoleAppender.setFollow(true); + assertTrue(consoleAppender.getFollow()); + } + + @Test + public void testTarget() { + // Only really care that it compiles, behavior is secondary at this level. + consoleAppender.setTarget(ConsoleAppender.SYSTEM_OUT); + assertEquals(ConsoleAppender.SYSTEM_OUT, consoleAppender.getTarget()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CustomAppenderSkeleton.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomAppenderSkeleton.java new file mode 100644 index 00000000000..37acfc4a433 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomAppenderSkeleton.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Used to test Log4j 1 support. All we are looking for here is that this code compiles. + */ +public class CustomAppenderSkeleton extends AppenderSkeleton { + + @Override + protected void append(final LoggingEvent event) { + // NOOP @Override + } + + @Override + public void close() { + // NOOP @Override + } + + @SuppressWarnings({"cast", "unused"}) + public void compilerAccessToWriterAppenderSkeletonVariables() { + if (closed) { + // Yep, it compiles. + final boolean compileMe = closed; + } + if (errorHandler instanceof ErrorHandler) { + // Yep, it compiles. + final ErrorHandler other = errorHandler; + } + if (headFilter instanceof Filter) { + // Yep, it compiles. + final Filter other = headFilter; + } + if (layout instanceof Layout) { + // Yep, it compiles. + final Layout other = layout; + } + if (name instanceof String) { + // Yep, it compiles. + final String other = name; + } + if (tailFilter instanceof Filter) { + // Yep, it compiles. + final Filter other = tailFilter; + } + if (threshold instanceof Priority) { + // Yep, it compiles. + final Priority other = threshold; + } + } + + @Override + public boolean requiresLayout() { + // NOOP @Override + return false; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CustomConsoleAppender.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomConsoleAppender.java new file mode 100644 index 00000000000..d26b32fab5f --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomConsoleAppender.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.helpers.QuietWriter; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; + +/** + * Used to test Log4j 1 support. All we are looking for here is that this code compiles. + */ +public class CustomConsoleAppender extends ConsoleAppender { + + public CustomConsoleAppender() { + super(); + } + + public CustomConsoleAppender(final Layout layout) { + super(layout); + } + + public CustomConsoleAppender(final Layout layout, final String target) { + super(layout, target); + } + + @SuppressWarnings({"cast", "unused"}) + public void compilerAccessToConsoleAppenderInstanceVariables() { + if (target instanceof String) { + final String other = name; + } + } + + @SuppressWarnings({"cast", "unused"}) + public void compilerAccessToWriterAppenderInstanceVariables() { + if (immediateFlush) { + final boolean other = immediateFlush; + } + if (encoding instanceof String) { + final String other = encoding; + } + if (qw instanceof QuietWriter) { + final QuietWriter other = qw; + } + } + + @SuppressWarnings({"cast", "unused"}) + public void compilerAccessToWriterAppenderSkeletonVariables() { + if (closed) { + final boolean compileMe = closed; + } + if (errorHandler instanceof ErrorHandler) { + final ErrorHandler other = errorHandler; + } + if (headFilter instanceof Filter) { + final Filter other = headFilter; + } + if (layout instanceof Layout) { + final Layout other = layout; + } + if (name instanceof String) { + final String other = name; + } + if (tailFilter instanceof Filter) { + final Filter other = tailFilter; + } + if (threshold instanceof Priority) { + final Priority other = threshold; + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CustomFileAppender.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomFileAppender.java new file mode 100644 index 00000000000..ae57b8bba0b --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomFileAppender.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j; + +public class CustomFileAppender extends FileAppender { + + private boolean booleanA; + private int intA; + private String stringA; + + public boolean getBooleanA() { + return booleanA; + } + + public int getIntA() { + return intA; + } + + public String getStringA() { + return stringA; + } + + public void setBooleanA(final boolean booleanA) { + this.booleanA = booleanA; + } + + public void setIntA(final int intA) { + this.intA = intA; + } + + public void setStringA(final String stringA) { + this.stringA = stringA; + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CustomNoopAppender.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomNoopAppender.java new file mode 100644 index 00000000000..15e6fa09cbc --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomNoopAppender.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j; + +import org.apache.log4j.spi.LoggingEvent; + +public class CustomNoopAppender extends AppenderSkeleton { + + private boolean booleanA; + private int intA; + private String stringA; + + @Override + protected void append(final LoggingEvent event) { + // Noop + } + + @Override + public void close() { + // Noop + } + + public boolean getBooleanA() { + return booleanA; + } + + public int getIntA() { + return intA; + } + + public String getStringA() { + return stringA; + } + + @Override + public boolean requiresLayout() { + return false; + } + + public void setBooleanA(final boolean booleanA) { + this.booleanA = booleanA; + } + + public void setIntA(final int intA) { + this.intA = intA; + } + + public void setStringA(final String stringA) { + this.stringA = stringA; + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/CustomWriterAppender.java b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomWriterAppender.java new file mode 100644 index 00000000000..b1be153326b --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/CustomWriterAppender.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import org.apache.log4j.helpers.QuietWriter; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; + +/** + * Used to test Log4j 1 support. All we are looking for here is that this code compiles. + */ +public class CustomWriterAppender extends WriterAppender { + + public void compilerAccessToWriterAppenderInstanceVariables() { + if (immediateFlush) { + // Yep, it compiles. + final boolean other = immediateFlush; + } + if (encoding instanceof String) { + // Yep, it compiles. + final String other = encoding; + } + if (qw instanceof QuietWriter) { + // Yep, it compiles. + final QuietWriter other = qw; + } + } + + @SuppressWarnings({"cast", "unused"}) + public void compilerAccessToWriterAppenderSkeletonVariables() { + if (closed) { + // Yep, it compiles. + final boolean compileMe = closed; + } + if (errorHandler instanceof ErrorHandler) { + // Yep, it compiles. + final ErrorHandler other = errorHandler; + } + if (headFilter instanceof Filter) { + // Yep, it compiles. + final Filter other = headFilter; + } + if (layout instanceof Layout) { + // Yep, it compiles. + final Layout other = layout; + } + if (name instanceof String) { + // Yep, it compiles. + final String other = name; + } + if (tailFilter instanceof Filter) { + // Yep, it compiles. + final Filter other = tailFilter; + } + if (threshold instanceof Priority) { + // Yep, it compiles. + final Priority other = threshold; + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LayoutTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LayoutTest.java new file mode 100644 index 00000000000..991c3e93d83 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LayoutTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import org.apache.log4j.spi.LoggingEvent; + +import junit.framework.TestCase; + +/** + * Tests for Layout. + * + */ +public class LayoutTest extends TestCase { + + /** + * Concrete Layout class for tests. + */ + private static final class MockLayout extends Layout { + /** + * @{inheritDoc} + */ + public void activateOptions() { + } + + /** + * @{inheritDoc} + */ + public String format(final LoggingEvent event) { + return "Mock"; + } + + /** + * @{inheritDoc} + */ + public boolean ignoresThrowable() { + return true; + } + } + + /** + * Expected content type. + */ + private final String contentType; + + /** + * Expected value for ignoresThrowable. + */ + private final boolean ignoresThrowable; + + /** + * Expected value for header. + */ + private final String header; + + /** + * Expected value for footer. + */ + private final String footer; + + /** + * Construct a new instance of LayoutTest. + * + * @param testName test name. + */ + public LayoutTest(final String testName) { + super(testName); + contentType = "text/plain"; + ignoresThrowable = true; + header = null; + footer = null; + } + + /** + * Constructor for use by derived tests. + * + * @param testName name of test. + * @param expectedContentType expected value for getContentType(). + * @param expectedIgnoresThrowable expected value for ignoresThrowable(). + * @param expectedHeader expected value for getHeader(). + * @param expectedFooter expected value for getFooter(). + */ + protected LayoutTest(final String testName, final String expectedContentType, final boolean expectedIgnoresThrowable, final String expectedHeader, + final String expectedFooter) { + super(testName); + contentType = expectedContentType; + ignoresThrowable = expectedIgnoresThrowable; + header = expectedHeader; + footer = expectedFooter; + } + + /** + * Creates layout for test. + * + * @return new instance of Layout. + */ + protected Layout createLayout() { + return new MockLayout(); + } + + /** + * Tests format. + * + * @throws Exception derived tests, particular XMLLayoutTest, may throw exceptions. + */ + public void testFormat() throws Exception { + Logger logger = Logger.getLogger("org.apache.log4j.LayoutTest"); + LoggingEvent event = new LoggingEvent("org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null); + String result = createLayout().format(event); + assertEquals("Mock", result); + } + + /** + * Tests getContentType. + */ + public void testGetContentType() { + assertEquals(contentType, createLayout().getContentType()); + } + + /** + * Tests getFooter. + */ + public void testGetFooter() { + assertEquals(footer, createLayout().getFooter()); + } + + /** + * Tests getHeader. + */ + public void testGetHeader() { + assertEquals(header, createLayout().getHeader()); + } + + /** + * Tests ignoresThrowable. + */ + public void testIgnoresThrowable() { + assertEquals(ignoresThrowable, createLayout().ignoresThrowable()); + } + + /** + * Tests Layout.LINE_SEP. + */ + public void testLineSep() { + assertEquals(System.getProperty("line.separator"), Layout.LINE_SEP); + } + + /** + * Tests Layout.LINE_SEP. + */ + public void testLineSepLen() { + assertEquals(Layout.LINE_SEP.length(), Layout.LINE_SEP_LEN); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LevelTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LevelTest.java index bb991ad33da..5f9c673d89d 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/LevelTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LevelTest.java @@ -17,13 +17,14 @@ package org.apache.log4j; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.util.Locale; import org.apache.log4j.util.SerializationTestHelper; import org.junit.Test; -import static org.junit.Assert.*; - /** * Tests of Level. diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/ListAppender.java b/log4j-1.2-api/src/test/java/org/apache/log4j/ListAppender.java new file mode 100644 index 00000000000..c1d5a799aaf --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/ListAppender.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.log4j.spi.LoggingEvent; + +/** + * Used to test Log4j 1 support. + */ +public class ListAppender extends AppenderSkeleton { + // Use Collections.synchronizedList rather than CopyOnWriteArrayList because we expect + // more frequent writes than reads. + final List events = Collections.synchronizedList(new ArrayList<>()); + + private final List messages = Collections.synchronizedList(new ArrayList<>()); + + + private static final String WINDOWS_LINE_SEP = "\r\n"; + + @Override + protected void append(LoggingEvent event) { + Layout layout = getLayout(); + if (layout != null) { + String result = layout.format(event); + if (result != null) { + messages.add(result); + } + } else { + events.add(event); + } + } + + @Override + public void close() { + + } + + @Override + public boolean requiresLayout() { + return false; + } + + /** Returns an immutable snapshot of captured log events */ + public List getEvents() { + return Collections.unmodifiableList(new ArrayList<>(events)); + } + + /** Returns an immutable snapshot of captured messages */ + public List getMessages() { + return Collections.unmodifiableList(new ArrayList<>(messages)); + } + + /** + * Polls the messages list for it to grow to a given minimum size at most timeout timeUnits and return a copy of + * what we have so far. + */ + public List getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) throws InterruptedException { + final long endMillis = System.currentTimeMillis() + timeUnit.toMillis(timeout); + while (messages.size() < minSize && System.currentTimeMillis() < endMillis) { + Thread.sleep(100); + } + return getMessages(); + } + + public String toString() { + return String.format("ListAppender[%s]", getName()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LogManagerTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LogManagerTest.java new file mode 100644 index 00000000000..b3d56b21813 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LogManagerTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j; + +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Test; + +/** + * Tests {@link LogManager}. + */ +public class LogManagerTest { + + private static final String SIMPLE_NAME = LogManagerTest.class.getSimpleName(); + + List getCurrentLoggerNames() { + return Collections.list((Enumeration) LogManager.getCurrentLoggers()).stream().map(Logger::getName).collect(Collectors.toList()); + } + + @Test + public void testGetCurrentLoggers() { + Logger.getLogger(SIMPLE_NAME); + Logger.getLogger(SIMPLE_NAME + ".foo"); + Logger.getLogger(SIMPLE_NAME + ".foo.bar"); + final List names = getCurrentLoggerNames(); + assertTrue(names.contains(SIMPLE_NAME)); + assertTrue(names.contains(SIMPLE_NAME + ".foo")); + assertTrue(names.contains(SIMPLE_NAME + ".foo.bar")); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithMDCTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithMDCTest.java index c48e35ece7a..ece28261648 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithMDCTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithMDCTest.java @@ -16,14 +16,15 @@ */ package org.apache.log4j; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.ClassRule; -import org.junit.Test; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.List; -import static org.junit.Assert.*; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.ClassRule; +import org.junit.Test; /** * Test logging with MDC values. @@ -36,17 +37,22 @@ public class LogWithMDCTest { public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG); @Test - public void testMDC() throws Exception { + public void testMDC() { MDC.put("Key1", "John"); MDC.put("Key2", "Smith"); - final Logger logger = Logger.getLogger("org.apache.test.logging"); - logger.debug("This is a test"); - final ListAppender listApp = (ListAppender) CTX.getAppender("List"); - assertNotNull(listApp); - final List msgs = listApp.getMessages(); - assertNotNull("No messages received", msgs); - assertTrue(msgs.size() == 1); - assertTrue("Key1 is missing", msgs.get(0).contains("Key1=John")); - assertTrue("Key2 is missing", msgs.get(0).contains("Key2=Smith")); + try { + final Logger logger = Logger.getLogger("org.apache.test.logging"); + logger.debug("This is a test"); + final ListAppender listApp = (ListAppender) CTX.getAppender("List"); + assertNotNull(listApp); + final List msgs = listApp.getMessages(); + assertNotNull("No messages received", msgs); + assertTrue(msgs.size() == 1); + assertTrue("Key1 is missing", msgs.get(0).contains("Key1=John")); + assertTrue("Key2 is missing", msgs.get(0).contains("Key2=Smith")); + } finally { + MDC.remove("Key1"); + MDC.remove("Key2"); + } } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithRouteTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithRouteTest.java index 06ad4e8090c..19a464228bd 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithRouteTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LogWithRouteTest.java @@ -16,38 +16,44 @@ */ package org.apache.log4j; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.ClassRule; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Test; /** * Test passing MDC values to the Routing appender. */ +@UsingThreadContextMap public class LogWithRouteTest { private static final String CONFIG = "log-RouteWithMDC.xml"; - @ClassRule - public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG); - @Test - public void testMDC() throws Exception { + @LoggerContextSource(CONFIG) + public void testMDC(final Configuration configuration) { MDC.put("Type", "Service"); MDC.put("Name", "John Smith"); - final Logger logger = Logger.getLogger("org.apache.test.logging"); - logger.debug("This is a test"); - final ListAppender listApp = (ListAppender) CTX.getAppender("List"); - assertNotNull(listApp); - final List msgs = listApp.getMessages(); - assertNotNull("No messages received", msgs); - assertTrue(msgs.size() == 1); - assertTrue("Type is missing", msgs.get(0).contains("Type=Service")); - assertTrue("Name is missing", msgs.get(0).contains("Name=John Smith")); + try { + final Logger logger = Logger.getLogger("org.apache.test.logging"); + logger.debug("This is a test"); + final ListAppender listApp = configuration.getAppender("List"); + assertNotNull(listApp); + final List msgs = listApp.getMessages(); + assertNotNull(msgs, "No messages received"); + assertEquals(1, msgs.size()); + assertTrue(msgs.get(0).contains("Type=Service"), "Type is missing"); + assertTrue(msgs.get(0).contains("Name=John Smith"), "Name is missing"); + } finally { + MDC.remove("Type"); + MDC.remove("Name"); + } } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerJira3410Test.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerJira3410Test.java new file mode 100644 index 00000000000..bdb661a8b40 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerJira3410Test.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.config.TestConfigurator; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.junit.Test; + +/** + * Tests Jira3410. + */ +public class LoggerJira3410Test { + + @Test + public void test() throws Exception { + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/log4j1-list.properties")) { + final Logger logger = LogManager.getLogger("test"); + // + Map map = new HashMap<>(1); + map.put(Long.MAX_VALUE, 1); + logger.debug(map); + // + map.put(null, null); + logger.debug(map); + // + logger.debug(new SortedArrayStringMap((Map) map)); + // + final Configuration configuration = loggerContext.getConfiguration(); + final Map appenders = configuration.getAppenders(); + ListAppender listAppender = null; + for (final Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("list")) { + listAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull("No Message Appender", listAppender); + final List messages = listAppender.getMessages(); + assertTrue("No messages", messages != null && !messages.isEmpty()); + final String msg0 = messages.get(0); + final String msg1 = messages.get(1); + final String msg2 = messages.get(2); + // TODO Should be 1, not "1". + // TODO Where are the {} characters? + assertTrue(msg0, msg0.trim().endsWith(Long.MAX_VALUE + "=\"1\"")); + // + // TODO Should be 1, not "1". + // TODO Should be null, not "null". + // TODO Where are the {} characters? + // TODO Where is the , characters? + assertTrue(msg1, msg1.trim().endsWith("null=\"null\" " + Long.MAX_VALUE + "=\"1\"")); + // + assertTrue(msg2, msg2.trim().endsWith("{null=null, " + Long.MAX_VALUE + "=1}")); + } + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java index d21fa6243df..63403010654 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggerTest.java @@ -17,6 +17,13 @@ package org.apache.log4j; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.List; import java.util.Locale; import java.util.ResourceBundle; @@ -26,14 +33,13 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.*; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; /** * Used for internal unit testing the Logger class. @@ -50,28 +56,26 @@ public class LoggerTest { // A short message. static String MSG = "M"; - static ConfigurationFactory configurationFactory = new BasicConfigurationFactory(); - - @BeforeClass + @BeforeAll public static void setUpClass() { rbUS = ResourceBundle.getBundle("L7D", new Locale("en", "US")); assertNotNull(rbUS); rbFR = ResourceBundle.getBundle("L7D", new Locale("fr", "FR")); - assertNotNull("Got a null resource bundle.", rbFR); + assertNotNull(rbFR, "Got a null resource bundle."); rbCH = ResourceBundle.getBundle("L7D", new Locale("fr", "CH")); - assertNotNull("Got a null resource bundle.", rbCH); + assertNotNull(rbCH, "Got a null resource bundle."); - ConfigurationFactory.setConfigurationFactory(configurationFactory); + System.setProperty(ConfigurationFactory.CONFIGURATION_FACTORY_PROPERTY, BasicConfigurationFactory.class.getName()); } - @AfterClass + @AfterAll public static void tearDownClass() { - ConfigurationFactory.removeConfigurationFactory(configurationFactory); + System.clearProperty(ConfigurationFactory.CONFIGURATION_FACTORY_PROPERTY); } - @After + @AfterEach public void tearDown() { LoggerContext.getContext().reconfigure(); a1 = null; @@ -117,24 +121,25 @@ public void testAppender2() { @Test public void testAdditivity1() { final Logger loggerA = Logger.getLogger("a"); + assertEquals(Level.DEBUG, loggerA.getLevel()); final Logger loggerAB = Logger.getLogger("a.b"); - final CountingAppender coutingAppender = new CountingAppender(); - coutingAppender.start(); + final CountingAppender countingAppender = new CountingAppender(); + countingAppender.start(); try { - loggerA.getLogger().addAppender(coutingAppender); + ((org.apache.logging.log4j.core.Logger) loggerA.getLogger()).addAppender(countingAppender); - assertEquals(0, coutingAppender.counter); + assertEquals(0, countingAppender.counter); loggerAB.debug(MSG); - assertEquals(1, coutingAppender.counter); + assertEquals(1, countingAppender.counter); loggerAB.info(MSG); - assertEquals(2, coutingAppender.counter); + assertEquals(2, countingAppender.counter); loggerAB.warn(MSG); - assertEquals(3, coutingAppender.counter); + assertEquals(3, countingAppender.counter); loggerAB.error(MSG); - assertEquals(4, coutingAppender.counter); - coutingAppender.stop(); + assertEquals(4, countingAppender.counter); + countingAppender.stop(); } finally { - loggerA.getLogger().removeAppender(coutingAppender); + ((org.apache.logging.log4j.core.Logger) loggerA.getLogger()).removeAppender(countingAppender); } } @@ -154,8 +159,8 @@ public void testAdditivity2() { ca2.start(); try { - a.getLogger().addAppender(ca1); - abc.getLogger().addAppender(ca2); + ((org.apache.logging.log4j.core.Logger) a.getLogger()).addAppender(ca1); + ((org.apache.logging.log4j.core.Logger) abc.getLogger()).addAppender(ca2); assertEquals(ca1.counter, 0); assertEquals(ca2.counter, 0); @@ -174,8 +179,8 @@ public void testAdditivity2() { ca1.stop(); ca2.stop(); } finally { - a.getLogger().removeAppender(ca1); - abc.getLogger().removeAppender(ca2); + ((org.apache.logging.log4j.core.Logger) a.getLogger()).removeAppender(ca1); + ((org.apache.logging.log4j.core.Logger) abc.getLogger()).removeAppender(ca2); }} /** @@ -196,9 +201,9 @@ public void testAdditivity3() { final CountingAppender caABC = new CountingAppender(); caABC.start(); try { - root.getLogger().addAppender(caRoot); - a.getLogger().addAppender(caA); - abc.getLogger().addAppender(caABC); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(caRoot); + ((org.apache.logging.log4j.core.Logger) a.getLogger()).addAppender(caA); + ((org.apache.logging.log4j.core.Logger) abc.getLogger()).addAppender(caABC); assertEquals(caRoot.counter, 0); assertEquals(caA.counter, 0); @@ -224,9 +229,9 @@ public void testAdditivity3() { caA.stop(); caABC.stop(); } finally { - root.getLogger().removeAppender(caRoot); - a.getLogger().removeAppender(caA); - abc.getLogger().removeAppender(caABC); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(caRoot); + ((org.apache.logging.log4j.core.Logger) a.getLogger()).removeAppender(caA); + ((org.apache.logging.log4j.core.Logger) abc.getLogger()).removeAppender(caABC); }} /* Don't support getLoggerRepository @@ -317,7 +322,7 @@ public void testRB2() { final Logger root = Logger.getRootLogger(); root.setResourceBundle(rbUS); ResourceBundle t = root.getResourceBundle(); - assertTrue(t == rbUS); + assertSame(t, rbUS); final Logger x = Logger.getLogger("x"); final Logger x_y = Logger.getLogger("x.y"); @@ -337,7 +342,7 @@ public void testRB3() { final Logger root = Logger.getRootLogger(); root.setResourceBundle(rbUS); ResourceBundle t = root.getResourceBundle(); - assertTrue(t == rbUS); + assertSame(t, rbUS); final Logger x = Logger.getLogger("x"); final Logger x_y = Logger.getLogger("x.y"); @@ -389,7 +394,7 @@ public void testTrace() { final ListAppender appender = new ListAppender("List"); appender.start(); final Logger root = Logger.getRootLogger(); - root.getLogger().addAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender); root.setLevel(Level.INFO); final Logger tracer = Logger.getLogger("com.example.Tracer"); @@ -405,7 +410,7 @@ public void testTrace() { assertEquals(org.apache.logging.log4j.Level.TRACE, event.getLevel()); assertEquals("Message 1", event.getMessage().getFormat()); appender.stop(); - root.getLogger().removeAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender); } /** @@ -417,7 +422,7 @@ public void testTraceWithException() { appender.start(); final Logger root = Logger.getRootLogger(); try { - root.getLogger().addAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender); root.setLevel(Level.INFO); final Logger tracer = Logger.getLogger("com.example.Tracer"); @@ -435,7 +440,7 @@ public void testTraceWithException() { assertEquals("Message 1", event.getMessage().getFormattedMessage()); appender.stop(); } finally { - root.getLogger().removeAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender); } } @@ -448,7 +453,7 @@ public void testIsTraceEnabled() { appender.start(); final Logger root = Logger.getRootLogger(); try { - root.getLogger().addAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender); root.setLevel(Level.INFO); final Logger tracer = Logger.getLogger("com.example.Tracer"); @@ -458,31 +463,31 @@ public void testIsTraceEnabled() { assertFalse(root.isTraceEnabled()); appender.stop(); } finally { - root.getLogger().removeAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender); } } @Test @SuppressWarnings("deprecation") public void testLog() { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d %C %L %m").build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d %C %L %m").build(); final ListAppender appender = new ListAppender("List", null, layout, false, false); appender.start(); final Logger root = Logger.getRootLogger(); try { - root.getLogger().addAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender); root.setLevel(Level.INFO); final MyLogger log = new MyLogger(root); log.logInfo("This is a test", null); root.log(Priority.INFO, "Test msg2", null); root.log(Priority.INFO, "Test msg3"); final List msgs = appender.getMessages(); - assertTrue("Incorrect number of messages", msgs.size() == 3); + assertEquals(3, msgs.size(), "Incorrect number of messages"); final String msg = msgs.get(0); - assertTrue("Message contains incorrect class name: " + msg, msg.contains(LoggerTest.class.getName())); + assertTrue(msg.contains(LoggerTest.class.getName()), "Message contains incorrect class name: " + msg); appender.stop(); } finally { - root.getLogger().removeAppender(appender); + ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender); } } @@ -502,12 +507,10 @@ public void logInfo(final String msg, final Throwable t) { private static class CountingAppender extends AbstractAppender { - private static final long serialVersionUID = 1L; - int counter; CountingAppender() { - super("Counter", null, null); + super("Counter", null, null, true, Property.EMPTY_ARRAY); counter = 0; } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/LoggingTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggingTest.java index a30dd881bf5..e72f3bec7c8 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/LoggingTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/LoggingTest.java @@ -16,11 +16,11 @@ */ package org.apache.log4j; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.junit.ClassRule; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.Assert.*; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; /** * @@ -29,15 +29,13 @@ public class LoggingTest { private static final String CONFIG = "log4j2-config.xml"; - @ClassRule - public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG); - @Test + @LoggerContextSource(CONFIG) public void testParent() { final Logger logger = Logger.getLogger("org.apache.test.logging.Test"); final Category parent = logger.getParent(); - assertNotNull("No parent Logger", parent); - assertEquals("Incorrect parent logger", "org.apache.test.logging", parent.getName()); + assertNotNull(parent, "No parent Logger"); + assertEquals("org.apache.test.logging", parent.getName(), "Incorrect parent logger"); } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/MDCTestCase.java b/log4j-1.2-api/src/test/java/org/apache/log4j/MDCTestCase.java index c0e5ba59418..f86a6e67282 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/MDCTestCase.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/MDCTestCase.java @@ -5,9 +5,9 @@ * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -34,14 +34,14 @@ public void tearDown() { } @Test - public void testPut() throws Exception { + public void testPut() { MDC.put("key", "some value"); Assert.assertEquals("some value", MDC.get("key")); Assert.assertEquals(1, MDC.getContext().size()); } @Test - public void testRemoveLastKey() throws Exception { + public void testRemoveLastKey() { MDC.put("key", "some value"); MDC.remove("key"); } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/NDCTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/NDCTest.java index c8baf0ff9ba..41ba01510db 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/NDCTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/NDCTest.java @@ -16,6 +16,8 @@ */ package org.apache.log4j; +import java.util.Stack; + import org.apache.logging.log4j.util.Strings; import org.junit.Assert; import org.junit.Test; @@ -33,4 +35,14 @@ public void testPeekEmpty() { NDC.clear(); Assert.assertEquals(Strings.EMPTY, NDC.peek()); } + + @SuppressWarnings({"rawtypes"}) + @Test + public void testCompileCloneToInherit() { + NDC.inherit(NDC.cloneStack()); + final Stack stackRaw = NDC.cloneStack(); + NDC.inherit(stackRaw); + final Stack stackAny = NDC.cloneStack(); + NDC.inherit(stackAny); + } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/PriorityTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/PriorityTest.java index 63321a74d7e..09f6386b21f 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/PriorityTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/PriorityTest.java @@ -17,12 +17,14 @@ package org.apache.log4j; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.util.Locale; import org.junit.Test; -import static org.junit.Assert.*; - /** * Tests of Priority. * diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java new file mode 100644 index 00000000000..347bfd0c4cc --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggerRepository; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.OptionHandler; +import org.apache.log4j.spi.RootLogger; +import org.apache.log4j.spi.ThrowableRenderer; +import org.apache.log4j.spi.ThrowableRendererSupport; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link PropertyConfigurator}. + */ +public class PropertyConfiguratorTest { + + /** + * Mock definition of FilterBasedTriggeringPolicy from extras companion. + */ + public static final class FilterBasedTriggeringPolicy extends TriggeringPolicy { + private Filter filter; + + public FilterBasedTriggeringPolicy() { + } + + public Filter getFilter() { + return filter; + + } + + public void setFilter(final Filter val) { + filter = val; + } + } + + /** + * Mock definition of FixedWindowRollingPolicy from extras companion. + */ + public static final class FixedWindowRollingPolicy extends RollingPolicy { + private String activeFileName; + private String fileNamePattern; + private int minIndex; + + public FixedWindowRollingPolicy() { + minIndex = -1; + } + + public String getActiveFileName() { + return activeFileName; + } + + public String getFileNamePattern() { + return fileNamePattern; + } + + public int getMinIndex() { + return minIndex; + } + + public void setActiveFileName(final String val) { + activeFileName = val; + } + + public void setFileNamePattern(final String val) { + fileNamePattern = val; + } + + public void setMinIndex(final int val) { + minIndex = val; + } + } + + /** + * Mock ThrowableRenderer for testThrowableRenderer. See bug 45721. + */ + public static class MockThrowableRenderer implements ThrowableRenderer, OptionHandler { + private boolean activated = false; + private boolean showVersion = true; + + public MockThrowableRenderer() { + } + + @Override + public void activateOptions() { + activated = true; + } + + @Override + public String[] doRender(final Throwable t) { + return new String[0]; + } + + public boolean getShowVersion() { + return showVersion; + } + + public boolean isActivated() { + return activated; + } + + public void setShowVersion(final boolean v) { + showVersion = v; + } + } + + /** + * Mock definition of org.apache.log4j.rolling.RollingFileAppender from extras companion. + */ + public static final class RollingFileAppender extends AppenderSkeleton { + private RollingPolicy rollingPolicy; + private TriggeringPolicy triggeringPolicy; + private boolean append; + + public RollingFileAppender() { + + } + + @Override + public void append(final LoggingEvent event) { + } + + @Override + public void close() { + } + + public boolean getAppend() { + return append; + } + + public RollingPolicy getRollingPolicy() { + return rollingPolicy; + } + + public TriggeringPolicy getTriggeringPolicy() { + return triggeringPolicy; + } + + @Override + public boolean requiresLayout() { + return true; + } + + public void setAppend(final boolean val) { + append = val; + } + + public void setRollingPolicy(final RollingPolicy policy) { + rollingPolicy = policy; + } + + public void setTriggeringPolicy(final TriggeringPolicy policy) { + triggeringPolicy = policy; + } + } + + /** + * Mock definition of org.apache.log4j.rolling.RollingPolicy from extras companion. + */ + public static class RollingPolicy implements OptionHandler { + private boolean activated = false; + + public RollingPolicy() { + + } + + @Override + public void activateOptions() { + activated = true; + } + + public final boolean isActivated() { + return activated; + } + + } + + /** + * Mock definition of TriggeringPolicy from extras companion. + */ + public static class TriggeringPolicy implements OptionHandler { + private boolean activated = false; + + public TriggeringPolicy() { + + } + + @Override + public void activateOptions() { + activated = true; + } + + public final boolean isActivated() { + return activated; + } + + } + + private static final String FILTER1_PROPERTIES = "target/test-classes/log4j1-1.2.17/input/filter1.properties"; + + private static final String CAT_A_NAME = "categoryA"; + + private static final String CAT_B_NAME = "categoryB"; + + private static final String CAT_C_NAME = "categoryC"; + + /** + * Test for bug 40944. Did not catch IllegalArgumentException on Properties.load and close input stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + public void testBadUnicodeEscape() throws IOException { + final String fileName = "target/badescape.properties"; + try (FileWriter writer = new FileWriter(fileName)) { + writer.write("log4j.rootLogger=\\uXX41"); + } + PropertyConfigurator.configure(fileName); + final File file = new File(fileName); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + /** + * Tests configuring Log4J from an InputStream. + * + * @since 1.2.17 + */ + @Test + public void testInputStream() throws IOException { + final Path file = Paths.get(FILTER1_PROPERTIES); + assertTrue(Files.exists(file)); + try (InputStream inputStream = Files.newInputStream(file)) { + PropertyConfigurator.configure(inputStream); + } + this.validateNested(); + LogManager.resetConfiguration(); + } + + /** + * Test for bug 47465. configure(URL) did not close opened JarURLConnection. + * + * @throws IOException if IOException creating properties jar. + */ + @Test + public void testJarURL() throws IOException { + final File dir = new File("output"); + dir.mkdirs(); + final File file = new File("output/properties.jar"); + try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file))) { + zos.putNextEntry(new ZipEntry(LogManager.DEFAULT_CONFIGURATION_FILE)); + zos.write("log4j.rootLogger=debug".getBytes()); + zos.closeEntry(); + } + final URL url = new URL("jar:" + file.toURI().toURL() + "!/" + LogManager.DEFAULT_CONFIGURATION_FILE); + PropertyConfigurator.configure(url); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + @Test + public void testLocalVsGlobal() { + LoggerRepository repos1, repos2; + final Logger catA = Logger.getLogger(CAT_A_NAME); + final Logger catB = Logger.getLogger(CAT_B_NAME); + final Logger catC = Logger.getLogger(CAT_C_NAME); + + final Properties globalSettings = new Properties(); + globalSettings.put("log4j.logger." + CAT_A_NAME, Level.WARN.toString()); + globalSettings.put("log4j.logger." + CAT_B_NAME, Level.WARN.toString()); + globalSettings.put("log4j.logger." + CAT_C_NAME, Level.DEBUG.toString()); + PropertyConfigurator.configure(globalSettings); + assertEquals(Level.WARN, catA.getLevel()); + assertEquals(Level.WARN, catB.getLevel()); + assertEquals(Level.DEBUG, catC.getLevel()); + + assertEquals(Level.WARN, catA.getLoggerRepository().getLogger(CAT_A_NAME).getLevel()); + assertEquals(Level.WARN, catB.getLoggerRepository().getLogger(CAT_B_NAME).getLevel()); + assertEquals(Level.DEBUG, catC.getLoggerRepository().getLogger(CAT_C_NAME).getLevel()); + + final Properties repos1Settings = new Properties(); + repos1Settings.put("log4j.logger." + CAT_A_NAME, Level.DEBUG.toString()); + repos1Settings.put("log4j.logger." + CAT_B_NAME, Level.INFO.toString()); + repos1 = new Hierarchy(new RootLogger(Level.OFF)); + new PropertyConfigurator().doConfigure(repos1Settings, repos1); + assertEquals(Level.DEBUG, repos1.getLogger(CAT_A_NAME).getLevel()); + assertEquals(Level.INFO, repos1.getLogger(CAT_B_NAME).getLevel()); + + final Properties repos2Settings = new Properties(); + repos2Settings.put("log4j.logger." + CAT_A_NAME, Level.INFO.toString()); + repos2Settings.put("log4j.logger." + CAT_B_NAME, Level.DEBUG.toString()); + repos2 = new Hierarchy(new RootLogger(Level.OFF)); + new PropertyConfigurator().doConfigure(repos2Settings, repos2); + assertEquals(Level.INFO, repos2.getLogger(CAT_A_NAME).getLevel()); + assertEquals(Level.DEBUG, repos2.getLogger(CAT_B_NAME).getLevel()); + } + + /** + * Tests processing of nested objects, see bug 36384. + */ + public void testNested() { + try { + PropertyConfigurator.configure(FILTER1_PROPERTIES); + this.validateNested(); + } finally { + LogManager.resetConfiguration(); + } + } + + /** + * Test processing of log4j.reset property, see bug 17531. + */ + @Test + public void testReset() { + final VectorAppender appender = new VectorAppender(); + appender.setName("A1"); + Logger.getRootLogger().addAppender(appender); + final Properties properties = new Properties(); + properties.put("log4j.reset", "true"); + PropertyConfigurator.configure(properties); + assertNull(Logger.getRootLogger().getAppender("A1")); + LogManager.resetConfiguration(); + } + + /** + * Test of log4j.throwableRenderer support. See bug 45721. + */ + public void testThrowableRenderer() { + final Properties properties = new Properties(); + properties.put("log4j.throwableRenderer", "org.apache.log4j.PropertyConfiguratorTest$MockThrowableRenderer"); + properties.put("log4j.throwableRenderer.showVersion", "false"); + PropertyConfigurator.configure(properties); + final ThrowableRendererSupport repo = (ThrowableRendererSupport) LogManager.getLoggerRepository(); + final MockThrowableRenderer renderer = (MockThrowableRenderer) repo.getThrowableRenderer(); + LogManager.resetConfiguration(); +// assertNotNull(renderer); +// assertEquals(true, renderer.isActivated()); +// assertEquals(false, renderer.getShowVersion()); + } + + /** + * Test for bug 40944. configure(URL) never closed opened stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + public void testURL() throws IOException { + final File file = new File("target/unclosed.properties"); + try (FileWriter writer = new FileWriter(file)) { + writer.write("log4j.rootLogger=debug"); + } + final URL url = file.toURI().toURL(); + PropertyConfigurator.configure(url); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + /** + * Test for bug 40944. configure(URL) did not catch IllegalArgumentException and did not close stream. + * + * @throws IOException if IOException creating properties file. + */ + @Test + public void testURLBadEscape() throws IOException { + final File file = new File("target/urlbadescape.properties"); + try (FileWriter writer = new FileWriter(file)) { + writer.write("log4j.rootLogger=\\uXX41"); + } + final URL url = file.toURI().toURL(); + PropertyConfigurator.configure(url); + assertTrue(file.delete()); + assertFalse(file.exists()); + } + + public void validateNested() { + final Logger logger = Logger.getLogger("org.apache.log4j.PropertyConfiguratorTest"); + final String appenderName = "ROLLING"; + // Appender OK + final Appender appender = logger.getAppender(appenderName); + assertNotNull(appender); + // Down-cast? +// final RollingFileAppender rfa = (RollingFileAppender) appender; +// assertNotNull(appenderName, rfa); +// final FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) rfa.getRollingPolicy(); +// assertEquals("filterBase-test1.log", rollingPolicy.getActiveFileName()); +// assertEquals("filterBased-test1.%i", rollingPolicy.getFileNamePattern()); +// assertEquals(0, rollingPolicy.getMinIndex()); +// assertTrue(rollingPolicy.isActivated()); +// final FilterBasedTriggeringPolicy triggeringPolicy = (FilterBasedTriggeringPolicy) rfa.getTriggeringPolicy(); +// final LevelRangeFilter filter = (LevelRangeFilter) triggeringPolicy.getFilter(); +// assertTrue(Level.INFO.equals(filter.getLevelMin())); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/VelocityTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/VelocityTest.java index 99bd5a2c7c6..c1165d74fd3 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/VelocityTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/VelocityTest.java @@ -34,7 +34,7 @@ public class VelocityTest { private static LoggerContext context; - + @BeforeClass public static void setupClass() { context = LoggerContext.getContext(false); @@ -44,8 +44,8 @@ public static void setupClass() { public static void tearDownClass() { Configurator.shutdown(context); StatusLogger.getLogger().reset(); - } - + } + @Test public void testVelocity() { Velocity.init(); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/bridge/LogEventWrapperTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/bridge/LogEventWrapperTest.java new file mode 100644 index 00000000000..3f599d64b26 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/bridge/LogEventWrapperTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j.bridge; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.Test; + +public class LogEventWrapperTest { + + @Test + public void testThread() { + Thread currentThread = Thread.currentThread(); + String threadName = currentThread.getName(); + LoggingEvent log4j1Event = new LoggingEvent() { + + @Override + public String getThreadName() { + return threadName; + } + }; + LogEvent log4j2Event = new LogEventWrapper(log4j1Event); + assertEquals(currentThread.getId(), log4j2Event.getThreadId()); + assertEquals(currentThread.getPriority(), log4j2Event.getThreadPriority()); + } + + @Test + public void testToImmutable() { + LogEventWrapper wrapper = new LogEventWrapper(new LoggingEvent()); + assertSame(wrapper, wrapper.toImmutable()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/builders/BuilderManagerTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/BuilderManagerTest.java new file mode 100644 index 00000000000..562fa317a02 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/BuilderManagerTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders; + +import org.apache.log4j.Appender; +import org.apache.log4j.FileAppender; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.varia.StringMatchFilter; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BuilderManagerTest { + + /** + * This test ensures that instantiation failures due to missing parameters + * always return an empty wrapper instead of null, hence disabling the + * "instantiate by classname" fallback mechanism for supported components. + */ + @Test + public void testReturnInvalidValueOnError() { + final PropertiesConfiguration config = new PropertiesConfiguration(null, null); + final Injector injector = DI.createInjector(); + final BuilderManager manager = injector.getInstance(BuilderManager.class); + final Properties props = new Properties(); + props.setProperty("FILE", FileAppender.class.getName()); + props.setProperty("FILE.filter.1", StringMatchFilter.class.getName()); + // Parse an invalid StringMatchFilter + final Filter filter = manager.parse(StringMatchFilter.class.getName(), "FILE.filter", props, config, + BuilderManager.INVALID_FILTER); + assertEquals(BuilderManager.INVALID_FILTER, filter); + // Parse an invalid FileAppender + final Appender appender = manager.parseAppender("FILE", FileAppender.class.getName(), "FILE", "FILE.layout", + "FILE.filter.", props, config); + assertEquals(BuilderManager.INVALID_APPENDER, appender); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/builders/Log4j2ListAppenderBuilder.java b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/Log4j2ListAppenderBuilder.java new file mode 100644 index 00000000000..ca730ef35d4 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/Log4j2ListAppenderBuilder.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.log4j.builders.appender.AppenderBuilder; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.xml.XmlConfiguration; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.w3c.dom.Element; + +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.log4j.builders.BuilderManager.NAMESPACE; +import static org.apache.log4j.xml.XmlConfiguration.*; + +/** + * Builder for the native Log4j 2.x list appender to be used in the tests. + */ +@Namespace(NAMESPACE) +@Plugin("org.apache.logging.log4j.test.appender.ListAppender") +public class Log4j2ListAppenderBuilder extends AbstractBuilder implements AppenderBuilder { + + public Log4j2ListAppenderBuilder() { + } + + public Log4j2ListAppenderBuilder(final String prefix, final Properties props) { + super(prefix, props); + } + + @Override + public Appender parseAppender(Element element, XmlConfiguration configuration) { + final String name = getNameAttribute(element); + final AtomicReference layout = new AtomicReference<>(); + final AtomicReference filter = new AtomicReference<>(); + forEachElement(element.getChildNodes(), currentElement -> { + switch (currentElement.getTagName()) { + case LAYOUT_TAG : + layout.set(configuration.parseLayout(currentElement)); + break; + case FILTER_TAG : + configuration.addFilter(filter, currentElement); + break; + default : + } + }); + return createAppender(name, layout.get(), filter.get()); + } + + @Override + public Appender parseAppender(String name, String appenderPrefix, String layoutPrefix, String filterPrefix, + Properties props, PropertiesConfiguration configuration) { + final Layout layout = configuration.parseLayout(layoutPrefix, name, props); + final Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name); + return createAppender(name, layout, filter); + } + + private Appender createAppender(String name, Layout layout, Filter filter) { + final org.apache.logging.log4j.core.Layout log4j2Layout = LayoutAdapter.adapt(layout); + return AppenderWrapper.adapt( + ListAppender.newBuilder() + .setName(name) + .setLayout(log4j2Layout) + .setFilter(AbstractBuilder.buildFilters(null, filter)) + .build()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/builders/layout/PatternLayoutBuilderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/layout/PatternLayoutBuilderTest.java new file mode 100644 index 00000000000..e8a8280b169 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/builders/layout/PatternLayoutBuilderTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.builders.layout; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.apache.log4j.Layout; +import org.apache.log4j.bridge.LayoutAdapter; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PatternLayoutBuilderTest { + + static Stream patterns() { + return Arrays.asList( + Arguments.of("%p", "%v1Level"), + Arguments.of("%100p", "%100v1Level"), + Arguments.of("%-100p", "%-100v1Level"), + Arguments.of("%x", "%ndc"), + Arguments.of("%X", "%properties"), + Arguments.of("%.20x", "%.20ndc"), + Arguments.of("%pid", "%pid"), + Arguments.of("%xEx", "%xEx"), + Arguments.of("%XX", "%XX"), + Arguments.of("%p id", "%v1Level id"), + Arguments.of("%x Ex", "%ndc Ex"), + Arguments.of("%X X", "%properties X")) + .stream(); + } + + @ParameterizedTest + @MethodSource("patterns") + public void testLevelPatternReplacement(final String v1Pattern, final String v2Pattern) { + final PatternLayoutBuilder builder = new PatternLayoutBuilder(); + final PatternLayout layout = (PatternLayout) LayoutAdapter.adapt(builder.createLayout(v1Pattern, null)); + assertEquals(v2Pattern, layout.getConversionPattern()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java index 9c973ee2b54..bee8a50418c 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java @@ -1,19 +1,3 @@ -package org.apache.log4j.config; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -31,6 +15,28 @@ * limitations under the license. */ +package org.apache.log4j.config; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.xml.sax.SAXException; + @RunWith(Parameterized.class) public abstract class AbstractLog4j1ConfigurationConverterTest { @@ -49,20 +55,32 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr private final Path pathIn; public AbstractLog4j1ConfigurationConverterTest(final Path path) { - super(); this.pathIn = path; } @Test - public void test() throws IOException { + public void test() throws Exception { final Path tempFile = Files.createTempFile("log4j2", ".xml"); try { final Log4j1ConfigurationConverter.CommandLineArguments cla = new Log4j1ConfigurationConverter.CommandLineArguments(); cla.setPathIn(pathIn); cla.setPathOut(tempFile); Log4j1ConfigurationConverter.run(cla); + checkWellFormedXml(tempFile); + checkUnnecessaryEscaping(tempFile); } finally { Files.deleteIfExists(tempFile); } } + + private void checkUnnecessaryEscaping(Path tempFile) throws IOException { + for (String line : Files.readAllLines(tempFile)) { + assertFalse(line.endsWith(" ")); + } + + } + + private void checkWellFormedXml(Path xmlFilePath) throws SAXException, IOException, ParserConfigurationException { + DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFilePath.toUri().toString()); + } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java new file mode 100644 index 00000000000..f04ec2bbd8a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationTest.java @@ -0,0 +1,528 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.nio.file.FileSystemException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.apache.log4j.ListAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.bridge.AppenderAdapter.Adapter; +import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.bridge.FilterWrapper; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.ConsoleAppender.Target; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.appender.NullAppender; +import org.apache.logging.log4j.core.appender.OutputStreamManager; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.filter.CompositeFilter; +import org.apache.logging.log4j.core.filter.Filterable; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.core.layout.HtmlLayout; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.util.CloseShieldOutputStream; + +public abstract class AbstractLog4j1ConfigurationTest { + + abstract Configuration getConfiguration(String configResourcePrefix) throws URISyntaxException, IOException; + + protected LoggerContext configure(String configResourcePrefix) throws URISyntaxException, IOException { + Configurator.reconfigure(getConfiguration(configResourcePrefix)); + return (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + } + + public void testConsoleCapitalization() throws Exception { + final Configuration config = getConfiguration("config-1.2/log4j-capitalization"); + final Appender capitalized = config.getAppender("ConsoleCapitalized"); + assertNotNull(capitalized); + assertEquals(capitalized.getClass(), ConsoleAppender.class); + final Appender javaStyle = config.getAppender("ConsoleJavaStyle"); + assertNotNull(javaStyle); + assertEquals(javaStyle.getClass(), ConsoleAppender.class); + testConsoleAppender((ConsoleAppender) capitalized, (ConsoleAppender) javaStyle); + } + + private void testConsoleAppender(ConsoleAppender expected, ConsoleAppender actual) { + assertEquals("immediateFlush", expected.getImmediateFlush(), actual.getImmediateFlush()); + assertEquals("target", expected.getTarget(), actual.getTarget()); + assertEquals("layoutClass", expected.getLayout().getClass(), actual.getLayout().getClass()); + if (expected.getLayout() instanceof PatternLayout) { + patternLayoutEquals((PatternLayout) expected.getLayout(), (PatternLayout) actual.getLayout()); + } + } + + private void patternLayoutEquals(PatternLayout expected, PatternLayout actual) { + assertEquals(expected.getCharset(), actual.getCharset()); + assertEquals(expected.getConversionPattern(), actual.getConversionPattern()); + } + + private Layout testConsole(final String configResource) throws Exception { + final Configuration configuration = getConfiguration(configResource); + final String name = "Console"; + final ConsoleAppender appender = configuration.getAppender(name); + assertNotNull("Missing appender '" + name + "' in configuration " + configResource + " → " + configuration, appender); + assertEquals("follow", true, getFollowProperty(appender)); + assertEquals(Target.SYSTEM_ERR, appender.getTarget()); + // + final LoggerConfig loggerConfig = configuration.getLoggerConfig("com.example.foo"); + assertNotNull(loggerConfig); + assertEquals(Level.DEBUG, loggerConfig.getLevel()); + // immediateFlush is always true in Log4j 2.x + configuration.start(); + configuration.stop(); + return appender.getLayout(); + } + + public void testConsoleTtccLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-TTCCLayout"); + assertEquals("%d{ISO8601}{CET} %p - %m%n", layout.getConversionPattern()); + } + + public void testRollingFileAppender() throws Exception { + testRollingFileAppender("config-1.2/log4j-RollingFileAppender", "RFA", "target/hadoop.log.%i"); + } + + public void testDailyRollingFileAppender() throws Exception { + testDailyRollingFileAppender("config-1.2/log4j-DailyRollingFileAppender", "DRFA", "target/hadoop.log%d{.dd-MM-yyyy}"); + } + + public void testRollingFileAppenderWithProperties() throws Exception { + testRollingFileAppender("config-1.2/log4j-RollingFileAppender-with-props", "RFA", "target/hadoop.log.%i"); + } + + public void testSystemProperties1() throws Exception { + final String tempFileName = System.getProperty("java.io.tmpdir") + "/hadoop.log"; + final Path tempFilePath = new File(tempFileName).toPath(); + Files.deleteIfExists(tempFilePath); + final Configuration configuration = getConfiguration("config-1.2/log4j-system-properties-1"); + try { + final RollingFileAppender appender = configuration.getAppender("RFA"); + assertEquals("append", false, getAppendProperty(appender)); + assertEquals("bufferSize", 1000, appender.getManager().getBufferSize()); + assertEquals("immediateFlush", false, appender.getImmediateFlush()); + final DefaultRolloverStrategy rolloverStrategy = (DefaultRolloverStrategy) appender.getManager().getRolloverStrategy(); + assertEquals(16, rolloverStrategy.getMaxIndex()); + final CompositeTriggeringPolicy ctp = (CompositeTriggeringPolicy) appender.getTriggeringPolicy(); + final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); + assertEquals(1, triggeringPolicies.length); + final TriggeringPolicy tp = triggeringPolicies[0]; + assertTrue(tp.getClass().getName(), tp instanceof SizeBasedTriggeringPolicy); + final SizeBasedTriggeringPolicy sbtp = (SizeBasedTriggeringPolicy) tp; + assertEquals(20 * 1024 * 1024, sbtp.getMaxFileSize()); + appender.stop(10, TimeUnit.SECONDS); + // System.out.println("expected: " + tempFileName + " Actual: " + + // appender.getFileName()); + assertEquals(tempFileName, appender.getFileName()); + } finally { + configuration.start(); + configuration.stop(); + try { + Files.deleteIfExists(tempFilePath); + } catch (final FileSystemException e) { + e.printStackTrace(); + } + } + } + + public void testSystemProperties2() throws Exception { + final Configuration configuration = getConfiguration("config-1.2/log4j-system-properties-2"); + final RollingFileAppender appender = configuration.getAppender("RFA"); + final String tmpDir = System.getProperty("java.io.tmpdir"); + assertEquals(tmpDir + "/hadoop.log", appender.getFileName()); + appender.stop(10, TimeUnit.SECONDS); + // Try to clean up + try { + Path path = new File(appender.getFileName()).toPath(); + Files.deleteIfExists(path); + path = new File("${java.io.tmpdir}").toPath(); + Files.deleteIfExists(path); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void testRollingFileAppender(final String configResource, final String name, final String filePattern) + throws Exception { + final Configuration configuration = getConfiguration(configResource); + final Appender appender = configuration.getAppender(name); + assertNotNull(appender); + assertEquals(name, appender.getName()); + assertTrue(appender.getClass().getName(), appender instanceof RollingFileAppender); + final RollingFileAppender rfa = (RollingFileAppender) appender; + assertEquals("append", false, getAppendProperty(rfa)); + assertEquals("bufferSize", 1000, rfa.getManager().getBufferSize()); + assertEquals("immediateFlush", false, rfa.getImmediateFlush()); + assertEquals("target/hadoop.log", rfa.getFileName()); + assertEquals(filePattern, rfa.getFilePattern()); + final TriggeringPolicy triggeringPolicy = rfa.getTriggeringPolicy(); + assertNotNull(triggeringPolicy); + assertTrue(triggeringPolicy.getClass().getName(), triggeringPolicy instanceof CompositeTriggeringPolicy); + final CompositeTriggeringPolicy ctp = (CompositeTriggeringPolicy) triggeringPolicy; + final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); + assertEquals(1, triggeringPolicies.length); + final TriggeringPolicy tp = triggeringPolicies[0]; + assertTrue(tp.getClass().getName(), tp instanceof SizeBasedTriggeringPolicy); + final SizeBasedTriggeringPolicy sbtp = (SizeBasedTriggeringPolicy) tp; + assertEquals(256 * 1024 * 1024, sbtp.getMaxFileSize()); + final RolloverStrategy rolloverStrategy = rfa.getManager().getRolloverStrategy(); + assertTrue(rolloverStrategy.getClass().getName(), rolloverStrategy instanceof DefaultRolloverStrategy); + final DefaultRolloverStrategy drs = (DefaultRolloverStrategy) rolloverStrategy; + assertEquals(20, drs.getMaxIndex()); + configuration.start(); + configuration.stop(); + } + + private void testDailyRollingFileAppender(final String configResource, final String name, final String filePattern) + throws Exception { + final Configuration configuration = getConfiguration(configResource); + try { + final Appender appender = configuration.getAppender(name); + assertNotNull(appender); + assertEquals(name, appender.getName()); + assertTrue(appender.getClass().getName(), appender instanceof RollingFileAppender); + final RollingFileAppender rfa = (RollingFileAppender) appender; + assertEquals("append", false, getAppendProperty(rfa)); + assertEquals("bufferSize", 1000, rfa.getManager().getBufferSize()); + assertEquals("immediateFlush", false, rfa.getImmediateFlush()); + assertEquals("target/hadoop.log", rfa.getFileName()); + assertEquals(filePattern, rfa.getFilePattern()); + final TriggeringPolicy triggeringPolicy = rfa.getTriggeringPolicy(); + assertNotNull(triggeringPolicy); + assertTrue(triggeringPolicy.getClass().getName(), triggeringPolicy instanceof CompositeTriggeringPolicy); + final CompositeTriggeringPolicy ctp = (CompositeTriggeringPolicy) triggeringPolicy; + final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); + assertEquals(1, triggeringPolicies.length); + final TriggeringPolicy tp = triggeringPolicies[0]; + assertTrue(tp.getClass().getName(), tp instanceof TimeBasedTriggeringPolicy); + final TimeBasedTriggeringPolicy tbtp = (TimeBasedTriggeringPolicy) tp; + assertEquals(1, tbtp.getInterval()); + final RolloverStrategy rolloverStrategy = rfa.getManager().getRolloverStrategy(); + assertTrue(rolloverStrategy.getClass().getName(), rolloverStrategy instanceof DefaultRolloverStrategy); + final DefaultRolloverStrategy drs = (DefaultRolloverStrategy) rolloverStrategy; + assertEquals(Integer.MAX_VALUE, drs.getMaxIndex()); + } finally { + configuration.start(); + configuration.stop(); + } + } + + private Layout testFile(final String configResource) throws Exception { + final Configuration configuration = getConfiguration(configResource); + final FileAppender appender = configuration.getAppender("File"); + assertNotNull(appender); + assertEquals("target/mylog.txt", appender.getFileName()); + // + final LoggerConfig loggerConfig = configuration.getLoggerConfig("com.example.foo"); + assertNotNull(loggerConfig); + assertEquals(Level.DEBUG, loggerConfig.getLevel()); + assertEquals("append", false, getAppendProperty(appender)); + assertEquals("bufferSize", 1000, appender.getManager().getBufferSize()); + assertEquals("immediateFlush", false, appender.getImmediateFlush()); + configuration.start(); + configuration.stop(); + return appender.getLayout(); + } + + public void testConsoleEnhancedPatternLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-EnhancedPatternLayout"); + // %p, %X and %x converted to their Log4j 1.x bridge equivalent + assertEquals("%d{ISO8601} [%t][%c] %-5v1Level %properties %ndc: %m%n", layout.getConversionPattern()); + } + + public void testConsoleHtmlLayout() throws Exception { + final HtmlLayout layout = (HtmlLayout) testConsole("config-1.2/log4j-console-HtmlLayout"); + assertEquals("Headline", layout.getTitle()); + assertTrue(layout.isLocationInfo()); + } + + public void testConsolePatternLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-PatternLayout"); + // %p converted to its Log4j 1.x bridge equivalent + assertEquals("%d{ISO8601} [%t][%c] %-5v1Level: %m%n", layout.getConversionPattern()); + } + + public void testConsoleSimpleLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-SimpleLayout"); + assertEquals("%v1Level - %m%n", layout.getConversionPattern()); + } + + public void testFileSimpleLayout() throws Exception { + final PatternLayout layout = (PatternLayout) testFile("config-1.2/log4j-file-SimpleLayout"); + assertEquals("%v1Level - %m%n", layout.getConversionPattern()); + } + + public void testNullAppender() throws Exception { + final Configuration configuration = getConfiguration("config-1.2/log4j-NullAppender"); + final Appender appender = configuration.getAppender("NullAppender"); + assertNotNull(appender); + assertEquals("NullAppender", appender.getName()); + assertTrue(appender.getClass().getName(), appender instanceof NullAppender); + } + + private boolean getFollowProperty(final ConsoleAppender consoleAppender) + throws Exception, NoSuchFieldException, IllegalAccessException { + CloseShieldOutputStream wrapperStream = (CloseShieldOutputStream) getOutputStream(consoleAppender.getManager()); + Field delegateField = CloseShieldOutputStream.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + boolean follow = !System.out.equals(delegateField.get(wrapperStream)); + return follow; + } + + private boolean getAppendProperty(final RollingFileAppender appender) throws Exception { + return getAppendProperty((FileOutputStream) getOutputStream(appender.getManager())); + } + + private boolean getAppendProperty(final FileAppender appender) throws Exception { + return getAppendProperty((FileOutputStream) getOutputStream(appender.getManager())); + } + + private boolean getAppendProperty(final FileOutputStream os) throws Exception { + // Java 11 + final Field appendField = FileDescriptor.class.getDeclaredField("append"); + appendField.setAccessible(true); + return (Boolean) appendField.get(os.getFD()); + } + + private OutputStream getOutputStream(final OutputStreamManager manager) throws Exception { + final Method getOutputStream = OutputStreamManager.class.getDeclaredMethod("getOutputStream"); + getOutputStream.setAccessible(true); + return (OutputStream) getOutputStream.invoke(manager); + } + + private Layout testLayout(final Configuration config, final String appenderName) { + final ConsoleAppender appender = config.getAppender(appenderName); + assertNotNull("Missing appender '" + appenderName + "' in configuration " + config.getConfigurationSource(), appender); + return appender.getLayout(); + } + + /** + * Test if the default values from Log4j 1.x are respected. + */ + public void testDefaultValues() throws Exception { + final Configuration config = getConfiguration("config-1.2/log4j-defaultValues"); + // HtmlLayout + final HtmlLayout htmlLayout = (HtmlLayout) testLayout(config, "HTMLLayout"); + assertNotNull(htmlLayout); + assertEquals("title", "Log4J Log Messages", htmlLayout.getTitle()); + assertEquals("locationInfo", false, htmlLayout.isLocationInfo()); + // PatternLayout + final PatternLayout patternLayout = (PatternLayout) testLayout(config, "PatternLayout"); + assertNotNull(patternLayout); + assertEquals("conversionPattern", "%m%n", patternLayout.getConversionPattern()); + // TTCCLayout + final PatternLayout ttccLayout = (PatternLayout) testLayout(config, "TTCCLayout"); + assertNotNull(ttccLayout); + assertEquals("equivalent conversion pattern", "%r [%t] %p %c %notEmpty{%ndc }- %m%n", ttccLayout.getConversionPattern()); + // TODO: XMLLayout + // final XmlLayout xmlLayout = (XmlLayout) testLayout(config, "XMLLayout"); + // assertNotNull(xmlLayout); + // ConsoleAppender + final ConsoleAppender consoleAppender = config.getAppender("ConsoleAppender"); + assertNotNull(consoleAppender); + assertEquals("target", Target.SYSTEM_OUT, consoleAppender.getTarget()); + boolean follow = getFollowProperty(consoleAppender); + assertEquals("follow", false, follow); + // DailyRollingFileAppender + final RollingFileAppender dailyRollingFileAppender = config.getAppender("DailyRollingFileAppender"); + assertNotNull(dailyRollingFileAppender); + assertEquals("equivalent file pattern", "target/dailyRollingFileAppender%d{.yyyy-MM-dd}", + dailyRollingFileAppender.getFilePattern()); + assertEquals("append", true, getAppendProperty(dailyRollingFileAppender)); + assertEquals("bufferSize", 8192, dailyRollingFileAppender.getManager().getBufferSize()); + assertEquals("immediateFlush", true, dailyRollingFileAppender.getImmediateFlush()); + // FileAppender + final FileAppender fileAppender = config.getAppender("FileAppender"); + assertNotNull(fileAppender); + assertEquals("append", true, getAppendProperty(fileAppender)); + assertEquals("bufferSize", 8192, fileAppender.getManager().getBufferSize()); + assertEquals("immediateFlush", true, fileAppender.getImmediateFlush()); + // RollingFileAppender + final RollingFileAppender rollingFileAppender = config.getAppender("RollingFileAppender"); + assertNotNull(rollingFileAppender); + assertEquals("equivalent file pattern", "target/rollingFileAppender.%i", rollingFileAppender.getFilePattern()); + final CompositeTriggeringPolicy compositePolicy = rollingFileAppender.getManager().getTriggeringPolicy(); + assertEquals(1, compositePolicy.getTriggeringPolicies().length); + final SizeBasedTriggeringPolicy sizePolicy = (SizeBasedTriggeringPolicy) compositePolicy.getTriggeringPolicies()[0]; + assertEquals("maxFileSize", 10 * 1024 * 1024L, sizePolicy.getMaxFileSize()); + final DefaultRolloverStrategy strategy = (DefaultRolloverStrategy) rollingFileAppender.getManager() + .getRolloverStrategy(); + assertEquals("maxBackupIndex", 1, strategy.getMaxIndex()); + assertEquals("append", true, getAppendProperty(rollingFileAppender)); + assertEquals("bufferSize", 8192, rollingFileAppender.getManager().getBufferSize()); + assertEquals("immediateFlush", true, rollingFileAppender.getImmediateFlush()); + config.start(); + config.stop(); + } + + /** + * Checks a hierarchy of filters. + * + * @param filter + * @return the number of filters + */ + private int checkFilters(final org.apache.logging.log4j.core.Filter filter) { + int count = 0; + if (filter instanceof CompositeFilter) { + for (final org.apache.logging.log4j.core.Filter part : ((CompositeFilter) filter).getFiltersArray()) { + count += checkFilters(part); + } + } else if (filter instanceof FilterAdapter) { + // Don't create adapters from wrappers + assertFalse("found FilterAdapter of a FilterWrapper", ((FilterAdapter) filter).getFilter() instanceof FilterWrapper); + count += checkFilters(((FilterAdapter) filter).getFilter()); + } else { + count++; + } + return count; + } + + /** + * Checks a hierarchy of filters. + * + * @param filter + * @return the number of filters + */ + private int checkFilters(final org.apache.log4j.spi.Filter filter) { + int count = 0; + if (filter instanceof FilterWrapper) { + // Don't create wrappers from adapters + assertFalse("found FilterWrapper of a FilterAdapter", ((FilterWrapper) filter).getFilter() instanceof FilterAdapter); + count += checkFilters(((FilterWrapper) filter).getFilter()); + } else { + count++; + } + // We prefer a: + // CompositeFilter of native Log4j 2.x filters + // over a: + // FilterAdapter of a chain of FilterWrappers. + assertNull("found chain of Log4j 1.x filters", filter.getNext()); + return count; + } + + public void testMultipleFilters(final Path folder) throws Exception { + System.setProperty("test.tmpDir", folder.toString()); + try (LoggerContext loggerContext = configure("log4j-multipleFilters")) { + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + // Check only number of filters. + final Filterable console = configuration.getAppender("CONSOLE"); + assertNotNull(console); + assertEquals(4, checkFilters(console.getFilter())); + final Filterable file = configuration.getAppender("FILE"); + assertNotNull(file); + assertEquals(4, checkFilters(file.getFilter())); + final Filterable rfa = configuration.getAppender("RFA"); + assertNotNull(rfa); + assertEquals(4, checkFilters(rfa.getFilter())); + final Filterable drfa = configuration.getAppender("DRFA"); + assertNotNull(drfa); + assertEquals(4, checkFilters(drfa.getFilter())); + // List appenders + final Appender appender = configuration.getAppender("LIST"); + assertNotNull(appender); + assertEquals(3, checkFilters(((Filterable)appender).getFilter())); + final ListAppender legacyAppender = (ListAppender) ((Adapter) appender).getAppender(); + final org.apache.logging.log4j.core.test.appender.ListAppender nativeAppender = configuration.getAppender("LIST2"); + assertEquals(3, checkFilters(((Filterable)nativeAppender).getFilter())); + final Logger logger = LogManager.getLogger(PropertiesConfigurationTest.class); + int expected = 0; + // message blocked by Threshold + logger.trace("NEUTRAL message"); + assertEquals(expected, legacyAppender.getEvents().size()); + assertEquals(expected, nativeAppender.getEvents().size()); + // message blocked by DenyAll filter + logger.warn("NEUTRAL message"); + assertEquals(expected, legacyAppender.getEvents().size()); + assertEquals(expected, nativeAppender.getEvents().size()); + // message accepted by level filter + logger.info("NEUTRAL message"); + expected++; + assertEquals(expected, legacyAppender.getEvents().size()); + assertEquals(expected, nativeAppender.getEvents().size()); + // message accepted by "StartsWith" filter + logger.warn("ACCEPT message"); + expected++; + assertEquals(expected, legacyAppender.getEvents().size()); + assertEquals(expected, nativeAppender.getEvents().size()); + // message blocked by "StartsWith" filter + logger.info("DENY message"); + assertEquals(expected, legacyAppender.getEvents().size()); + assertEquals(expected, nativeAppender.getEvents().size()); + } finally { + System.clearProperty("test.tmpDir"); + } + } + + public void testGlobalThreshold() throws Exception { + try (final LoggerContext ctx = configure("config-1.2/log4j-global-threshold")) { + final Configuration config = ctx.getConfiguration(); + final Filter filter = config.getFilter(); + assertTrue(filter instanceof ThresholdFilter); + ThresholdFilter thresholdFilter = (ThresholdFilter)filter; + assertEquals(Level.INFO, thresholdFilter.getLevel()); + assertEquals(Filter.Result.NEUTRAL, thresholdFilter.getOnMatch()); + assertEquals(Filter.Result.DENY, thresholdFilter.getOnMismatch()); + + final Logger logger = LogManager.getLogger(PropertiesConfigurationTest.class); + // List appender + final Appender appender = config.getAppender("LIST"); + assertNotNull(appender); + final ListAppender legacyAppender = (ListAppender) ((Adapter) appender).getAppender(); + // Stopped by root logger level + logger.trace("TRACE"); + assertEquals(0, legacyAppender.getEvents().size()); + // Stopped by global threshold + logger.debug("DEBUG"); + assertEquals(0, legacyAppender.getEvents().size()); + // Accepted + logger.info("INFO"); + assertEquals(1, legacyAppender.getEvents().size()); + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java new file mode 100644 index 00000000000..90f41a21ace --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.ListAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test configuration from XML. + */ +@Tag("sleepy") +public class AsyncAppenderTest { + + @Test + public void testAsyncXml() throws Exception { + try (final LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/log4j1-async.xml")) { + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + Thread.sleep(50); + final Configuration configuration = loggerContext.getConfiguration(); + final Map appenders = configuration.getAppenders(); + ListAppender messageAppender = null; + for (final Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("list")) { + messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull(messageAppender, "No Message Appender"); + final List messages = messageAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages"); + } + } + + @Test + public void testAsyncProperties() throws Exception { + try (final LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/log4j1-async.properties")) { + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + Thread.sleep(50); + final Configuration configuration = loggerContext.getConfiguration(); + final Map appenders = configuration.getAppenders(); + ListAppender messageAppender = null; + for (final Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("list")) { + messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull(messageAppender, "No Message Appender"); + final List messages = messageAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages"); + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.java new file mode 100644 index 00000000000..8f8769ead23 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.ListAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test configuration from XML. + */ +public class AutoConfigTest { + + @Test + @LoggerContextSource(value = "log4j.xml", v1config = true) + public void testListAppender(final org.apache.logging.log4j.core.LoggerContext context) { + Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + Configuration configuration = context.getConfiguration(); + Map appenders = configuration.getAppenders(); + ListAppender eventAppender = null; + ListAppender messageAppender = null; + for (Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("list")) { + messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } else if (entry.getKey().equals("events")) { + eventAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull(eventAppender, "No Event Appender"); + assertNotNull(messageAppender, "No Message Appender"); + List events = eventAppender.getEvents(); + assertTrue(events != null && events.size() > 0, "No events"); + List messages = messageAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages"); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java index 152f5dd97e6..2cb91bc55f9 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java @@ -1,12 +1,3 @@ -package org.apache.log4j.config; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; - -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -24,6 +15,15 @@ * limitations under the license. */ +package org.apache.log4j.config; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + @RunWith(Parameterized.class) public class Log4j1ConfigurationConverterHadoopTest extends AbstractLog4j1ConfigurationConverterTest { diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java index 2b39d4ff311..ff5c8dc82fb 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java @@ -1,12 +1,3 @@ -package org.apache.log4j.config; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; - -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -24,6 +15,15 @@ * limitations under the license. */ +package org.apache.log4j.config; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + @RunWith(Parameterized.class) public class Log4j1ConfigurationConverterSparkTest extends AbstractLog4j1ConfigurationConverterTest { diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java index 8d3e4e2b3e7..4d7e315268e 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java @@ -16,48 +16,49 @@ */ package org.apache.log4j.config; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.File; +import java.io.Serializable; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.FileSystemException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.TimeUnit; import org.apache.log4j.layout.Log4j1XmlLayout; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.appender.ConsoleAppender.Target; -import org.apache.logging.log4j.core.appender.FileAppender; -import org.apache.logging.log4j.core.appender.NullAppender; -import org.apache.logging.log4j.core.appender.RollingFileAppender; -import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy; -import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; -import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; -import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; -import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy; -import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.layout.HtmlLayout; +import org.apache.logging.log4j.core.filter.ThresholdFilter; import org.apache.logging.log4j.core.layout.PatternLayout; import org.junit.Test; -public class Log4j1ConfigurationFactoryTest { +public class Log4j1ConfigurationFactoryTest extends AbstractLog4j1ConfigurationTest { + + private static final String SUFFIX = ".properties"; + + @Override + protected Configuration getConfiguration(final String configResource) throws URISyntaxException { + final URL configLocation = ClassLoader.getSystemResource(configResource + SUFFIX); + assertNotNull(configLocation, configResource); + final Configuration configuration = new Log4j1ConfigurationFactory().getConfiguration(null, "test", configLocation.toURI()); + assertNotNull(configuration); + return configuration; + } private Layout testConsole(final String configResource) throws Exception { final Configuration configuration = getConfiguration(configResource); final String name = "Console"; final ConsoleAppender appender = configuration.getAppender(name); - assertNotNull("Missing appender '" + name + "' in configuration " + configResource + " → " + configuration, - appender); + assertNotNull(appender, + "Missing appender '" + name + "' in configuration " + configResource + " → " + configuration); assertEquals(Target.SYSTEM_ERR, appender.getTarget()); // final LoggerConfig loggerConfig = configuration.getLoggerConfig("com.example.foo"); @@ -68,182 +69,124 @@ private Layout testConsole(final String configResource) throws Exception { return appender.getLayout(); } - private Layout testFile(final String configResource) throws Exception { - final Configuration configuration = getConfiguration(configResource); - final FileAppender appender = configuration.getAppender("File"); - assertNotNull(appender); - assertEquals("target/mylog.txt", appender.getFileName()); - // - final LoggerConfig loggerConfig = configuration.getLoggerConfig("com.example.foo"); - assertNotNull(loggerConfig); - assertEquals(Level.DEBUG, loggerConfig.getLevel()); - configuration.start(); - configuration.stop(); - return appender.getLayout(); - } - - private Configuration getConfiguration(final String configResource) throws URISyntaxException { - final URL configLocation = ClassLoader.getSystemResource(configResource); - assertNotNull(configResource, configLocation); - final Configuration configuration = new Log4j1ConfigurationFactory().getConfiguration(null, "test", - configLocation.toURI()); - assertNotNull(configuration); - return configuration; - } - - @Test - public void testConsoleEnhancedPatternLayout() throws Exception { - final PatternLayout layout = (PatternLayout) testConsole( - "config-1.2/log4j-console-EnhancedPatternLayout.properties"); - assertEquals("%d{ISO8601} [%t][%c] %-5p %properties %ndc: %m%n", layout.getConversionPattern()); - } + @Override + @Test + public void testConsoleEnhancedPatternLayout() throws Exception { + super.testConsoleEnhancedPatternLayout(); + } - @Test - public void testConsoleHtmlLayout() throws Exception { - final HtmlLayout layout = (HtmlLayout) testConsole("config-1.2/log4j-console-HtmlLayout.properties"); - assertEquals("Headline", layout.getTitle()); - assertTrue(layout.isLocationInfo()); - } + @Override + @Test + public void testConsoleHtmlLayout() throws Exception { + super.testConsoleHtmlLayout(); + } - @Test - public void testConsolePatternLayout() throws Exception { - final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-PatternLayout.properties"); - assertEquals("%d{ISO8601} [%t][%c] %-5p: %m%n", layout.getConversionPattern()); - } + @Test + public void testConsolePatternLayout() throws Exception { + super.testConsolePatternLayout(); + } - @Test - public void testConsoleSimpleLayout() throws Exception { - final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-SimpleLayout.properties"); - assertEquals("%level - %m%n", layout.getConversionPattern()); - } + @Override + @Test + public void testConsoleSimpleLayout() throws Exception { + super.testConsoleSimpleLayout(); + } - @Test - public void testConsoleTtccLayout() throws Exception { - final PatternLayout layout = (PatternLayout) testConsole("config-1.2/log4j-console-TTCCLayout.properties"); - assertEquals("%r [%t] %p %notEmpty{%ndc }- %m%n", layout.getConversionPattern()); - } + @Override + @Test + public void testConsoleTtccLayout() throws Exception { + super.testConsoleTtccLayout(); + } - @Test - public void testConsoleXmlLayout() throws Exception { - final Log4j1XmlLayout layout = (Log4j1XmlLayout) testConsole("config-1.2/log4j-console-XmlLayout.properties"); - assertTrue(layout.isLocationInfo()); - assertFalse(layout.isProperties()); - } + @Test + public void testConsoleXmlLayout() throws Exception { + final Log4j1XmlLayout layout = (Log4j1XmlLayout) testConsole("config-1.2/log4j-console-XmlLayout"); + assertTrue(layout.isLocationInfo()); + assertFalse(layout.isProperties()); + } - @Test - public void testFileSimpleLayout() throws Exception { - final PatternLayout layout = (PatternLayout) testFile("config-1.2/log4j-file-SimpleLayout.properties"); - assertEquals("%level - %m%n", layout.getConversionPattern()); - } + @Override + @Test + public void testFileSimpleLayout() throws Exception { + super.testFileSimpleLayout(); + } - @Test + @Override + @Test public void testNullAppender() throws Exception { - final Configuration configuration = getConfiguration("config-1.2/log4j-NullAppender.properties"); - final Appender appender = configuration.getAppender("NullAppender"); - assertNotNull(appender); - assertEquals("NullAppender", appender.getName()); - assertTrue(appender.getClass().getName(), appender instanceof NullAppender); + super.testNullAppender(); } - @Test - public void testRollingFileAppender() throws Exception { - testRollingFileAppender("config-1.2/log4j-RollingFileAppender.properties", "RFA", "target/hadoop.log.%i"); - } + @Override + @Test + public void testRollingFileAppender() throws Exception { + super.testRollingFileAppender(); + } - @Test - public void testDailyRollingFileAppender() throws Exception { - testDailyRollingFileAppender("config-1.2/log4j-DailyRollingFileAppender.properties", "DRFA", "target/hadoop.log%d{.yyyy-MM-dd}"); - } + @Override + @Test + public void testDailyRollingFileAppender() throws Exception { + super.testDailyRollingFileAppender(); + } - @Test - public void testRollingFileAppenderWithProperties() throws Exception { - testRollingFileAppender("config-1.2/log4j-RollingFileAppender-with-props.properties", "RFA", "target/hadoop.log.%i"); - } + @Test + public void testRollingFileAppenderWithProperties() throws Exception { + super.testRollingFileAppenderWithProperties(); + } - @Test - public void testSystemProperties1() throws Exception { - final String tempFileName = System.getProperty("java.io.tmpdir") + "/hadoop.log"; - final Path tempFilePath = new File(tempFileName).toPath(); - Files.deleteIfExists(tempFilePath); - try { - final Configuration configuration = getConfiguration("config-1.2/log4j-system-properties-1.properties"); - final RollingFileAppender appender = configuration.getAppender("RFA"); - appender.stop(10, TimeUnit.SECONDS); - System.out.println("expected: " + tempFileName + " Actual: " + appender.getFileName()); - assertEquals(tempFileName, appender.getFileName()); - } finally { - try { - Files.deleteIfExists(tempFilePath); - } catch (FileSystemException e) { - e.printStackTrace(); - } - } - } + @Override + @Test + public void testSystemProperties1() throws Exception { + super.testSystemProperties1(); + } - @Test - public void testSystemProperties2() throws Exception { - final Configuration configuration = getConfiguration("config-1.2/log4j-system-properties-2.properties"); - final RollingFileAppender appender = configuration.getAppender("RFA"); - assertEquals("${java.io.tmpdir}/hadoop.log", appender.getFileName()); - appender.stop(10, TimeUnit.SECONDS); - Path path = new File(appender.getFileName()).toPath(); - Files.deleteIfExists(path); - path = new File("${java.io.tmpdir}").toPath(); - Files.deleteIfExists(path); - } + @Override + @Test + public void testSystemProperties2() throws Exception { + super.testSystemProperties2(); + } - private void testRollingFileAppender(final String configResource, final String name, final String filePattern) throws URISyntaxException { - final Configuration configuration = getConfiguration(configResource); - final Appender appender = configuration.getAppender(name); - assertNotNull(appender); - assertEquals(name, appender.getName()); - assertTrue(appender.getClass().getName(), appender instanceof RollingFileAppender); - final RollingFileAppender rfa = (RollingFileAppender) appender; - assertEquals("target/hadoop.log", rfa.getFileName()); - assertEquals(filePattern, rfa.getFilePattern()); - final TriggeringPolicy triggeringPolicy = rfa.getTriggeringPolicy(); - assertNotNull(triggeringPolicy); - assertTrue(triggeringPolicy.getClass().getName(), triggeringPolicy instanceof CompositeTriggeringPolicy); - final CompositeTriggeringPolicy ctp = (CompositeTriggeringPolicy) triggeringPolicy; - final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); - assertEquals(1, triggeringPolicies.length); - final TriggeringPolicy tp = triggeringPolicies[0]; - assertTrue(tp.getClass().getName(), tp instanceof SizeBasedTriggeringPolicy); - final SizeBasedTriggeringPolicy sbtp = (SizeBasedTriggeringPolicy) tp; - assertEquals(256 * 1024 * 1024, sbtp.getMaxFileSize()); - final RolloverStrategy rolloverStrategy = rfa.getManager().getRolloverStrategy(); - assertTrue(rolloverStrategy.getClass().getName(), rolloverStrategy instanceof DefaultRolloverStrategy); - final DefaultRolloverStrategy drs = (DefaultRolloverStrategy) rolloverStrategy; - assertEquals(20, drs.getMaxIndex()); - configuration.start(); - configuration.stop(); - } + @Override + @Test + public void testConsoleCapitalization() throws Exception { + super.testConsoleCapitalization(); + } - private void testDailyRollingFileAppender(final String configResource, final String name, final String filePattern) throws URISyntaxException { - final Configuration configuration = getConfiguration(configResource); - final Appender appender = configuration.getAppender(name); - assertNotNull(appender); - assertEquals(name, appender.getName()); - assertTrue(appender.getClass().getName(), appender instanceof RollingFileAppender); - final RollingFileAppender rfa = (RollingFileAppender) appender; - assertEquals("target/hadoop.log", rfa.getFileName()); - assertEquals(filePattern, rfa.getFilePattern()); - final TriggeringPolicy triggeringPolicy = rfa.getTriggeringPolicy(); - assertNotNull(triggeringPolicy); - assertTrue(triggeringPolicy.getClass().getName(), triggeringPolicy instanceof CompositeTriggeringPolicy); - final CompositeTriggeringPolicy ctp = (CompositeTriggeringPolicy) triggeringPolicy; - final TriggeringPolicy[] triggeringPolicies = ctp.getTriggeringPolicies(); - assertEquals(1, triggeringPolicies.length); - final TriggeringPolicy tp = triggeringPolicies[0]; - assertTrue(tp.getClass().getName(), tp instanceof TimeBasedTriggeringPolicy); - final TimeBasedTriggeringPolicy tbtp = (TimeBasedTriggeringPolicy) tp; - assertEquals(1, tbtp.getInterval()); - final RolloverStrategy rolloverStrategy = rfa.getManager().getRolloverStrategy(); - assertTrue(rolloverStrategy.getClass().getName(), rolloverStrategy instanceof DefaultRolloverStrategy); - final DefaultRolloverStrategy drs = (DefaultRolloverStrategy) rolloverStrategy; - assertEquals(Integer.MAX_VALUE, drs.getMaxIndex()); - configuration.start(); - configuration.stop(); - } + @Override + @Test + public void testDefaultValues() throws Exception { + super.testDefaultValues(); + } + + @Test + public void testUntrimmedValues() throws Exception { + try { + final Configuration config = getConfiguration("config-1.2/log4j-untrimmed"); + final LoggerConfig rootLogger = config.getRootLogger(); + assertEquals(Level.DEBUG, rootLogger.getLevel()); + final Appender appender = config.getAppender("Console"); + assertTrue(appender instanceof ConsoleAppender); + final Layout layout = appender.getLayout(); + assertTrue(layout instanceof PatternLayout); + assertEquals("%v1Level - %m%n", ((PatternLayout)layout).getConversionPattern()); + // No filter support + config.start(); + config.stop(); + } catch (NoClassDefFoundError e) { + fail(e.getMessage()); + } + } + @Test + public void testGlobalThreshold() throws Exception { + try (final LoggerContext ctx = configure("config-1.2/log4j-global-threshold")) { + final Configuration config = ctx.getConfiguration(); + final Filter filter = config.getFilter(); + assertTrue(filter instanceof ThresholdFilter); + ThresholdFilter thresholdFilter = (ThresholdFilter)filter; + assertEquals(Level.INFO, thresholdFilter.getLevel()); + assertEquals(Filter.Result.NEUTRAL, thresholdFilter.getOnMatch()); + assertEquals(Filter.Result.DENY, thresholdFilter.getOnMismatch()); + } + } } diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.java new file mode 100644 index 00000000000..e0399e72dbc --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/MapRewriteAppenderTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.ListAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test RewriteAppender + */ +@UsingThreadContextMap +public class MapRewriteAppenderTest { + + @Test + @LoggerContextSource(value = "log4j1-mapRewrite.xml", v1config = true) + public void testRewrite() { + Logger logger = LogManager.getLogger("test"); + Map map = new HashMap<>(); + map.put("message", "This is a test"); + map.put("hello", "world"); + logger.debug(map); + LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + Configuration configuration = context.getConfiguration(); + Map appenders = configuration.getAppenders(); + ListAppender eventAppender = null; + for (Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("events")) { + eventAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull(eventAppender, "No Event Appender"); + List events = eventAppender.getEvents(); + assertTrue(events != null && events.size() > 0, "No events"); + assertNotNull(events.get(0).getProperties(), "No properties in the event"); + assertTrue(events.get(0).getProperties().containsKey("hello"), "Key was not inserted"); + assertEquals("world", events.get(0).getProperties().get("hello"), "Key value is incorrect"); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/NeutralFilterFixture.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/NeutralFilterFixture.java new file mode 100644 index 00000000000..f6a20b2bc56 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/NeutralFilterFixture.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * A test fixture used by {@code src/test/resources/LOG4J2-3247.properties}. + */ +public class NeutralFilterFixture extends Filter { + + @Override + public int decide(LoggingEvent event) { + return NEUTRAL; + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationFactoryTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationFactoryTest.java new file mode 100644 index 00000000000..549f7c1b5c5 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationFactoryTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Test configuration from Properties. + */ +public class PropertiesConfigurationFactoryTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-file-1.properties"); + } + + @Test + public void testProperties() { + Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + File file = new File("target/temp.A1"); + assertTrue("File A1 was not created", file.exists()); + assertTrue("File A1 is empty", file.length() > 0); + file = new File("target/temp.A2"); + assertTrue("File A2 was not created", file.exists()); + assertTrue("File A2 is empty", file.length() > 0); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java new file mode 100644 index 00000000000..bf4705e9d3f --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java @@ -0,0 +1,320 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.ListAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.bridge.FilterAdapter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.filter.CompositeFilter; +import org.apache.logging.log4j.core.filter.DenyAllFilter; +import org.apache.logging.log4j.core.filter.Filterable; +import org.apache.logging.log4j.core.filter.LevelRangeFilter; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Test configuration from Properties. + */ +public class PropertiesConfigurationTest extends AbstractLog4j1ConfigurationTest { + + private static final String TEST_KEY = "log4j.test.tmpdir"; + private static final String SUFFIX = ".properties"; + + @Override + Configuration getConfiguration(String configResourcePrefix) throws URISyntaxException, IOException { + final String configResource = configResourcePrefix + SUFFIX; + final InputStream inputStream = ClassLoader.getSystemResourceAsStream(configResource); + final ConfigurationSource source = new ConfigurationSource(inputStream); + final LoggerContext context = LoggerContext.getContext(false); + final Configuration configuration = new PropertiesConfigurationFactory().getConfiguration(context, source); + assertNotNull(configuration, "No configuration created"); + configuration.initialize(); + return configuration; + } + + @Test + public void testConfigureNullPointerException() throws Exception { + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/LOG4J2-3247.properties")) { + // [LOG4J2-3247] configure() should not throw an NPE. + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + final Appender appender = configuration.getAppender("CONSOLE"); + assertNotNull(appender); + } + } + + @Test + public void testConsoleAppenderFilter() throws Exception { + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/LOG4J2-3247.properties")) { + // LOG4J2-3281 PropertiesConfiguration.buildAppender not adding filters to appender + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + final Appender appender = configuration.getAppender("CONSOLE"); + assertNotNull(appender); + final Filterable filterable = (Filterable) appender; + final FilterAdapter filter = (FilterAdapter) filterable.getFilter(); + assertNotNull(filter); + assertTrue(filter.getFilter() instanceof NeutralFilterFixture); + } + } + + @Test + public void testCustomAppenderFilter() throws Exception { + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/LOG4J2-3281.properties")) { + // LOG4J2-3281 PropertiesConfiguration.buildAppender not adding filters to appender + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + final Appender appender = configuration.getAppender("CUSTOM"); + assertNotNull(appender); + final Filterable filterable = (Filterable) appender; + final FilterAdapter filter = (FilterAdapter) filterable.getFilter(); + assertNotNull(filter); + assertTrue(filter.getFilter() instanceof NeutralFilterFixture); + } + } + + @Test + public void testConsoleAppenderLevelRangeFilter() throws Exception { + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/LOG4J2-3326.properties")) { + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + final Appender appender = configuration.getAppender("CUSTOM"); + assertNotNull(appender); + final Filterable filterable = (Filterable) appender; + final CompositeFilter filter = (CompositeFilter) filterable.getFilter(); + final org.apache.logging.log4j.core.Filter[] filters = filter.getFiltersArray(); + final LevelRangeFilter customFilterReal = (LevelRangeFilter) filters[0]; + assertEquals(Level.ALL, customFilterReal.getMinLevel()); + final LevelRangeFilter defaultFilter = (LevelRangeFilter) filters[1]; + assertEquals(Level.TRACE, defaultFilter.getMinLevel()); + } + } + + @Test + public void testConfigureAppenderDoesNotExist() throws Exception { + // Verify that we tolerate a logger which specifies an appender that does not exist. + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/LOG4J2-3407.properties")) { + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + } + } + + @Test + public void testListAppender() throws Exception { + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/log4j1-list.properties")) { + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + final Configuration configuration = loggerContext.getConfiguration(); + final Map appenders = configuration.getAppenders(); + ListAppender eventAppender = null; + ListAppender messageAppender = null; + for (final Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("list")) { + messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } else if (entry.getKey().equals("events")) { + eventAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull(eventAppender, "No Event Appender"); + assertNotNull(messageAppender, "No Message Appender"); + final List events = eventAppender.getEvents(); + assertTrue(events != null && events.size() > 0, "No events"); + final List messages = messageAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages"); + } + } + + @Test + public void testProperties() throws Exception { + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/log4j1-file-1.properties")) { + final Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + File file = new File("target/temp.A1"); + assertTrue(file.exists(), "File A1 was not created"); + assertTrue(file.length() > 0, "File A1 is empty"); + file = new File("target/temp.A2"); + assertTrue(file.exists(), "File A2 was not created"); + assertTrue(file.length() > 0, "File A2 is empty"); + } + } + + @Test + public void testSystemProperties() throws Exception { + final String testPathLocation = "target"; + System.setProperty(TEST_KEY, testPathLocation); + try (LoggerContext loggerContext = TestConfigurator.configure("target/test-classes/config-1.2/log4j-FileAppender-with-props.properties")) { + // [LOG4J2-3312] Bridge does not convert properties. + final Configuration configuration = loggerContext.getConfiguration(); + assertNotNull(configuration); + final String name = "FILE_APPENDER"; + final Appender appender = configuration.getAppender(name); + assertNotNull(appender, name); + assertTrue(appender instanceof FileAppender, appender.getClass().getName()); + final FileAppender fileAppender = (FileAppender) appender; + // Two slashes because that's how the config file is setup. + assertEquals(testPathLocation + "/hadoop.log", fileAppender.getFileName()); + } finally { + System.clearProperty(TEST_KEY); + } + } + + + @Override + @Test + public void testConsoleEnhancedPatternLayout() throws Exception { + super.testConsoleEnhancedPatternLayout(); + } + + @Override + @Test + public void testConsoleHtmlLayout() throws Exception { + super.testConsoleHtmlLayout(); + } + + @Override + @Test + public void testConsolePatternLayout() throws Exception { + super.testConsolePatternLayout(); + } + + @Override + @Test + public void testConsoleSimpleLayout() throws Exception { + super.testConsoleSimpleLayout(); + } + + @Override + @Test + public void testFileSimpleLayout() throws Exception { + super.testFileSimpleLayout(); + } + + @Override + @Test + public void testNullAppender() throws Exception { + super.testNullAppender(); + } + + @Override + @Test + public void testConsoleCapitalization() throws Exception { + super.testConsoleCapitalization(); + } + + @Override + @Test + public void testConsoleTtccLayout() throws Exception { + super.testConsoleTtccLayout(); + } + + @Override + @Test + public void testRollingFileAppender() throws Exception { + super.testRollingFileAppender(); + } + + @Override + @Test + public void testDailyRollingFileAppender() throws Exception { + super.testDailyRollingFileAppender(); + } + + @Override + @Test + public void testRollingFileAppenderWithProperties() throws Exception { + super.testRollingFileAppenderWithProperties(); + } + + @Override + @Test + public void testSystemProperties1() throws Exception { + super.testSystemProperties1(); + } + + @Override + @Test + public void testSystemProperties2() throws Exception { + super.testSystemProperties2(); + } + + @Override + @Test + public void testDefaultValues() throws Exception { + super.testDefaultValues(); + } + + @Override + @Test + public void testMultipleFilters(final @TempDir Path tmpFolder) throws Exception { + super.testMultipleFilters(tmpFolder); + } + + @Test + public void testUntrimmedValues() throws Exception { + try { + final Configuration config = getConfiguration("config-1.2/log4j-untrimmed"); + final LoggerConfig rootLogger = config.getRootLogger(); + assertEquals(Level.DEBUG, rootLogger.getLevel()); + final Appender appender = config.getAppender("Console"); + assertTrue(appender instanceof ConsoleAppender); + final Layout layout = appender.getLayout(); + assertTrue(layout instanceof PatternLayout); + assertEquals("%v1Level - %m%n", ((PatternLayout)layout).getConversionPattern()); + final Filter filter = ((Filterable) appender).getFilter(); + assertTrue(filter instanceof DenyAllFilter); + config.start(); + config.stop(); + } catch (NoClassDefFoundError e) { + fail(e.getMessage()); + } + } + + @Override + @Test + public void testGlobalThreshold() throws Exception { + super.testGlobalThreshold(); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesReconfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesReconfigurationTest.java new file mode 100644 index 00000000000..2b7d3bb7ba2 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesReconfigurationTest.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.commons.lang3.function.FailableConsumer; +import org.apache.log4j.CustomFileAppender; +import org.apache.log4j.CustomNoopAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.appender.FileManager; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationListener; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test reconfiguring with an XML configuration. + */ +public class PropertiesReconfigurationTest { + + private class TestListener implements ConfigurationListener { + + @Override + public synchronized void onChange(final Reconfigurable reconfigurable) { + toggle.countDown(); + } + + } + + private static final String CONFIG_CUSTOM_APPENDERS_1 = "target/test-classes/log4j1-appenders-custom-1.properties"; + private static final String CONFIG_CUSTOM_APPENDERS_2 = "target/test-classes/log4j1-appenders-custom-2.properties"; + + private static final String CONFIG_FILE_APPENDER_1 = "target/test-classes/log4j1-file-1.properties"; + private static final String CONFIG_FILE_APPENDER_2 = "target/test-classes/log4j1-file-2.properties"; + + private static final Duration FIVE_MINUTES = Duration.ofMinutes(5); + + private final CountDownLatch toggle = new CountDownLatch(1); + + private void assertCustomFileAppender(final org.apache.log4j.Appender appender, final boolean expectBoolean, final int expectInt, + final String expectString) { + final CustomFileAppender customAppender = (CustomFileAppender) appender; + assertEquals(expectBoolean, customAppender.getBooleanA()); + assertEquals(expectInt, customAppender.getIntA()); + assertEquals(expectString, customAppender.getStringA()); + } + + private void assertCustomNoopAppender(final org.apache.log4j.Appender appender, final boolean expectBoolean, final int expectInt, + final String expectString) { + final CustomNoopAppender customAppender = (CustomNoopAppender) appender; + assertEquals(expectBoolean, customAppender.getBooleanA()); + assertEquals(expectInt, customAppender.getIntA()); + assertEquals(expectString, customAppender.getStringA()); + } + + private void checkConfigureCustomAppenders(final String configPath, final boolean expectAppend, final int expectInt, final String expectString, + FailableConsumer configurator) throws IOException { + final File file = new File(configPath); + assertTrue(file.exists(), "No Config file"); + try (LoggerContext context = TestConfigurator.configure(file.toString())) { + final Logger logger = LogManager.getLogger("test"); + logger.info("Hello"); + // V1 + checkCustomAppender("A1", expectAppend, expectInt, expectString); + checkCustomFileAppender("A2", expectAppend, expectInt, expectString); + } + } + + private void checkConfigureFileAppender(final String configPath, final boolean expectAppend) throws IOException { + final File file = new File(configPath); + assertTrue(file.exists(), "No Config file"); + try (LoggerContext context = TestConfigurator.configure(file.toString())) { + final Logger logger = LogManager.getLogger("test"); + logger.info("Hello"); + final Configuration configuration = context.getConfiguration(); + // Core + checkCoreFileAppender(expectAppend, configuration, "A1"); + checkCoreFileAppender(expectAppend, configuration, "A2"); + // V1 + checkFileAppender(expectAppend, "A1"); + checkFileAppender(expectAppend, "A2"); + } + } + + private void checkCoreFileAppender(final boolean expectAppend, final Appender appender) { + assertNotNull(appender); + final FileAppender fileAppender = (FileAppender) appender; + @SuppressWarnings("resource") + final FileManager manager = fileAppender.getManager(); + assertNotNull(manager); + assertEquals(expectAppend, manager.isAppend()); + } + + private void checkCoreFileAppender(final boolean expectAppend, final Configuration configuration, final String appenderName) { + checkCoreFileAppender(expectAppend, configuration.getAppender(appenderName)); + } + + private void checkCustomAppender(final String appenderName, final boolean expectBoolean, final int expectInt, final String expectString) { + final Logger logger = LogManager.getRootLogger(); + final org.apache.log4j.Appender appender = logger.getAppender(appenderName); + assertNotNull(appender); + assertCustomNoopAppender(appender, expectBoolean, expectInt, expectString); + assertCustomNoopAppender(getAppenderFromContext(appenderName), expectBoolean, expectInt, expectString); + } + + private void checkCustomFileAppender(final String appenderName, final boolean expectBoolean, final int expectInt, final String expectString) { + final Logger logger = LogManager.getRootLogger(); + final org.apache.log4j.Appender appender = logger.getAppender(appenderName); + assertNotNull(appender); + assertCustomFileAppender(appender, expectBoolean, expectInt, expectString); + assertCustomFileAppender(getAppenderFromContext(appenderName), expectBoolean, expectInt, expectString); + } + + private void checkFileAppender(final boolean expectAppend, final String appenderName) { + final Logger logger = LogManager.getRootLogger(); + final org.apache.log4j.Appender appender = logger.getAppender(appenderName); + assertNotNull(appender); + final AppenderWrapper appenderWrapper = (AppenderWrapper) appender; + checkCoreFileAppender(expectAppend, appenderWrapper.getAppender()); + } + + @SuppressWarnings("unchecked") + private T getAppenderFromContext(final String appenderName) { + final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + final LoggerConfig loggerConfig = context.getConfiguration().getRootLogger(); + final AppenderAdapter.Adapter adapter = (AppenderAdapter.Adapter) loggerConfig.getAppenders().get(appenderName); + return adapter != null ? (T) adapter.getAppender() : null; + } + + /** + * Tests that configuring and reconfiguring CUSTOM appenders properly pick up different settings. + */ + @Test + public void testCustomAppenders_TestConfigurator() throws IOException { + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_1, true, 1, "A", TestConfigurator::configure); + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_2, false, 2, "B", TestConfigurator::configure); + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_1, true, 1, "A", TestConfigurator::configure); + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_2, false, 2, "B", TestConfigurator::configure); + } + + /** + * Tests that configuring and reconfiguring CUSTOM appenders properly pick up different settings. + */ + @Test + public void testCustomAppenders_PropertyConfigurator() throws IOException { + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_1, true, 1, "A", PropertyConfigurator::configure); + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_2, false, 2, "B", PropertyConfigurator::configure); + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_1, true, 1, "A", PropertyConfigurator::configure); + checkConfigureCustomAppenders(CONFIG_CUSTOM_APPENDERS_2, false, 2, "B", PropertyConfigurator::configure); + } + + /** + * Tests that configuring and reconfiguring STOCK file appenders properly pick up different settings. + */ + @Test + public void testFileAppenders() throws Exception { + checkConfigureFileAppender(CONFIG_FILE_APPENDER_1, false); + checkConfigureFileAppender(CONFIG_FILE_APPENDER_2, true); + checkConfigureFileAppender(CONFIG_FILE_APPENDER_1, false); + checkConfigureFileAppender(CONFIG_FILE_APPENDER_2, true); + } + + @Test + @Tag("sleepy") + public void testTestListener() throws Exception { + System.setProperty(Log4j1Configuration.MONITOR_INTERVAL, "1"); + final File file = new File(CONFIG_FILE_APPENDER_1); + assertTrue(file.exists(), "No Config file"); + final long configMillis = file.lastModified(); + assertTrue(file.setLastModified(configMillis - FIVE_MINUTES.toMillis()), "Unable to modified file time"); + try (LoggerContext context = TestConfigurator.configure(file.toString())) { + final Logger logger = LogManager.getLogger("test"); + logger.info("Hello"); + final Configuration original = context.getConfiguration(); + final TestListener listener = new TestListener(); + original.addListener(listener); + file.setLastModified(System.currentTimeMillis()); + try { + if (!toggle.await(3, TimeUnit.SECONDS)) { + fail("Reconfiguration timed out"); + } + // Allow reconfiguration to complete. + Thread.sleep(500); + } catch (final InterruptedException ie) { + fail("Reconfiguration interupted"); + } + final Configuration updated = context.getConfiguration(); + assertNotSame(original, updated, "Configurations are the same"); + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.java new file mode 100644 index 00000000000..9ad993c96f4 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesRollingWithPropertiesTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test configuration from Properties. + */ +public class PropertiesRollingWithPropertiesTest { + + private static final String TEST_DIR = "target/PropertiesRollingWithPropertiesTest"; + + @BeforeAll + static void beforeAll() { + System.setProperty("test.directory", TEST_DIR); + } + + @AfterAll + static void afterAll() { + System.clearProperty("test.directory"); + } + + @Test + @CleanUpDirectories(TEST_DIR) + @LoggerContextSource(value = "log4j1-rolling-properties.properties", v1config = true) + public void testProperties(final LoggerContext context) throws Exception { + final Logger logger = context.getLogger("test"); + logger.debug("This is a test of the root logger"); + assertThat(Paths.get(TEST_DIR, "somefile.log")).exists().isNotEmptyFile(); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.java new file mode 100644 index 00000000000..cb92229235e --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.ListAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test RewriteAppender + */ +@UsingThreadContextMap +public class RewriteAppenderTest { + + @Test + @LoggerContextSource(value = "log4j1-rewrite.xml", v1config = true) + public void testRewrite(final LoggerContext context) { + Logger logger = LogManager.getLogger("test"); + ThreadContext.put("key1", "This is a test"); + ThreadContext.put("hello", "world"); + long logTime = System.currentTimeMillis(); + logger.debug("Say hello"); + Configuration configuration = context.getConfiguration(); + Map appenders = configuration.getAppenders(); + ListAppender eventAppender = null; + for (Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("events")) { + eventAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull(eventAppender, "No Event Appender"); + List events = eventAppender.getEvents(); + assertTrue(events != null && events.size() > 0, "No events"); + assertNotNull(events.get(0).getProperties(), "No properties in the event"); + assertTrue(events.get(0).getProperties().containsKey("key2"), "Key was not inserted"); + assertEquals("Log4j", events.get(0).getProperties().get("key2"), "Key value is incorrect"); + assertTrue(events.get(0).getTimeStamp() >= logTime, "Timestamp is before point of logging"); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/SocketAppenderConfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SocketAppenderConfigurationTest.java new file mode 100644 index 00000000000..9434168bca8 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SocketAppenderConfigurationTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Map; + +import org.apache.log4j.layout.Log4j1XmlLayout; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.SocketAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.core.net.TcpSocketManager; +import org.junit.Test; + +/** + * Tests configuring a Syslog appender. + */ +public class SocketAppenderConfigurationTest { + + private SocketAppender check(final Protocol expected, final Configuration configuration) { + final Map appenders = configuration.getAppenders(); + assertNotNull(appenders); + final String appenderName = "socket"; + final Appender appender = appenders.get(appenderName); + assertNotNull(appender, "Missing appender " + appenderName); + final SocketAppender socketAppender = (SocketAppender) appender; + @SuppressWarnings("resource") + final TcpSocketManager manager = (TcpSocketManager) socketAppender.getManager(); + final String prefix = expected + ":"; + assertTrue(manager.getName().startsWith(prefix), () -> String.format("'%s' does not start with '%s'", manager.getName(), prefix)); + // Threshold + final ThresholdFilter filter = (ThresholdFilter) socketAppender.getFilter(); + assertEquals(Level.DEBUG, filter.getLevel()); + // Host + assertEquals("localhost", manager.getHost()); + // Port + assertEquals(9999, manager.getPort()); + // Port + assertEquals(100, manager.getReconnectionDelayMillis()); + return socketAppender; + } + + private void checkProtocolPropertiesConfig(final Protocol expected, final String xmlPath) throws IOException { + check(expected, TestConfigurator.configure(xmlPath).getConfiguration()); + } + + private SocketAppender checkProtocolXmlConfig(final Protocol expected, final String xmlPath) throws IOException { + return check(expected, TestConfigurator.configure(xmlPath).getConfiguration()); + } + + @Test + public void testProperties() throws Exception { + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-socket.properties"); + } + + @Test + public void testPropertiesXmlLayout() throws Exception { + final SocketAppender socketAppender = checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-socket-xml-layout.properties"); + assertTrue(socketAppender.getLayout() instanceof Log4j1XmlLayout); + } + + @Test + public void testXml() throws Exception { + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-socket.xml"); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/StartsWithFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/StartsWithFilter.java new file mode 100644 index 00000000000..db16d84f653 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/StartsWithFilter.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * A Filter used in tests. + */ +public class StartsWithFilter extends Filter { + + @Override + public int decide(LoggingEvent event) { + String message = String.valueOf(event.getMessage()); + if (message.startsWith("DENY")) { + return DENY; + } else if (message.startsWith("ACCEPT")) { + return ACCEPT; + } + return NEUTRAL; + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderConfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderConfigurationTest.java new file mode 100644 index 00000000000..cdf9b2f8c22 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderConfigurationTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.SyslogAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.core.net.AbstractSocketManager; +import org.apache.logging.log4j.core.net.Protocol; +import org.junit.Test; + +/** + * Tests configuring a Syslog appender. + */ +public class SyslogAppenderConfigurationTest { + + private void check(final Protocol expected, final Configuration configuration) { + final Map appenders = configuration.getAppenders(); + assertNotNull(appenders); + final String appenderName = "syslog"; + final Appender appender = appenders.get(appenderName); + assertNotNull(appender, "Missing appender " + appenderName); + final SyslogAppender syslogAppender = (SyslogAppender) appender; + @SuppressWarnings("resource") + final AbstractSocketManager manager = syslogAppender.getManager(); + final String prefix = expected + ":"; + assertTrue(manager.getName().startsWith(prefix), () -> String.format("'%s' does not start with '%s'", manager.getName(), prefix)); + // Threshold + final ThresholdFilter filter = (ThresholdFilter) syslogAppender.getFilter(); + assertEquals(Level.DEBUG, filter.getLevel()); + // Host + assertEquals("localhost", manager.getHost()); + // Port + assertEquals(9999, manager.getPort()); + } + + private void checkProtocolPropertiesConfig(final Protocol expected, final String xmlPath) throws IOException { + check(expected, TestConfigurator.configure(xmlPath).getConfiguration()); + } + + private void checkProtocolXmlConfig(final Protocol expected, final String xmlPath) throws IOException { + check(expected, TestConfigurator.configure(xmlPath).getConfiguration()); + } + + @Test + public void testPropertiesProtocolDefault() throws Exception { + checkProtocolPropertiesConfig(Protocol.TCP, "target/test-classes/log4j1-syslog-protocol-default.properties"); + } + + @Test + public void testPropertiesProtocolTcp() throws Exception { + checkProtocolPropertiesConfig(Protocol.TCP, "target/test-classes/log4j1-syslog-protocol-tcp.properties"); + } + + @Test + public void testPropertiesProtocolUdp() throws Exception { + checkProtocolPropertiesConfig(Protocol.UDP, "target/test-classes/log4j1-syslog-protocol-udp.properties"); + } + + @Test + public void testXmlProtocolDefault() throws Exception { + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-syslog.xml"); + } + + @Test + public void testXmlProtocolTcp() throws Exception { + checkProtocolXmlConfig(Protocol.TCP, "target/test-classes/log4j1-syslog-protocol-tcp.xml"); + } + + @Test + public void testXmlProtocolUdp() throws Exception { + checkProtocolXmlConfig(Protocol.UDP, "target/test-classes/log4j1-syslog-protocol-udp.xml"); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderTest.java new file mode 100644 index 00000000000..346f9e06c63 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.net.mock.MockSyslogServer; +import org.apache.logging.log4j.core.test.net.mock.MockSyslogServerFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Class Description goes here. + */ +@Tag("sleepy") +public class SyslogAppenderTest { + + // TODO Use an ephemeral port, save it in a sys prop, and update test config files. + private static final int PORTNUM = 9999; + private MockSyslogServer syslogServer; + + @BeforeAll + public static void beforeClass() { + System.setProperty("log4j.configuration", "target/test-classes/log4j1-syslog.xml"); + } + + @BeforeEach + public void setUp() { + } + + @AfterEach + public void teardown() { + if (syslogServer != null) { + syslogServer.shutdown(); + } + } + + @Test + public void sendMessage() throws Exception { + initTCPTestEnvironment(); + Logger logger = LogManager.getLogger(SyslogAppenderTest.class); + logger.info("This is a test"); + List messages = null; + for (int i = 0; i < 5; ++i) { + Thread.sleep(250); + messages = syslogServer.getMessageList(); + if (messages != null && messages.size() > 0) { + break; + } + } + assertNotNull(messages, "No messages received"); + assertEquals(1, messages.size(), "Sent message not detected"); + } + + + protected void initTCPTestEnvironment() throws IOException { + syslogServer = MockSyslogServerFactory.createTCPSyslogServer(1, PORTNUM); + syslogServer.start(); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/TestConfigurator.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/TestConfigurator.java new file mode 100644 index 00000000000..c87156682b9 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/TestConfigurator.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.log4j.xml.XmlConfigurationFactory; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Configurator; + +public class TestConfigurator { + + public static LoggerContext configure(final String configLocation) throws IOException { + final Path path = Paths.get(configLocation); + try (final InputStream inputStream = Files.newInputStream(path)) { + final ConfigurationSource source = new ConfigurationSource(inputStream, path); + final LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + Configuration configuration = null; + if (configLocation.endsWith(PropertiesConfigurationFactory.FILE_EXTENSION)) { + configuration = new PropertiesConfigurationFactory().getConfiguration(context, source); + } else if (configLocation.endsWith(XmlConfigurationFactory.FILE_EXTENSION)) { + configuration = new XmlConfigurationFactory().getConfiguration(context, source); + } else { + fail("Test infra does not support " + configLocation); + } + assertNotNull("No configuration created", configuration); + Configurator.reconfigure(configuration); + return context; + } + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.java new file mode 100644 index 00000000000..8967e6d80b0 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Test configuration from XML. + */ +public class XmlConfigurationFactoryTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-file.xml"); + } + @Test + public void testXML() { + Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + File file = new File("target/temp.A1"); + assertTrue("File A1 was not created", file.exists()); + assertTrue("File A1 is empty", file.length() > 0); + file = new File("target/temp.A2"); + assertTrue("File A2 was not created", file.exists()); + assertTrue("File A2 is empty", file.length() > 0); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java new file mode 100644 index 00000000000..f1e118bee8e --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.ListAppender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.bridge.AppenderAdapter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.xml.XmlConfigurationFactory; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.util.Lazy; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test configuration from XML. + */ +public class XmlConfigurationTest extends AbstractLog4j1ConfigurationTest { + + private static final String SUFFIX = ".xml"; + + @Override + Configuration getConfiguration(String configResourcePrefix) throws URISyntaxException, IOException { + final String configResource = configResourcePrefix + SUFFIX; + final InputStream inputStream = ClassLoader.getSystemResourceAsStream(configResource); + final ConfigurationSource source = new ConfigurationSource(inputStream); + final LoggerContext context = LoggerContext.getContext(false); + final Configuration configuration = context.getInjector() + .registerBinding(ConfigurationFactory.KEY, Lazy.lazy(XmlConfigurationFactory::new)) + .getInstance(ConfigurationFactory.KEY) + .getConfiguration(context, source); + assertNotNull(configuration, "No configuration created"); + configuration.initialize(); + return configuration; + } + + @Test + public void testXML() throws Exception { + configure("log4j1-file"); + Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + File file = new File("target/temp.A1"); + assertTrue(file.exists(), "File A1 was not created"); + assertTrue(file.length() > 0, "File A1 is empty"); + file = new File("target/temp.A2"); + assertTrue(file.exists(), "File A2 was not created"); + assertTrue(file.length() > 0, "File A2 is empty"); + } + + @Test + public void testListAppender() throws Exception { + LoggerContext loggerContext = configure("log4j1-list"); + Logger logger = LogManager.getLogger("test"); + logger.debug("This is a test of the root logger"); + Configuration configuration = loggerContext.getConfiguration(); + Map appenders = configuration.getAppenders(); + ListAppender eventAppender = null; + ListAppender messageAppender = null; + for (Map.Entry entry : appenders.entrySet()) { + if (entry.getKey().equals("list")) { + messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } else if (entry.getKey().equals("events")) { + eventAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender(); + } + } + assertNotNull(eventAppender, "No Event Appender"); + assertNotNull(messageAppender, "No Message Appender"); + List events = eventAppender.getEvents(); + assertTrue(events != null && events.size() > 0, "No events"); + List messages = messageAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages"); + } + + @Override + @Test + public void testConsoleEnhancedPatternLayout() throws Exception { + super.testConsoleEnhancedPatternLayout(); + } + + @Override + @Test + public void testConsoleHtmlLayout() throws Exception { + super.testConsoleHtmlLayout(); + } + + @Override + @Test + public void testConsolePatternLayout() throws Exception { + super.testConsolePatternLayout(); + } + + @Override + @Test + public void testConsoleSimpleLayout() throws Exception { + super.testConsoleSimpleLayout(); + } + + @Override + @Test + public void testFileSimpleLayout() throws Exception { + super.testFileSimpleLayout(); + } + + @Override + @Test + public void testNullAppender() throws Exception { + super.testNullAppender(); + } + + @Override + @Test + public void testConsoleCapitalization() throws Exception { + super.testConsoleCapitalization(); + } + + @Override + @Test + public void testConsoleTtccLayout() throws Exception { + super.testConsoleTtccLayout(); + } + + @Override + @Test + public void testRollingFileAppender() throws Exception { + super.testRollingFileAppender(); + } + + @Override + @Test + public void testDailyRollingFileAppender() throws Exception { + super.testDailyRollingFileAppender(); + } + + @Override + @Test + public void testSystemProperties1() throws Exception { + super.testSystemProperties1(); + } + + @Override + @Test + public void testDefaultValues() throws Exception { + super.testDefaultValues(); + } + + @Override + @Test + public void testMultipleFilters(final @TempDir Path tmpFolder) throws Exception { + super.testMultipleFilters(tmpFolder); + } + + @Override + @Test + public void testGlobalThreshold() throws Exception { + super.testGlobalThreshold(); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlReconfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlReconfigurationTest.java new file mode 100644 index 00000000000..835f1c44215 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlReconfigurationTest.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.XmlConfigurationFactory; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationListener; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test reconfiguring with an XML configuration. + */ +@Tag("sleepy") +public class XmlReconfigurationTest { + + private static final String CONFIG = "target/test-classes/log4j1-file.xml"; + private static final long FIVE_MINUTES = 5 * 60 * 1000; + + private final CountDownLatch toggle = new CountDownLatch(1); + + @Test + public void testReconfiguration() throws Exception { + System.setProperty(Log4j1Configuration.MONITOR_INTERVAL, "1"); + File file = new File(CONFIG); + assertNotNull(file, "No Config file"); + long configMillis = file.lastModified(); + Assertions.assertTrue(file.setLastModified(configMillis - FIVE_MINUTES), "Unable to modified file time"); + LoggerContext context = configure(file); + Logger logger = LogManager.getLogger("test"); + logger.info("Hello"); + Configuration original = context.getConfiguration(); + TestListener listener = new TestListener(); + original.addListener(listener); + file.setLastModified(System.currentTimeMillis()); + try { + if (!toggle.await(3, TimeUnit.SECONDS)) { + fail("Reconfiguration timed out"); + } + // Allow reconfiguration to complete. + Thread.sleep(500); + } catch (InterruptedException ie) { + fail("Reconfiguration interupted"); + } + Configuration updated = context.getConfiguration(); + assertNotSame(original, updated, "Configurations are the same"); + } + + private class TestListener implements ConfigurationListener { + + public synchronized void onChange(final Reconfigurable reconfigurable) { + toggle.countDown(); + } + + } + + private LoggerContext configure(File configFile) throws Exception { + InputStream is = new FileInputStream(configFile); + ConfigurationSource source = new ConfigurationSource(is, configFile); + LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false); + Configuration configuration = new XmlConfigurationFactory().getConfiguration(context, source); + assertNotNull(configuration, "No configuration created"); + Configurator.reconfigure(configuration); + return context; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.java new file mode 100644 index 00000000000..05ee7db08bc --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlRollingWithPropertiesTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.config; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test configuration from Properties. + */ +public class XmlRollingWithPropertiesTest { + + private static final String TEST_DIR = "target/XmlRollingWithPropertiesTest"; + + @BeforeAll + static void beforeAll() { + System.setProperty("test.directory", TEST_DIR); + } + + @AfterAll + static void afterAll() { + System.clearProperty("test.directory"); + } + + @Test + @CleanUpDirectories(TEST_DIR) + @LoggerContextSource(value = "log4j1-rolling-properties.xml", v1config = true) + public void testProperties(final LoggerContext context) { + // ${test.directory}/logs/etl.log + final Logger logger = context.getLogger("test"); + logger.debug("This is a test of the root logger"); + assertThat(Paths.get(TEST_DIR, "logs/etl.log")).exists().isNotEmptyFile(); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase1.java b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase1.java new file mode 100644 index 00000000000..44f5f04ec7a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase1.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.defaultInit; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.junit.Ignore; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +@Ignore +public class TestCase1 extends TestCase { + + public TestCase1(String name) { + super(name); + } + + public void setUp() { + } + + public void tearDown() { + LogManager.shutdown(); + } + + public void noneTest() { + Logger root = Logger.getRootLogger(); + boolean rootIsConfigured = root.getAllAppenders().hasMoreElements(); + assertTrue(!rootIsConfigured); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new TestCase1("noneTest")); + return suite; + } + +} + diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase2.java b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase2.java new file mode 100644 index 00000000000..33b2915903c --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase2.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.defaultInit; + +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.framework.Test; +import java.util.Enumeration; +import org.apache.log4j.Appender; +import org.apache.log4j.Logger; +import org.junit.Ignore; +import org.apache.log4j.LogManager; + +@Ignore +public class TestCase2 extends TestCase { + + public TestCase2(String name) { + super(name); + } + + public void setUp() { + } + + public void tearDown() { + LogManager.shutdown(); + } + + public void xmlTest() { + Logger root = Logger.getRootLogger(); + boolean rootIsConfigured = root.getAllAppenders().hasMoreElements(); + assertTrue(rootIsConfigured); + Enumeration e = root.getAllAppenders(); + Appender appender = (Appender) e.nextElement(); + assertEquals(appender.getName(), "D1"); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new TestCase2("xmlTest")); + return suite; + } + +} + diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase3.java b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase3.java new file mode 100644 index 00000000000..73c176f6e03 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase3.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.defaultInit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Enumeration; + +import org.apache.log4j.Appender; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.config.TestConfigurator; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled +public class TestCase3 { + + @AfterAll + public static void tearDown() { + LogManager.shutdown(); + } + + @Test + public void propertiesTest() throws IOException { + TestConfigurator.configure("src/test/resources/log4j1-1.2.17/input/defaultInit3.properties"); + Logger root = Logger.getRootLogger(); + boolean rootIsConfigured = root.getAllAppenders().hasMoreElements(); + assertTrue(rootIsConfigured); + Enumeration e = root.getAllAppenders(); + Appender appender = (Appender) e.nextElement(); + assertEquals(appender.getName(), "D3"); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase4.java b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase4.java new file mode 100644 index 00000000000..c44b546d156 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/defaultInit/TestCase4.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.defaultInit; + +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.framework.Test; +import java.util.Enumeration; +import org.apache.log4j.Appender; +import org.apache.log4j.Logger; +import org.junit.Ignore; +import org.apache.log4j.LogManager; + +@Ignore +public class TestCase4 extends TestCase { + + public TestCase4(String name) { + super(name); + } + + public void setUp() { + } + + public void tearDown() { + LogManager.shutdown(); + } + + public void combinedTest() { + Logger root = Logger.getRootLogger(); + boolean rootIsConfigured = root.getAllAppenders().hasMoreElements(); + assertTrue(rootIsConfigured); + Enumeration e = root.getAllAppenders(); + Appender appender = (Appender) e.nextElement(); + assertEquals(appender.getName(), "D1"); + assertEquals(e.hasMoreElements(), false); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new TestCase4("combinedTest")); + return suite; + } + +} + diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/BoundedFIFOTestCase.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/BoundedFIFOTestCase.java new file mode 100644 index 00000000000..c5ff7179fbf --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/BoundedFIFOTestCase.java @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test {@link BoundedFIFO}. + * + * @since 0.9.1 + */ +public class BoundedFIFOTestCase extends TestCase { + static Logger cat = Logger.getLogger("x"); + + static int MAX = 1000; + + static LoggingEvent[] e = new LoggingEvent[MAX]; + + public static Test suite() { + final TestSuite suite = new TestSuite(); + suite.addTest(new BoundedFIFOTestCase("test1")); + suite.addTest(new BoundedFIFOTestCase("test2")); + suite.addTest(new BoundedFIFOTestCase("testResize1")); + suite.addTest(new BoundedFIFOTestCase("testResize2")); + suite.addTest(new BoundedFIFOTestCase("testResize3")); + return suite; + } + + { + for (int i = 0; i < MAX; i++) { + e[i] = new LoggingEvent("", cat, Level.DEBUG, "e" + i, null); + } + } + + public BoundedFIFOTestCase(final String name) { + super(name); + } + + int min(final int a, final int b) { + return a < b ? a : b; + } + + @Override + public void setUp() { + + } + + /** + * Pattern: +++++..-----.. + */ + public void test1() { + for (int size = 1; size <= 128; size *= 2) { + final BoundedFIFO bf = new BoundedFIFO(size); + + assertEquals(bf.getMaxSize(), size); + assertNull(bf.get()); + + int i; + int j; + int k; + + for (i = 1; i < 2 * size; i++) { + for (j = 0; j < i; j++) { + // System.out.println("Putting "+e[j]); + bf.put(e[j]); + assertEquals(bf.length(), j < size ? j + 1 : size); + } + final int max = size < j ? size : j; + j--; + for (k = 0; k <= j; k++) { + // System.out.println("max="+max+", j="+j+", k="+k); + assertEquals(bf.length(), max - k > 0 ? max - k : 0); + final Object r = bf.get(); + // System.out.println("Got "+r); + if (k >= size) { + assertNull(r); + } else { + assertEquals(r, e[k]); + } + } + } + // System.out.println("Passed size="+size); + } + } + + /** + * Pattern: ++++--++--++ + */ + public void test2() { + final int size = 3; + final BoundedFIFO bf = new BoundedFIFO(size); + + bf.put(e[0]); + assertEquals(bf.get(), e[0]); + assertNull(bf.get()); + + bf.put(e[1]); + assertEquals(bf.length(), 1); + bf.put(e[2]); + assertEquals(bf.length(), 2); + bf.put(e[3]); + assertEquals(bf.length(), 3); + assertEquals(bf.get(), e[1]); + assertEquals(bf.length(), 2); + assertEquals(bf.get(), e[2]); + assertEquals(bf.length(), 1); + assertEquals(bf.get(), e[3]); + assertEquals(bf.length(), 0); + assertNull(bf.get()); + assertEquals(bf.length(), 0); + } + + /** + * Pattern ++++++++++++++++++++ (insert only); + */ + public void testResize1() { + final int size = 10; + + for (int n = 1; n < size * 2; n++) { + for (int i = 0; i < size * 2; i++) { + + final BoundedFIFO bf = new BoundedFIFO(size); + for (int f = 0; f < i; f++) { + bf.put(e[f]); + } + + bf.resize(n); + final int expectedSize = min(n, min(i, size)); + assertEquals(bf.length(), expectedSize); + for (int c = 0; c < expectedSize; c++) { + assertEquals(bf.get(), e[c]); + } + } + } + } + + /** + * Pattern ++...+ --...- + */ + public void testResize2() { + final int size = 10; + + for (int n = 1; n < size * 2; n++) { + for (int i = 0; i < size * 2; i++) { + for (int d = 0; d < min(i, size); d++) { + + final BoundedFIFO bf = new BoundedFIFO(size); + for (int p = 0; p < i; p++) { + bf.put(e[p]); + } + + for (int g = 0; g < d; g++) { + bf.get(); + } + + // x = the number of elems in + final int x = bf.length(); + + bf.resize(n); + + final int expectedSize = min(n, x); + assertEquals(bf.length(), expectedSize); + + for (int c = 0; c < expectedSize; c++) { + assertEquals(bf.get(), e[c + d]); + } + assertNull(bf.get()); + } + } + } + } + + /** + * Pattern: i inserts, d deletes, r inserts + */ + public void testResize3() { + final int size = 10; + + for (int n = 1; n < size * 2; n++) { + for (int i = 0; i < size; i++) { + for (int d = 0; d < i; d++) { + for (int r = 0; r < d; r++) { + + final BoundedFIFO bf = new BoundedFIFO(size); + for (int p0 = 0; p0 < i; p0++) { + bf.put(e[p0]); + } + + for (int g = 0; g < d; g++) { + bf.get(); + } + for (int p1 = 0; p1 < r; p1++) { + bf.put(e[i + p1]); + } + + final int x = bf.length(); + + bf.resize(n); + + final int expectedSize = min(n, x); + assertEquals(bf.length(), expectedSize); + + for (int c = 0; c < expectedSize; c++) { + assertEquals(bf.get(), e[c + d]); + } + // assertNull(bf.get()); + } + } + } + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/CyclicBufferTestCase.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/CyclicBufferTestCase.java new file mode 100644 index 00000000000..bb294d10640 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/CyclicBufferTestCase.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Log4j uses the JUnit framework for internal unit testing. JUnit +// available from +// +// http://www.junit.org + +package org.apache.log4j.helpers; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Tests {@link CyclicBuffer}. + */ +public class CyclicBufferTestCase extends TestCase { + + static Logger cat = Logger.getLogger("x"); + + static int MAX = 1000; + + static LoggingEvent[] e = new LoggingEvent[MAX]; + + public static Test suite() { + final TestSuite suite = new TestSuite(); + suite.addTest(new CyclicBufferTestCase("test0")); + suite.addTest(new CyclicBufferTestCase("test1")); + suite.addTest(new CyclicBufferTestCase("testResize")); + return suite; + } + + { + for (int i = 0; i < MAX; i++) { + e[i] = new LoggingEvent("", cat, Level.DEBUG, "e" + i, null); + } + } + + public CyclicBufferTestCase(final String name) { + super(name); + } + + void doTest1(final int size) { + // System.out.println("Doing test with size = "+size); + final CyclicBuffer cb = new CyclicBuffer(size); + + assertEquals(cb.getMaxSize(), size); + + for (int i = -(size + 10); i < (size + 10); i++) { + assertNull(cb.get(i)); + } + + for (int i = 0; i < MAX; i++) { + cb.add(e[i]); + final int limit = i < size - 1 ? i : size - 1; + + // System.out.println("\nLimit is " + limit + ", i="+i); + + for (int j = limit; j >= 0; j--) { + // System.out.println("i= "+i+", j="+j); + assertEquals(cb.get(j), e[i - (limit - j)]); + } + assertNull(cb.get(-1)); + assertNull(cb.get(limit + 1)); + } + } + + void doTestResize(final int initialSize, final int numberOfAdds, final int newSize) { + // System.out.println("initialSize = "+initialSize+", numberOfAdds=" + // +numberOfAdds+", newSize="+newSize); + final CyclicBuffer cb = new CyclicBuffer(initialSize); + for (int i = 0; i < numberOfAdds; i++) { + cb.add(e[i]); + } + cb.resize(newSize); + + int offset = numberOfAdds - initialSize; + if (offset < 0) { + offset = 0; + } + + int len = newSize < numberOfAdds ? newSize : numberOfAdds; + len = len < initialSize ? len : initialSize; + // System.out.println("Len = "+len+", offset="+offset); + for (int j = 0; j < len; j++) { + assertEquals(cb.get(j), e[offset + j]); + } + + } + + @Override + public void setUp() { + + } + + public void test0() { + final int size = 2; + + CyclicBuffer cb = new CyclicBuffer(size); + assertEquals(cb.getMaxSize(), size); + + cb.add(e[0]); + assertEquals(cb.length(), 1); + assertEquals(cb.get(), e[0]); + assertEquals(cb.length(), 0); + assertNull(cb.get()); + assertEquals(cb.length(), 0); + + cb = new CyclicBuffer(size); + cb.add(e[0]); + cb.add(e[1]); + assertEquals(cb.length(), 2); + assertEquals(cb.get(), e[0]); + assertEquals(cb.length(), 1); + assertEquals(cb.get(), e[1]); + assertEquals(cb.length(), 0); + assertNull(cb.get()); + assertEquals(cb.length(), 0); + + } + + /** + * Test a buffer of size 1,2,4,8,..,128 + */ + public void test1() { + for (int bufSize = 1; bufSize <= 128; bufSize *= 2) { + doTest1(bufSize); + } + } + + public void testResize() { + for (int isize = 1; isize <= 128; isize *= 2) { + doTestResize(isize, isize / 2 + 1, isize / 2 + 1); + doTestResize(isize, isize / 2 + 1, isize + 10); + doTestResize(isize, isize + 10, isize / 2 + 1); + doTestResize(isize, isize + 10, isize + 10); + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/DateLayoutTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/DateLayoutTest.java new file mode 100644 index 00000000000..e650f3df6da --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/DateLayoutTest.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.TimeZone; + +import org.apache.log4j.Layout; +import org.apache.log4j.LayoutTest; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Tests {@link DateLayout}. + */ +public class DateLayoutTest extends LayoutTest { + + /** + * Construct a new instance of LayoutTest. + * + * @param testName test name. + */ + public DateLayoutTest(final String testName) { + super(testName); + } + + /** + * Constructor for use by derived tests. + * + * @param testName name of test. + * @param expectedContentType expected value for getContentType(). + * @param expectedIgnoresThrowable expected value for ignoresThrowable(). + * @param expectedHeader expected value for getHeader(). + * @param expectedFooter expected value for getFooter(). + */ + protected DateLayoutTest(final String testName, final String expectedContentType, final boolean expectedIgnoresThrowable, final String expectedHeader, + final String expectedFooter) { + super(testName, expectedContentType, expectedIgnoresThrowable, expectedHeader, expectedFooter); + } + + /** + * @{inheritDoc} + */ + protected Layout createLayout() { + return new MockLayout(); + } + + /** + * Tests DateLayout.NULL_DATE_FORMAT constant. + */ + public void testNullDateFormat() { + assertEquals("NULL", DateLayout.NULL_DATE_FORMAT); + } + + /** + * Tests DateLayout.RELATIVE constant. + */ + public void testRelativeTimeDateFormat() { + assertEquals("RELATIVE", DateLayout.RELATIVE_TIME_DATE_FORMAT); + } + + /** + * Tests DateLayout.DATE_FORMAT_OPTION constant. + * + * @deprecated since constant is deprecated + */ + public void testDateFormatOption() { + assertEquals("DateFormat", DateLayout.DATE_FORMAT_OPTION); + } + + /** + * Tests DateLayout.TIMEZONE_OPTION constant. + * + * @deprecated since constant is deprecated + */ + public void testTimeZoneOption() { + assertEquals("TimeZone", DateLayout.TIMEZONE_OPTION); + } + + /** + * Tests getOptionStrings(). + * + * @deprecated since getOptionStrings is deprecated. + * + */ + public void testGetOptionStrings() { + String[] options = ((DateLayout) createLayout()).getOptionStrings(); + assertEquals(2, options.length); + } + + /** + * Tests setting DateFormat through setOption method. + * + * @deprecated since setOption is deprecated. + */ + public void testSetOptionDateFormat() { + DateLayout layout = (DateLayout) createLayout(); + layout.setOption("dAtefOrmat", "foobar"); + assertEquals("FOOBAR", layout.getDateFormat()); + } + + /** + * Tests setting TimeZone through setOption method. + * + * @deprecated since setOption is deprecated. + */ + public void testSetOptionTimeZone() { + DateLayout layout = (DateLayout) createLayout(); + layout.setOption("tImezOne", "+05:00"); + assertEquals("+05:00", layout.getTimeZone()); + } + + /** + * Tests setDateFormat. + */ + public void testSetDateFormat() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("ABSOLUTE"); + assertEquals("ABSOLUTE", layout.getDateFormat()); + } + + /** + * Tests setTimeZone. + */ + public void testSetTimeZone() { + DateLayout layout = (DateLayout) createLayout(); + layout.setTimeZone("+05:00"); + assertEquals("+05:00", layout.getTimeZone()); + } + + /** + * Tests 2 parameter setDateFormat with null. + */ + public void testSetDateFormatNull() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat((String) null, null); + } + + /** + * Tests 2 parameter setDateFormat with "NULL". + */ + public void testSetDateFormatNullString() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("NuLL", null); + } + + /** + * Tests 2 parameter setDateFormat with "RELATIVE". + */ + public void testSetDateFormatRelative() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("rElatIve", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "ABSOLUTE". + */ + public void testSetDateFormatAbsolute() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("aBsolUte", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "DATETIME". + */ + public void testSetDateFormatDateTime() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("dAte", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "ISO8601". + */ + public void testSetDateFormatISO8601() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("iSo8601", TimeZone.getDefault()); + } + + /** + * Tests 2 parameter setDateFormat with "HH:mm:ss". + */ + public void testSetDateFormatSimple() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("HH:mm:ss", TimeZone.getDefault()); + } + + /** + * Tests activateOptions. + */ + public void testActivateOptions() { + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat("HH:mm:ss"); + layout.setTimeZone("+05:00"); + layout.activateOptions(); + } + + /** + * Tests setDateFormat(DateFormat, TimeZone). + */ + public void testSetDateFormatWithFormat() { + DateFormat format = new SimpleDateFormat("HH:mm"); + DateLayout layout = (DateLayout) createLayout(); + layout.setDateFormat(format, TimeZone.getDefault()); + } + + /** + * Tests IS08601DateFormat class. + * + * @deprecated since ISO8601DateFormat is deprecated + */ + public void testISO8601Format() { + DateFormat format = new ISO8601DateFormat(); + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(1970, 0, 1, 0, 0, 0); + String actual = format.format(calendar.getTime()); + assertEquals("1970-01-01 00:00:00,000", actual); + } + + /** + * Tests DateTimeDateFormat class. + * + * @deprecated since DateTimeDateFormat is deprecated + */ + public void testDateTimeFormat() { + DateFormat format = new DateTimeDateFormat(); + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(1970, 0, 1, 0, 0, 0); + String actual = format.format(calendar.getTime()); + SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy HH:mm:ss,SSS"); + String expected = df.format(calendar.getTime()); + assertEquals(expected, actual); + } + + /** + * Concrete Layout class for tests. + */ + private static final class MockLayout extends DateLayout { + /** + * Create new instance of MockLayout. + */ + public MockLayout() { + // + // checks that protected fields are properly initialized + assertNotNull(pos); + assertNotNull(date); + assertNull(dateFormat); + } + + /** + * @{inheritDoc} + */ + public String format(final LoggingEvent event) { + return "Mock"; + } + + /** + * @{inheritDoc} + */ + public void activateOptions() { + } + + /** + * @{inheritDoc} + */ + public boolean ignoresThrowable() { + return true; + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/LogLogTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/LogLogTest.java new file mode 100644 index 00000000000..5341e1d3dfb --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/LogLogTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import junit.framework.TestCase; + +/** + * Tests {@link LogLog}. + */ +public class LogLogTest extends TestCase { + + /** + * Create new instance of LogLogTest. + * + * @param testName test name + */ + public LogLogTest(final String testName) { + super(testName); + } + + /** + * Check value of CONFIG_DEBUG_KEY. + * + * @deprecated since constant is deprecated + */ + @Deprecated + public void testConfigDebugKey() { + assertEquals("log4j.configDebug", LogLog.CONFIG_DEBUG_KEY); + } + + /** + * Check value of DEBUG_KEY. + */ + public void testDebugKey() { + assertEquals("log4j.debug", LogLog.DEBUG_KEY); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterLevelTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterLevelTest.java new file mode 100644 index 00000000000..da7852acf35 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterLevelTest.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.log4j.helpers; + +import static org.apache.log4j.helpers.OptionConverter.toLog4j1Level; +import static org.apache.log4j.helpers.OptionConverter.toLog4j2Level; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.apache.log4j.Level; +import org.apache.log4j.Priority; +import org.apache.log4j.bridge.LogEventAdapter; +import org.apache.logging.log4j.spi.StandardLevel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class OptionConverterLevelTest { + + static Stream standardLevels() { + return Arrays.stream(StandardLevel.values()) + .map(Enum::name) + .map(name -> Arguments.of(Level.toLevel(name), org.apache.logging.log4j.Level.toLevel(name))); + } + + /** + * Test if the standard levels are transformed correctly. + * + * @param log4j1Level + * @param log4j2Level + */ + @ParameterizedTest + @MethodSource("standardLevels") + public void testStandardLevelConversion(final Level log4j1Level, final org.apache.logging.log4j.Level log4j2Level) { + assertEquals(log4j2Level, OptionConverter.convertLevel(log4j1Level)); + assertEquals(log4j1Level, OptionConverter.convertLevel(log4j2Level)); + } + + /** + * Test if the conversion works at an integer level. + * + * @param log4j1Level + * @param log4j2Level + */ + @ParameterizedTest + @MethodSource("standardLevels") + public void testStandardIntLevelConversion(final Level log4j1Level, + final org.apache.logging.log4j.Level log4j2Level) { + assertEquals(log4j2Level.intLevel(), toLog4j2Level(log4j1Level.toInt())); + assertEquals(log4j1Level.toInt(), toLog4j1Level(log4j2Level.intLevel())); + } + + @Test + public void testMaxMinCutoff() { + // The cutoff values are transformed into ALL and OFF + assertEquals(StandardLevel.ALL.intLevel(), toLog4j2Level(OptionConverter.MIN_CUTOFF_LEVEL)); + assertEquals(StandardLevel.OFF.intLevel(), toLog4j2Level(OptionConverter.MAX_CUTOFF_LEVEL)); + // Maximal and minimal Log4j 1.x values different from ALL or OFF + int minTransformed = toLog4j1Level(toLog4j2Level(OptionConverter.MIN_CUTOFF_LEVEL + 1)); + assertEquals(OptionConverter.MIN_CUTOFF_LEVEL + 1, minTransformed); + int maxTransformed = toLog4j1Level(toLog4j2Level(OptionConverter.MAX_CUTOFF_LEVEL - 1)); + assertEquals(OptionConverter.MAX_CUTOFF_LEVEL - 1, maxTransformed); + // Maximal and minimal Log4j 2.x value different from ALL or OFF + minTransformed = toLog4j2Level(toLog4j1Level(StandardLevel.OFF.intLevel() + 1)); + assertEquals(StandardLevel.OFF.intLevel() + 1, minTransformed); + maxTransformed = toLog4j2Level(toLog4j1Level(StandardLevel.ALL.intLevel() - 1)); + assertEquals(StandardLevel.ALL.intLevel() - 1, maxTransformed); + } + + /** + * Test if the values in at least the TRACE to FATAL range are transformed + * correctly. + */ + @Test + public void testUsefulRange() { + for (int intLevel = StandardLevel.OFF.intLevel(); intLevel <= StandardLevel.TRACE.intLevel(); intLevel++) { + assertEquals(intLevel, toLog4j2Level(toLog4j1Level(intLevel))); + } + for (int intLevel = Level.TRACE_INT; intLevel < OptionConverter.MAX_CUTOFF_LEVEL; intLevel = intLevel + 100) { + assertEquals(intLevel, toLog4j1Level(toLog4j2Level(intLevel))); + } + } + + /** + * Levels defined in Log4j 2.x should have an equivalent in Log4j 1.x. Those are + * used in {@link LogEventAdapter}. + */ + @Test + public void testCustomLog4j2Levels() { + final int infoDebug = (StandardLevel.INFO.intLevel() + StandardLevel.DEBUG.intLevel()) / 2; + final org.apache.logging.log4j.Level v2Level = org.apache.logging.log4j.Level.forName("INFO_DEBUG", infoDebug); + final Level v1Level = OptionConverter.toLevel("INFO_DEBUG#" + org.apache.logging.log4j.Level.class.getName(), null); + assertNotNull(v1Level); + assertEquals(v2Level, v1Level.getVersion2Level()); + final int expectedLevel = (Priority.INFO_INT + Priority.DEBUG_INT) / 2; + assertEquals(expectedLevel, v1Level.toInt()); + // convertLevel + assertEquals(v1Level, OptionConverter.convertLevel(v2Level)); + // Non-existent level + assertNull(OptionConverter.toLevel("WARN_INFO#" + org.apache.logging.log4j.Level.class.getName(), null)); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterTestCase.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterTestCase.java new file mode 100644 index 00000000000..83ded203055 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/OptionConverterTestCase.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Log4j uses the JUnit framework for internal unit testing. JUnit +// is available from "http://www.junit.org". + +package org.apache.log4j.helpers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.PropertyConfiguratorTest; +import org.apache.log4j.xml.XLevel; +import org.junit.Ignore; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test variable substitution code. + * + * @since 1.0 + */ +@Ignore("WIP") +public class OptionConverterTestCase extends TestCase { + + Properties props; + + public OptionConverterTestCase(String name) { + super(name); + } + + public void setUp() { + props = new Properties(); + props.put("TOTO", "wonderful"); + props.put("key1", "value1"); + props.put("key2", "value2"); + // Log4J will NPE without this: + props.put("line.separator", System.getProperty("line.separator")); + // Log4J will throw an Error without this: + props.put("java.home", System.getProperty("java.home")); + System.setProperties(props); + + } + + public void tearDown() { + props = null; + LogManager.resetConfiguration(); + } + + public void varSubstTest1() { + String r; + + r = OptionConverter.substVars("hello world.", null); + assertEquals("hello world.", r); + + r = OptionConverter.substVars("hello ${TOTO} world.", null); + + assertEquals("hello wonderful world.", r); + } + + public void varSubstTest2() { + String r; + + r = OptionConverter.substVars("Test2 ${key1} mid ${key2} end.", null); + assertEquals("Test2 value1 mid value2 end.", r); + } + + public void varSubstTest3() { + String r; + + r = OptionConverter.substVars("Test3 ${unset} mid ${key1} end.", null); + assertEquals("Test3 mid value1 end.", r); + } + + public void varSubstTest4() { + String val = "Test4 ${incomplete "; + try { + OptionConverter.substVars(val, null); + } catch (IllegalArgumentException e) { + String errorMsg = e.getMessage(); + // System.out.println('['+errorMsg+']'); + assertEquals('"' + val + "\" has no closing brace. Opening brace at position 6.", errorMsg); + } + } + + public void varSubstTest5() { + Properties props = new Properties(); + props.put("p1", "x1"); + props.put("p2", "${p1}"); + String res = OptionConverter.substVars("${p2}", props); + System.out.println("Result is [" + res + "]."); + assertEquals(res, "x1"); + } + + /** + * Tests configuring Log4J from an InputStream. + * + * @since 1.2.17 + */ + public void testInputStream() throws IOException { + File file = new File("src/test/resources/log4j1-1.2.17/input/filter1.properties"); + assertTrue(file.exists()); + try (FileInputStream inputStream = new FileInputStream(file)) { + OptionConverter.selectAndConfigure(inputStream, null, LogManager.getLoggerRepository()); + } + new PropertyConfiguratorTest().validateNested(); + } + + public void toLevelTest1() { + String val = "INFO"; + Level p = OptionConverter.toLevel(val, null); + assertEquals(p, Level.INFO); + } + + public void toLevelTest2() { + String val = "INFO#org.apache.log4j.xml.XLevel"; + Level p = OptionConverter.toLevel(val, null); + assertEquals(p, Level.INFO); + } + + public void toLevelTest3() { + String val = "TRACE#org.apache.log4j.xml.XLevel"; + Level p = OptionConverter.toLevel(val, null); + assertEquals(p, XLevel.TRACE); + } + + public void toLevelTest4() { + String val = "TR#org.apache.log4j.xml.XLevel"; + Level p = OptionConverter.toLevel(val, null); + assertEquals(p, null); + } + + public void toLevelTest5() { + String val = "INFO#org.apache.log4j.xml.TOTO"; + Level p = OptionConverter.toLevel(val, null); + assertEquals(p, null); + } + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new OptionConverterTestCase("varSubstTest5")); + suite.addTest(new OptionConverterTestCase("varSubstTest1")); + suite.addTest(new OptionConverterTestCase("varSubstTest2")); + suite.addTest(new OptionConverterTestCase("varSubstTest3")); + suite.addTest(new OptionConverterTestCase("varSubstTest4")); + + suite.addTest(new OptionConverterTestCase("testInputStream")); + + suite.addTest(new OptionConverterTestCase("toLevelTest1")); + suite.addTest(new OptionConverterTestCase("toLevelTest2")); + suite.addTest(new OptionConverterTestCase("toLevelTest3")); + suite.addTest(new OptionConverterTestCase("toLevelTest4")); + suite.addTest(new OptionConverterTestCase("toLevelTest5")); + return suite; + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/PatternParserTestCase.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/PatternParserTestCase.java new file mode 100644 index 00000000000..e91cc0f3588 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/PatternParserTestCase.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import org.apache.log4j.Appender; +import org.apache.log4j.FileAppender; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.MDC; +import org.apache.log4j.PatternLayout; +import org.apache.log4j.util.Compare; +import org.junit.Ignore; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test case for helpers/PatternParser.java. Tests the various conversion patterns supported by PatternParser. This test + * class tests PatternParser via the PatternLayout class which uses it. + */ +@Ignore("WIP") +public class PatternParserTestCase extends TestCase { + + static String OUTPUT_FILE = "target/PatternParser"; + static String WITNESS_FILE = "target/witness/PatternParser"; + + static String msgPattern = "%m%n"; + + public static Test suite() { + TestSuite suite = new TestSuite(); + suite.addTest(new PatternParserTestCase("mdcPattern")); + return suite; + } + Logger root; + + Logger logger; + + public PatternParserTestCase(String name) { + super(name); + } + + /** + * Test case for MDC conversion pattern. + */ + public void mdcPattern() throws Exception { + + String mdcMsgPattern1 = "%m : %X%n"; + String mdcMsgPattern2 = "%m : %X{key1}%n"; + String mdcMsgPattern3 = "%m : %X{key2}%n"; + String mdcMsgPattern4 = "%m : %X{key3}%n"; + String mdcMsgPattern5 = "%m : %X{key1},%X{key2},%X{key3}%n"; + + // set up appender + PatternLayout layout = new PatternLayout(msgPattern); + Appender appender = new FileAppender(layout, OUTPUT_FILE + "_mdc", false); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + // output starting message + root.debug("starting mdc pattern test"); + + layout.setConversionPattern(mdcMsgPattern1); + root.debug("empty mdc, no key specified in pattern"); + + layout.setConversionPattern(mdcMsgPattern2); + root.debug("empty mdc, key1 in pattern"); + + layout.setConversionPattern(mdcMsgPattern3); + root.debug("empty mdc, key2 in pattern"); + + layout.setConversionPattern(mdcMsgPattern4); + root.debug("empty mdc, key3 in pattern"); + + layout.setConversionPattern(mdcMsgPattern5); + root.debug("empty mdc, key1, key2, and key3 in pattern"); + + MDC.put("key1", "value1"); + MDC.put("key2", "value2"); + + layout.setConversionPattern(mdcMsgPattern1); + root.debug("filled mdc, no key specified in pattern"); + + layout.setConversionPattern(mdcMsgPattern2); + root.debug("filled mdc, key1 in pattern"); + + layout.setConversionPattern(mdcMsgPattern3); + root.debug("filled mdc, key2 in pattern"); + + layout.setConversionPattern(mdcMsgPattern4); + root.debug("filled mdc, key3 in pattern"); + + layout.setConversionPattern(mdcMsgPattern5); + root.debug("filled mdc, key1, key2, and key3 in pattern"); + + MDC.remove("key1"); + MDC.remove("key2"); + + layout.setConversionPattern(msgPattern); + root.debug("finished mdc pattern test"); + + assertTrue(Compare.compare(OUTPUT_FILE + "_mdc", WITNESS_FILE + "_mdc")); + } + + public void setUp() { + root = Logger.getRootLogger(); + root.removeAllAppenders(); + } + + public void tearDown() { + root.getLoggerRepository().resetConfiguration(); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java new file mode 100644 index 00000000000..3b53aa668d6 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/helpers/UtilLoggingLevelTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.helpers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Stream; + +import org.apache.log4j.Level; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + + +/** + * Unit tests for UtilLoggingLevel. + */ +public class UtilLoggingLevelTest { + + /** + * Test toLevel("fiNeSt"). + */ + public void testToLevelFINEST() { + assertEquals(UtilLoggingLevel.FINEST, UtilLoggingLevel.toLevel("fiNeSt")); + } + + static Stream namesAndLevels() { + return UtilLoggingLevel.getAllPossibleLevels() + .stream() + .map(level -> Arguments.of(level.toString() + "#" + UtilLoggingLevel.class.getName(), level)); + } + + @ParameterizedTest + @MethodSource("namesAndLevels") + public void testOptionConverterToLevel(final String name, final UtilLoggingLevel level) { + assertTrue(level == OptionConverter.toLevel(name, Level.ALL), "get v1 level by name"); + // Comparison of Log4j 2.x levels + assertTrue(level.getVersion2Level() == org.apache.logging.log4j.Level.getLevel(name), "get v2 level by name"); + // Test convertLevel + assertTrue(level == OptionConverter.convertLevel(level.getVersion2Level()), "convert level v2 -> v1"); + } +} + diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java new file mode 100644 index 00000000000..2a1e0b997a6 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1SyslogLayoutTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.layout; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.stream.Stream; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.StringLayout; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class Log4j1SyslogLayoutTest { + + private static final SimpleMessage MESSAGE = new SimpleMessage("Hello world!"); + private static final long TIMESTAMP = LocalDateTime.of(2022, 4, 5, 12, 34, 56).atZone(ZoneId.systemDefault()) + .toEpochSecond(); + private static final String localhostName = NetUtils.getLocalHostname(); + + private static LogEvent createLogEvent() { + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(TIMESTAMP, 0); + final LogEvent event = mock(LogEvent.class); + when(event.getInstant()).thenReturn(instant); + when(event.getMessage()).thenReturn(MESSAGE); + when(event.getLevel()).thenReturn(Level.INFO); + return event; + } + + static Stream configurations() { + return Stream + .of(Arguments.of("<30>Hello world!", Facility.DAEMON, false, false), + Arguments.of("<30>Apr 5 12:34:56 %s Hello world!", Facility.DAEMON, true, false), + Arguments.of("<30>daemon:Hello world!", Facility.DAEMON, false, true), + Arguments.of("<30>Apr 5 12:34:56 %s daemon:Hello world!", Facility.DAEMON, true, true)) + .map(args -> { + final Object[] objs = args.get(); + objs[0] = String.format((String) objs[0], localhostName); + return Arguments.of(objs); + }); + } + + @ParameterizedTest + @MethodSource("configurations") + public void testSimpleLayout(String expected, Facility facility, boolean header, boolean facilityPrinting) { + final LogEvent logEvent = createLogEvent(); + StringLayout appenderLayout = Log4j1SyslogLayout.newBuilder() + .setFacility(facility) + .setHeader(header) + .setFacilityPrinting(facilityPrinting) + .build(); + assertEquals(expected, appenderLayout.toSerializable(logEvent)); + final StringLayout messageLayout = PatternLayout.newBuilder() + .setPattern("%m") + .build(); + appenderLayout = Log4j1SyslogLayout.newBuilder() + .setFacility(facility) + .setHeader(header) + .setFacilityPrinting(facilityPrinting) + .setMessageLayout(messageLayout) + .build(); + assertEquals(expected, appenderLayout.toSerializable(logEvent)); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java index 395cf3b3e98..1c2cf7130b6 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java @@ -21,12 +21,17 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.test.junit.ThreadContextRule; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.StringMap; +import org.junit.Rule; import org.junit.Test; public class Log4j1XmlLayoutTest { + @Rule + public ThreadContextRule threadContextRule = new ThreadContextRule(); + @Test public void testWithoutThrown() { final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(false, true); diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/FormattingInfoTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/FormattingInfoTest.java new file mode 100644 index 00000000000..d4a396dc924 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/FormattingInfoTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.pattern; + +import junit.framework.TestCase; + +/** + * Tests for FormattingInfo. + * + * @author Curt Arnold + * + */ +public class FormattingInfoTest extends TestCase { + /** + * Create a new instance. + * + * @param name test name + */ + public FormattingInfoTest(final String name) { + super(name); + } + + /** + * Check constructor + * + */ + public void testConstructor() { + FormattingInfo field = new FormattingInfo(true, 3, 6); + assertNotNull(field); + assertEquals(3, field.getMinLength()); + assertEquals(6, field.getMaxLength()); + assertEquals(true, field.isLeftAligned()); + } + + /** + * Check that getDefault does not return null. + * + */ + public void testGetDefault() { + FormattingInfo field = FormattingInfo.getDefault(); + assertNotNull(field); + assertEquals(0, field.getMinLength()); + assertEquals(Integer.MAX_VALUE, field.getMaxLength()); + assertEquals(false, field.isLeftAligned()); + } + + /** + * Add padding to left since field is not minimum width. + */ + public void testPadLeft() { + StringBuffer buf = new StringBuffer("foobar"); + FormattingInfo field = new FormattingInfo(false, 5, 10); + field.format(2, buf); + assertEquals("fo obar", buf.toString()); + } + + /** + * Add padding to right since field is not minimum width. + */ + public void testPadRight() { + StringBuffer buf = new StringBuffer("foobar"); + FormattingInfo field = new FormattingInfo(true, 5, 10); + field.format(2, buf); + assertEquals("foobar ", buf.toString()); + } + + /** + * Field exceeds maximum width + */ + public void testTruncate() { + StringBuffer buf = new StringBuffer("foobar"); + FormattingInfo field = new FormattingInfo(true, 0, 3); + field.format(2, buf); + assertEquals("fobar", buf.toString()); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1LevelPatternConverterTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1LevelPatternConverterTest.java new file mode 100644 index 00000000000..11150b0113b --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1LevelPatternConverterTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.log4j.pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class Log4j1LevelPatternConverterTest { + + /** + * Tests if the converter returns the Log4j 1.x {@code toString()} value of + * custom Log4j 1.x levels. + * + * @param level a Log4j 1.x level + */ + @ParameterizedTest + @MethodSource("org.apache.log4j.helpers.UtilLoggingLevel#getAllPossibleLevels") + public void testUtilLoggingLevels(final Level level) { + final Log4j1LevelPatternConverter converter = Log4j1LevelPatternConverter.newInstance(null); + final LogEvent logEvent = mock(LogEvent.class); + when(logEvent.getLevel()).thenReturn(level.getVersion2Level()); + final StringBuilder result = new StringBuilder(); + converter.format(logEvent, result); + assertEquals(level.toString(), result.toString()); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1NdcPatternConverterTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1NdcPatternConverterTest.java index 2f0b80f78ab..170e7bf68f3 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1NdcPatternConverterTest.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/Log4j1NdcPatternConverterTest.java @@ -16,17 +16,17 @@ */ package org.apache.log4j.pattern; +import static org.junit.Assert.assertEquals; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.junit.ThreadContextStackRule; +import org.apache.logging.log4j.test.junit.ThreadContextStackRule; import org.apache.logging.log4j.message.SimpleMessage; import org.junit.Rule; import org.junit.Test; -import static org.junit.Assert.assertEquals; - public class Log4j1NdcPatternConverterTest { @Rule diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/NameAbbreviatorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/NameAbbreviatorTest.java new file mode 100644 index 00000000000..5494a5974bc --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/pattern/NameAbbreviatorTest.java @@ -0,0 +1,329 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.pattern; + +import junit.framework.TestCase; + +/** + * Tests for NameAbbrevator. + * + */ +public class NameAbbreviatorTest extends TestCase { + /** + * Create a new instance. + * + * @param name test name + */ + public NameAbbreviatorTest(final String name) { + super(name); + } + + /** + * Check that getAbbreviator(" ") returns default abbreviator. + * + */ + public void testBlank() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator(" "); + NameAbbreviator defaultAbbrev = NameAbbreviator.getDefaultAbbreviator(); + assertTrue(abbrev == defaultAbbrev); + } + + /** + * Check that blanks are trimmed in evaluating abbreviation pattern. + */ + public void testBlankOne() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator(" 1 "); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + } + + /** + * Check that getDefaultAbbreviator does not return null. + * + */ + public void testGetDefault() { + NameAbbreviator abbrev = NameAbbreviator.getDefaultAbbreviator(); + assertNotNull(abbrev); + } + + /** + * Check that getAbbreviator("-1").abbreviate() drops first name element. + * + */ + public void testMinusOne() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("-1"); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - example.foo.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("."); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + + } + + /** + * Check that getAbbreviator("1.*.2").abbreviate drops all but the first character from the first element, uses all of + * the second element and drops all but the first two characters of the rest of the non-final elements. + * + */ + public void testMulti() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1.*.2"); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - o.example.fo.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("org.example.foo."); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - o.example.fo.", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - f.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("."); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - .", buf.toString()); + } + + /** + * Check that getAbbreviator("1").abbreviate() drops all but the final name element. + * + */ + public void testOne() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1"); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + } + + /** + * Check that getAbbreviator("1.").abbreviate abbreviates non-final elements to one character. + * + */ + public void testOneDot() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1."); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - o.e.f.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("org.example.foo."); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - o.e.f.", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - f.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("."); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - .", buf.toString()); + } + + /** + * Check that getAbbreviator("1~.").abbreviate abbreviates non-final elements to one character and a tilde. + * + */ + public void testOneTildeDot() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("1~."); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - o~.e~.f~.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("org.example.foo."); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - o~.e~.f~.", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - f~.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("."); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - .", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("o.e.f.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - o.e.f.bar", buf.toString()); + } + + /** + * Check that getAbbreviator("2").abbreviate drops all but the last two elements. + * + */ + public void testTwo() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("2"); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - foo.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - foo.bar", buf.toString()); + + buf.setLength(0); + buf.append("DEBUG - "); + fieldStart = buf.length(); + buf.append("bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - bar", buf.toString()); + } + + /** + * Check that "0" drops all name content. + * + */ + public void testZero() { + NameAbbreviator abbrev = NameAbbreviator.getAbbreviator("0"); + StringBuffer buf = new StringBuffer("DEBUG - "); + int fieldStart = buf.length(); + buf.append("org.example.foo.bar"); + abbrev.abbreviate(fieldStart, buf); + assertEquals("DEBUG - ", buf.toString()); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/spi/LocationInfoTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/spi/LocationInfoTest.java new file mode 100644 index 00000000000..a6daefcaa79 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/spi/LocationInfoTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.spi; + +import junit.framework.TestCase; + +/** + * Tests for LocationInfo. + */ +public class LocationInfoTest extends TestCase { + + /** + * Tests four parameter constructor. + */ + public void testFourParamConstructor() { + final String className = LocationInfoTest.class.getName(); + final String methodName = "testFourParamConstructor"; + final String fileName = "LocationInfoTest.java"; + final String lineNumber = "41"; + LocationInfo li = new LocationInfo(fileName, className, methodName, lineNumber); + assertEquals(className, li.getClassName()); + assertEquals(methodName, li.getMethodName()); + assertEquals(fileName, li.getFileName()); + assertEquals(lineNumber, li.getLineNumber()); + assertEquals(className + "." + methodName + "(" + fileName + ":" + lineNumber + ")", li.fullInfo); + } + + /** + * Class with name that is a substring of its caller. + */ + private static class NameSubstring { + /** + * Construct a LocationInfo. Location should be immediate caller of this method. + * + * @return location info. + */ + public static LocationInfo getInfo() { + return new LocationInfo(new Throwable(), NameSubstring.class.getName()); + + } + } + + /** + * Class whose name is contains the name of the class that obtains the LocationInfo. + */ + private static class NameSubstringCaller { + /** + * Construct a locationInfo. Location should be this location. + * + * @return location info. + */ + public static LocationInfo getInfo() { + return NameSubstring.getInfo(); + } + + } + + /** + * Tests creation of location info when the logger class name is a substring of one of the other classes in the stack + * trace. See bug 44888. + */ + public void testLocationInfo() { + LocationInfo li = NameSubstringCaller.getInfo(); + assertEquals(NameSubstringCaller.class.getName(), li.getClassName()); + assertEquals("getInfo", li.getMethodName()); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/spi/ThrowableInformationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/spi/ThrowableInformationTest.java new file mode 100644 index 00000000000..e032bd78e7a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/spi/ThrowableInformationTest.java @@ -0,0 +1,338 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.spi; + +import java.io.PrintWriter; + +import junit.framework.TestCase; + +/** + * Tests {@link ThrowableInformation}. + */ +public class ThrowableInformationTest extends TestCase { + + /** + * Create ThrowableInformationTest. + * + * @param name test name. + */ + public ThrowableInformationTest(final String name) { + super(name); + } + + /** + * Custom throwable that only calls methods overridden by VectorWriter in log4j 1.2.14 and earlier. + */ + private static final class OverriddenThrowable extends Throwable { + private static final long serialVersionUID = 1L; + + /** + * Create new instance. + */ + public OverriddenThrowable() { + } + + /** + * Print stack trace. + * + * @param s print writer. + */ + public void printStackTrace(final PrintWriter s) { + s.print((Object) "print(Object)"); + s.print("print(char[])".toCharArray()); + s.print("print(String)"); + s.println((Object) "println(Object)"); + s.println("println(char[])".toCharArray()); + s.println("println(String)"); + s.write("write(char[])".toCharArray()); + s.write("write(char[], int, int)".toCharArray(), 2, 8); + s.write("write(String, int, int)", 2, 8); + } + } + + /** + * Test capturing stack trace from a throwable that only uses the PrintWriter methods overridden in log4j 1.2.14 and + * earlier. + */ + public void testOverriddenBehavior() { + ThrowableInformation ti = new ThrowableInformation(new OverriddenThrowable()); + String[] rep = ti.getThrowableStrRep(); + assertEquals(4, rep.length); + assertEquals("print(Object)print(char[])print(String)println(Object)", rep[0]); + assertEquals("println(char[])", rep[1]); + assertEquals("println(String)", rep[2]); + assertEquals("write(char[])ite(charite(Stri", rep[3]); + } + + /** + * Custom throwable that calls methods not overridden by VectorWriter in log4j 1.2.14 and earlier. + */ + private static final class NotOverriddenThrowable extends Throwable { + private static final long serialVersionUID = 1L; + + /** + * Create new instance. + */ + public NotOverriddenThrowable() { + } + + /** + * Print stack trace. + * + * @param s print writer. + */ + public void printStackTrace(final PrintWriter s) { + s.print(true); + s.print('a'); + s.print(1); + s.print(2L); + s.print(Float.MAX_VALUE); + s.print(Double.MIN_VALUE); + s.println(true); + s.println('a'); + s.println(1); + s.println(2L); + s.println(Float.MAX_VALUE); + s.println(Double.MIN_VALUE); + s.write('C'); + } + } + + /** + * Test capturing stack trace from a throwable that uses the PrintWriter methods not overridden in log4j 1.2.14 and + * earlier. + */ + public void testNotOverriddenBehavior() { + ThrowableInformation ti = new ThrowableInformation(new NotOverriddenThrowable()); + String[] rep = ti.getThrowableStrRep(); + assertEquals(7, rep.length); + StringBuffer buf = new StringBuffer(String.valueOf(true)); + buf.append('a'); + buf.append(String.valueOf(1)); + buf.append(String.valueOf(2L)); + buf.append(String.valueOf(Float.MAX_VALUE)); + buf.append(String.valueOf(Double.MIN_VALUE)); + buf.append(String.valueOf(true)); + assertEquals(buf.toString(), rep[0]); + assertEquals("a", rep[1]); + assertEquals(String.valueOf(1), rep[2]); + assertEquals(String.valueOf(2L), rep[3]); + assertEquals(String.valueOf(Float.MAX_VALUE), rep[4]); + assertEquals(String.valueOf(Double.MIN_VALUE), rep[5]); + assertEquals("C", rep[6]); + } + + /** + * Custom throwable that calls methods of VectorWriter with null. + */ + private static final class NullThrowable extends Throwable { + private static final long serialVersionUID = 1L; + + /** + * Create new instance. + */ + public NullThrowable() { + } + + /** + * Print stack trace. + * + * @param s print writer. + */ + public void printStackTrace(final PrintWriter s) { + s.print((Object) null); + s.print((String) null); + s.println((Object) null); + s.println((String) null); + } + } + + /** + * Test capturing stack trace from a throwable that passes null to PrintWriter methods. + */ + + public void testNull() { + ThrowableInformation ti = new ThrowableInformation(new NullThrowable()); + String[] rep = ti.getThrowableStrRep(); + assertEquals(2, rep.length); + String nullStr = String.valueOf((Object) null); + assertEquals(nullStr + nullStr + nullStr, rep[0]); + assertEquals(nullStr, rep[1]); + } + + /** + * Custom throwable that does nothing in printStackTrace. + */ + private static final class EmptyThrowable extends Throwable { + private static final long serialVersionUID = 1L; + + /** + * Create new instance. + */ + public EmptyThrowable() { + } + + /** + * Print stack trace. + * + * @param s print writer. + */ + public void printStackTrace(final PrintWriter s) { + } + } + + /** + * Test capturing stack trace from a throwable that does nothing on a call to printStackTrace. + */ + + public void testEmpty() { + ThrowableInformation ti = new ThrowableInformation(new EmptyThrowable()); + String[] rep = ti.getThrowableStrRep(); + assertEquals(0, rep.length); + } + + /** + * Custom throwable that emits a specified string in printStackTrace. + */ + private static final class StringThrowable extends Throwable { + private static final long serialVersionUID = 1L; + /** + * Stack trace. + */ + private final String stackTrace; + + /** + * Create new instance. + * + * @param trace stack trace. + */ + public StringThrowable(final String trace) { + stackTrace = trace; + } + + /** + * Print stack trace. + * + * @param s print writer. + */ + public void printStackTrace(final PrintWriter s) { + s.print(stackTrace); + } + } + + /** + * Test capturing stack trace from throwable that just has a line feed. + */ + public void testLineFeed() { + ThrowableInformation ti = new ThrowableInformation(new StringThrowable("\n")); + String[] rep = ti.getThrowableStrRep(); + assertEquals(1, rep.length); + assertEquals("", rep[0]); + } + + /** + * Test capturing stack trace from throwable that just has a carriage return. + */ + public void testCarriageReturn() { + ThrowableInformation ti = new ThrowableInformation(new StringThrowable("\r")); + String[] rep = ti.getThrowableStrRep(); + assertEquals(1, rep.length); + assertEquals("", rep[0]); + } + + /** + * Test parsing of line breaks. + */ + public void testParsing() { + ThrowableInformation ti = new ThrowableInformation(new StringThrowable("Line1\rLine2\nLine3\r\nLine4\n\rLine6")); + String[] rep = ti.getThrowableStrRep(); + assertEquals(6, rep.length); + assertEquals("Line1", rep[0]); + assertEquals("Line2", rep[1]); + assertEquals("Line3", rep[2]); + assertEquals("Line4", rep[3]); + assertEquals("", rep[4]); + assertEquals("Line6", rep[5]); + } + + /** + * Test capturing stack trace from throwable that a line feed followed by blank. + */ + public void testLineFeedBlank() { + ThrowableInformation ti = new ThrowableInformation(new StringThrowable("\n ")); + String[] rep = ti.getThrowableStrRep(); + assertEquals(2, rep.length); + assertEquals("", rep[0]); + assertEquals(" ", rep[1]); + } + + /** + * Test that getThrowable returns the throwable provided to the constructor. + */ + public void testGetThrowable() { + Throwable t = new StringThrowable("Hello, World"); + ThrowableInformation ti = new ThrowableInformation(t); + assertSame(t, ti.getThrowable()); + } + + /** + * Tests isolation of returned string representation from internal state of ThrowableInformation. log4j 1.2.15 and + * earlier did not isolate initial call. See bug 44032. + */ + public void testIsolation() { + ThrowableInformation ti = new ThrowableInformation(new StringThrowable("Hello, World")); + String[] rep = ti.getThrowableStrRep(); + assertEquals("Hello, World", rep[0]); + rep[0] = "Bonjour, Monde"; + String[] rep2 = ti.getThrowableStrRep(); + assertEquals("Hello, World", rep2[0]); + } + + /** + * Custom throwable that throws a runtime exception when printStackTrace is called. + */ + private static final class NastyThrowable extends Throwable { + private static final long serialVersionUID = 1L; + + /** + * Create new instance. + */ + public NastyThrowable() { + } + + /** + * Print stack trace. + * + * @param s print writer. + */ + public void printStackTrace(final PrintWriter s) { + s.print("NastyException"); + throw new RuntimeException("Intentional exception"); + } + } + + /** + * Tests that a failure in printStackTrace does not percolate out of getThrowableStrRep(). + * + */ + public void testNastyException() { + ThrowableInformation ti = new ThrowableInformation(new NastyThrowable()); + String[] rep = ti.getThrowableStrRep(); + assertEquals("NastyException", rep[0]); + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/AbsoluteDateAndTimeFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/AbsoluteDateAndTimeFilter.java new file mode 100644 index 00000000000..73f3c2bfcdb --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/AbsoluteDateAndTimeFilter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class AbsoluteDateAndTimeFilter implements Filter { + + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + final String pat = "/" + Filter.ABSOLUTE_DATE_AND_TIME_PAT + "/"; + + if (util.match(pat, in)) { + return util.substitute("s/" + Filter.ABSOLUTE_DATE_AND_TIME_PAT + "//", in); + } else { + return in; + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/AbsoluteTimeFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/AbsoluteTimeFilter.java new file mode 100644 index 00000000000..4528e8abbaf --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/AbsoluteTimeFilter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class AbsoluteTimeFilter implements Filter { + + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + final String pat = "/" + Filter.ABSOLUTE_TIME_PAT + "/"; + + if (util.match(pat, in)) { + return util.substitute("s/" + Filter.ABSOLUTE_TIME_PAT + "//", in); + } + return in; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/Compare.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/Compare.java new file mode 100644 index 00000000000..b65b9e9ce36 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/Compare.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +public class Compare { + + static final int B1_NULL = -1; + static final int B2_NULL = -2; + + public static boolean compare(final Class testClass, final String file1, final String file2) throws IOException { + try (final BufferedReader in1 = new BufferedReader(new FileReader(file1)); + final BufferedReader in2 = new BufferedReader(new InputStreamReader(open(testClass, file2)))) { + return compare(testClass, file1, file2, in1, in2); + } + } + + public static boolean compare(final Class testClass, final String file1, final String file2, final BufferedReader in1, final BufferedReader in2) + throws IOException { + + String s1; + int lineCounter = 0; + + while ((s1 = in1.readLine()) != null) { + lineCounter++; + + final String s2 = in2.readLine(); + + if (!s1.equals(s2)) { + System.out.println("Files [" + file1 + "] and [" + file2 + "] differ on line " + lineCounter); + System.out.println("One reads: [" + s1 + "]."); + System.out.println("Other reads:[" + s2 + "]."); + outputFile(testClass, file1); + outputFile(testClass, file2); + + return false; + } + } + + // the second file is longer + if (in2.read() != -1) { + System.out.println("File [" + file2 + "] longer than file [" + file1 + "]."); + outputFile(testClass, file1); + outputFile(testClass, file2); + + return false; + } + + return true; + } + + static public boolean compare(final String file1, final String file2) throws FileNotFoundException, IOException { + try (final BufferedReader in1 = new BufferedReader(new FileReader(file1)); final BufferedReader in2 = new BufferedReader(new FileReader(file2))) { + + String s1; + int lineCounter = 0; + while ((s1 = in1.readLine()) != null) { + lineCounter++; + final String s2 = in2.readLine(); + if (!s1.equals(s2)) { + System.out.println("Files [" + file1 + "] and [" + file2 + "] differ on line " + lineCounter); + System.out.println("One reads: [" + s1 + "]."); + System.out.println("Other reads:[" + s2 + "]."); + return false; + } + } + + // the second file is longer + if (in2.read() != -1) { + System.out.println("File [" + file2 + "] longer than file [" + file1 + "]."); + return false; + } + + return true; + } + } + + private static final InputStream open(final Class testClass, final String fileName) throws IOException { + String resourceName = fileName; + if (fileName.startsWith("witness/")) { + resourceName = fileName.substring(fileName.lastIndexOf('/') + 1); + } + InputStream is = testClass.getResourceAsStream(resourceName); + if (is == null) { + final File file = new File(fileName); + if (file.exists()) { + is = new FileInputStream(file); + } else { + throw new FileNotFoundException("Resource " + resourceName + " not found"); + } + } + return is; + } + + /** + * + * Prints file on the console. + * + */ + private static void outputFile(final Class testClass, final String file) throws IOException { + try (final InputStream is = open(testClass, file); final BufferedReader in1 = new BufferedReader(new InputStreamReader(is))) { + + String s1; + int lineCounter = 0; + System.out.println("--------------------------------"); + System.out.println("Contents of " + file + ":"); + + while ((s1 = in1.readLine()) != null) { + lineCounter++; + System.out.print(lineCounter); + + if (lineCounter < 10) { + System.out.print(" : "); + } else if (lineCounter < 100) { + System.out.print(" : "); + } else if (lineCounter < 1000) { + System.out.print(" : "); + } else { + System.out.print(": "); + } + + System.out.println(s1); + } + } + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/ControlFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/ControlFilter.java new file mode 100644 index 00000000000..325d460e548 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/ControlFilter.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import java.util.Arrays; + +import org.apache.oro.text.perl.Perl5Util; + +public class ControlFilter implements Filter { + + Perl5Util util = new Perl5Util(); + + String[] allowedPatterns; + + public ControlFilter(final String[] allowedPatterns) { + this.allowedPatterns = allowedPatterns; + } + + @Override + public String filter(final String in) throws UnexpectedFormatException { + final int len = allowedPatterns.length; + for (int i = 0; i < len; i++) { + // System.out.println("["+allowedPatterns[i]+"]"); + if (util.match("/" + allowedPatterns[i] + "/", in)) { + // System.out.println("["+in+"] matched ["+allowedPatterns[i]); + return in; + } + } + + throw new UnexpectedFormatException("[" + in + "] allowedPatterns = " + Arrays.toString(allowedPatterns)); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/EnhancedJunitTestRunnerFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/EnhancedJunitTestRunnerFilter.java new file mode 100644 index 00000000000..172d317464c --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/EnhancedJunitTestRunnerFilter.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class EnhancedJunitTestRunnerFilter implements Filter { + private static final String[] PATTERNS = {"at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner", "at org.apache.tools.ant", + "at junit.textui.TestRunner", "at com.intellij.rt.execution.junit", "at java.lang.reflect.Method.invoke", "at org.apache.maven.", "at org.codehaus.", + "at org.junit.internal.runners.", "at junit.framework.JUnit4TestAdapter"}; + + private final Perl5Util util = new Perl5Util(); + + public EnhancedJunitTestRunnerFilter() { + } + + /** + * Filter out stack trace lines coming from the various JUnit TestRunners. + */ + @Override + public String filter(final String in) { + if (in == null) { + return null; + } + + // + // restore the one instance of Method.invoke that we actually want + // + if (in.indexOf("at junit.framework.TestCase.runTest") != -1) { + return "\tat java.lang.reflect.Method.invoke(X)\n\t" + in.trim(); + } + + for (final String element : PATTERNS) { + if (in.indexOf(element) != -1) { + return null; + } + } + if (util.match("/\\sat /", in)) { + return "\t" + in.trim(); + } + return in; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/EnhancedLineNumberFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/EnhancedLineNumberFilter.java new file mode 100644 index 00000000000..b112a03b5c1 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/EnhancedLineNumberFilter.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import java.util.regex.Pattern; + +public class EnhancedLineNumberFilter implements Filter { + private final Pattern linePattern; + private final Pattern nativePattern; + + public EnhancedLineNumberFilter() { + linePattern = Pattern.compile("\\(.*:\\d{1,4}\\)"); + nativePattern = Pattern.compile("\\(Native Method\\)"); + } + + @Override + public String filter(final String in) { + + if (linePattern.matcher(in).find()) { + return linePattern.matcher(in).replaceAll("(X)"); + } else if (nativePattern.matcher(in).find()) { + return nativePattern.matcher(in).replaceAll("(X)"); + } else { + return in; + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/Filter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/Filter.java new file mode 100644 index 00000000000..16bb43d5d83 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/Filter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +public interface Filter { + + final String BASIC_PAT = "\\[main\\] (FATAL|ERROR|WARN|INFO|DEBUG)"; + final String ISO8601_PAT = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3}"; + + // 06 avr. 2002 18:36:32,036 + // 18 fevr. 2002 20:05:36,222 + static public final String ABSOLUTE_DATE_AND_TIME_PAT = "^\\d{1,2} .{2,6}\\.? 2\\d{3} \\d{2}:\\d{2}:\\d{2},\\d{3}"; + + // 18:54:19,201 + static public final String ABSOLUTE_TIME_PAT = "^\\d{2}:\\d{2}:\\d{2},\\d{3}"; + + static public final String RELATIVE_TIME_PAT = "^\\d{1,10}"; + + String filter(String in) throws UnexpectedFormatException; +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/ISO8601Filter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/ISO8601Filter.java new file mode 100644 index 00000000000..47232ad4e12 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/ISO8601Filter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class ISO8601Filter implements Filter { + + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + final String pat = "/" + ISO8601_PAT + "/"; + + if (util.match(pat, in)) { + return util.substitute("s/" + ISO8601_PAT + "//", in); + } + return in; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/JunitTestRunnerFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/JunitTestRunnerFilter.java new file mode 100644 index 00000000000..175224a96ba --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/JunitTestRunnerFilter.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class JunitTestRunnerFilter implements Filter { + Perl5Util util = new Perl5Util(); + + /** + * Filter out stack trace lines coming from the various JUnit TestRunners. + */ + @Override + public String filter(final String in) { + if (in == null) { + return null; + } + + if (util.match("/at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner/", in)) { + return null; + } else if (util.match("/at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner/", in)) { + return null; + } else if (util.match("/at com.intellij/", in)) { + return null; + } else if (in.indexOf("at junit.") >= 0 && in.indexOf("ui.TestRunner") >= 0) { + return null; + } else if (in.indexOf("org.apache.maven") >= 0) { + return null; + } else if (in.indexOf("junit.internal") >= 0) { + return null; + } else if (in.indexOf("JUnit4TestAdapter") >= 0) { + return null; + } else if (util.match("/\\sat /", in)) { + return "\t" + in.trim(); + } else { + return in; + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/LineNumberFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/LineNumberFilter.java new file mode 100644 index 00000000000..45c65786a3f --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/LineNumberFilter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class LineNumberFilter implements Filter { + + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + if (util.match("/\\(.*:\\d{1,4}\\)/", in)) { + return util.substitute("s/:\\d{1,4}\\)/:XXX)/", in); + } + if (in.indexOf(", Compiled Code") >= 0) { + return util.substitute("s/, Compiled Code/:XXX/", in); + } + return in; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/RelativeTimeFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/RelativeTimeFilter.java new file mode 100644 index 00000000000..a15ffb10c06 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/RelativeTimeFilter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class RelativeTimeFilter implements Filter { + + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + final String pat = "/" + Filter.RELATIVE_TIME_PAT + "/"; + + if (util.match(pat, in)) { + // System.out.println("Removing relative time from line ["+in+"]"); + return util.substitute("s/" + Filter.RELATIVE_TIME_PAT + "//", in); + } + return in; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java index 057646370e3..a3584c2def7 100644 --- a/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/SerializationTestHelper.java @@ -30,65 +30,20 @@ import org.apache.commons.io.FileUtils; - /** * Utiities for serialization tests. */ public class SerializationTestHelper { /** - * Private constructor. - */ - private SerializationTestHelper() { - } - - /** - * Creates a clone by serializing object and - * deserializing byte stream. - * - * @param obj object to serialize and deserialize. - * @return clone - * @throws IOException on IO error. - * @throws ClassNotFoundException if class not found. - */ - public static Object serializeClone(final Object obj) - throws IOException, ClassNotFoundException { - final ByteArrayOutputStream memOut = new ByteArrayOutputStream(); - try (final ObjectOutputStream objOut = new ObjectOutputStream(memOut)) { - objOut.writeObject(obj); - } - - final ByteArrayInputStream src = new ByteArrayInputStream(memOut.toByteArray()); - final ObjectInputStream objIs = new ObjectInputStream(src); - - return objIs.readObject(); - } - - /** - * Deserializes a specified file. - * - * @param witness serialization file, may not be null. - * @return deserialized object. - * @throws Exception thrown on IO or deserialization exception. - */ - public static Object deserializeStream(final String witness) throws Exception { - try (final ObjectInputStream objIs = new ObjectInputStream(new FileInputStream(witness))) { - return objIs.readObject(); - } - } - - /** - * Checks the serialization of an object against an file - * containing the expected serialization. + * Checks the serialization of an object against an file containing the expected serialization. * - * @param witness name of file containing expected serialization. - * @param obj object to be serialized. - * @param skip positions in serialized stream that should not be compared. + * @param witness name of file containing expected serialization. + * @param obj object to be serialized. + * @param skip positions in serialized stream that should not be compared. * @param endCompare position to stop comparison. * @throws Exception thrown on IO or serialization exception. */ - public static void assertSerializationEquals( - final String witness, final Object obj, final int[] skip, - final int endCompare) throws Exception { + public static void assertSerializationEquals(final String witness, final Object obj, final int[] skip, final int endCompare) throws Exception { final ByteArrayOutputStream memOut = new ByteArrayOutputStream(); try (final ObjectOutputStream objOut = new ObjectOutputStream(memOut)) { objOut.writeObject(obj); @@ -100,15 +55,13 @@ public static void assertSerializationEquals( /** * Asserts the serialized form of an object. * - * @param witness file name of expected serialization. - * @param actual byte array of actual serialization. - * @param skip positions to skip comparison. + * @param witness file name of expected serialization. + * @param actual byte array of actual serialization. + * @param skip positions to skip comparison. * @param endCompare position to stop comparison. * @throws IOException thrown on IO or serialization exception. */ - public static void assertStreamEquals( - final String witness, final byte[] actual, final int[] skip, - final int endCompare) throws IOException { + public static void assertStreamEquals(final String witness, final byte[] actual, final int[] skip, final int endCompare) throws IOException { final File witnessFile = new File(witness); if (witnessFile.exists()) { @@ -129,20 +82,55 @@ public static void assertStreamEquals( for (int i = 0; i < endScan; i++) { if ((skipIndex < skip.length) && (skip[skipIndex] == i)) { skipIndex++; - } else { - if (expected[i] != actual[i]) { - assertEquals( - "Difference at offset " + i, expected[i], actual[i]); - } + } else if (expected[i] != actual[i]) { + assertEquals("Difference at offset " + i, expected[i], actual[i]); } } } else { // - // if the file doesn't exist then - // assume that we are setting up and need to write it + // if the file doesn't exist then + // assume that we are setting up and need to write it FileUtils.writeByteArrayToFile(witnessFile, actual); fail("Writing witness file " + witness); } } -} + /** + * Deserializes a specified file. + * + * @param witness serialization file, may not be null. + * @return deserialized object. + * @throws Exception thrown on IO or deserialization exception. + */ + public static Object deserializeStream(final String witness) throws Exception { + try (final ObjectInputStream objIs = new ObjectInputStream(new FileInputStream(witness))) { + return objIs.readObject(); + } + } + + /** + * Creates a clone by serializing object and deserializing byte stream. + * + * @param obj object to serialize and deserialize. + * @return clone + * @throws IOException on IO error. + * @throws ClassNotFoundException if class not found. + */ + public static Object serializeClone(final Object obj) throws IOException, ClassNotFoundException { + final ByteArrayOutputStream memOut = new ByteArrayOutputStream(); + try (final ObjectOutputStream objOut = new ObjectOutputStream(memOut)) { + objOut.writeObject(obj); + } + + final ByteArrayInputStream src = new ByteArrayInputStream(memOut.toByteArray()); + final ObjectInputStream objIs = new ObjectInputStream(src); + + return objIs.readObject(); + } + + /** + * Private constructor. + */ + private SerializationTestHelper() { + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/SunReflectFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/SunReflectFilter.java new file mode 100644 index 00000000000..14c8e00bb64 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/SunReflectFilter.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +/** + * The sun.reflect.* and java.lang.reflect.* lines are not present in all JDKs. + */ +public class SunReflectFilter implements Filter { + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + if ((in == null) || util.match("/at sun.reflect/", in) || (in.indexOf("at java.lang.reflect.") >= 0)) { + return null; + } + if (in.indexOf("Compiled Code") >= 0) { + if (in.indexOf("junit.framework.TestSuite") >= 0) { + return util.substitute("s/Compiled Code/TestSuite.java:XXX/", in); + } + } + if (util.match("/\\(Method.java:.*\\)/", in)) { + return util.substitute("s/\\(Method.java:.*\\)/(Native Method)/", in); + } + return in; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/Transformer.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/Transformer.java new file mode 100644 index 00000000000..79d659e332e --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/Transformer.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; + +public class Transformer { + + public static void transform(final String in, final String out, final Filter filter) throws IOException, UnexpectedFormatException { + + String line; + final BufferedReader input = new BufferedReader(new FileReader(in)); + final PrintStream output = new PrintStream(new FileOutputStream(out)); + + // Initialization of input and output omitted + while ((line = input.readLine()) != null) { + line = filter.filter(line); + output.println(line); + } + } + + public static void transform(final String in, final String out, final Filter[] filters) throws IOException, UnexpectedFormatException { + + String line; + final BufferedReader input = new BufferedReader(new FileReader(in)); + final PrintStream output = new PrintStream(new FileOutputStream(out, false)); + + // Initialization of input and output omitted + while ((line = input.readLine()) != null) { + // apply all filters + for (final Filter filter : filters) { + line = filter.filter(line); + } + if (line != null) { + output.println(line); + } + } + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/UnexpectedFormatException.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/UnexpectedFormatException.java new file mode 100644 index 00000000000..ed25a357d8d --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/UnexpectedFormatException.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +public class UnexpectedFormatException extends Exception { + + private static final long serialVersionUID = 1787725660780924147L; + + public UnexpectedFormatException(final String msg) { + super(msg); + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/XMLLineAttributeFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/XMLLineAttributeFilter.java new file mode 100644 index 00000000000..9fcd07016a5 --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/XMLLineAttributeFilter.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class XMLLineAttributeFilter implements Filter { + + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + if (util.match("/line=\"\\d{1,3}\"/", in)) { + return util.substitute("s/line=\"\\d{1,3}\"/line=\"X\"/", in); + } else if (util.match("/line=\"?\"/", in)) { + return util.substitute("s/line=\"?\"/line=\"X\"/", in); + } else { + return in; + } + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/util/XMLTimestampFilter.java b/log4j-1.2-api/src/test/java/org/apache/log4j/util/XMLTimestampFilter.java new file mode 100644 index 00000000000..5012831f37a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/util/XMLTimestampFilter.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.util; + +import org.apache.oro.text.perl.Perl5Util; + +public class XMLTimestampFilter implements Filter { + + Perl5Util util = new Perl5Util(); + + @Override + public String filter(final String in) { + if (util.match("/timestamp=\"\\d{10,13}\"/", in)) { + return util.substitute("s/timestamp=\"\\d{10,13}\"/timestamp=\"XXX\"/", in); + } + return in; + } +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/xml/DOMTestCase.java b/log4j-1.2-api/src/test/java/org/apache/log4j/xml/DOMTestCase.java new file mode 100644 index 00000000000..a01aa88fe2b --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/xml/DOMTestCase.java @@ -0,0 +1,473 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.log4j.Appender; +import org.apache.log4j.FileAppender; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.VectorAppender; +import org.apache.log4j.bridge.AppenderWrapper; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.LoggerFactory; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.OptionHandler; +import org.apache.log4j.spi.ThrowableRenderer; +import org.apache.log4j.spi.ThrowableRendererSupport; +import org.apache.log4j.util.Compare; +import org.apache.log4j.util.ControlFilter; +import org.apache.log4j.util.Filter; +import org.apache.log4j.util.ISO8601Filter; +import org.apache.log4j.util.JunitTestRunnerFilter; +import org.apache.log4j.util.LineNumberFilter; +import org.apache.log4j.util.SunReflectFilter; +import org.apache.log4j.util.Transformer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class DOMTestCase { + + /** + * CustomErrorHandler for testCategoryFactory2. + */ + public static class CustomErrorHandler implements ErrorHandler { + public CustomErrorHandler() { + } + + public void activateOptions() { + } + + @Override + public void error(final String message) { + } + + @Override + public void error(final String message, final Exception e, final int errorCode) { + } + + @Override + public void error(final String message, final Exception e, final int errorCode, final LoggingEvent event) { + } + + @Override + public void setAppender(final Appender appender) { + } + + @Override + public void setBackupAppender(final Appender appender) { + } + + @Override + public void setLogger(final Logger logger) { + } + } + + /** + * CustomLogger implementation for testCategoryFactory1 and 2. + */ + private static class CustomLogger extends Logger { + /** + * Creates new instance. + * + * @param name logger name. + */ + public CustomLogger(final String name) { + super(name); + } + } + + /** + * Creates new instances of CustomLogger. + */ + public static class CustomLoggerFactory implements LoggerFactory { + + /** + * Additivity, expected to be set false in configuration file. + */ + private boolean additivity; + + /** + * Create new instance of factory. + */ + public CustomLoggerFactory() { + additivity = true; + } + + /** + * Create new logger. + * + * @param name logger name. + * @return new logger. + */ + @Override + public Logger makeNewLoggerInstance(final String name) { + final Logger logger = new CustomLogger(name); + assertFalse(additivity); + return logger; + } + + /** + * Set additivity. + * + * @param newVal new value of additivity. + */ + public void setAdditivity(final boolean newVal) { + additivity = newVal; + } + } + + /** + * Mock ThrowableRenderer for testThrowableRenderer. See bug 45721. + */ + public static class MockThrowableRenderer implements ThrowableRenderer, OptionHandler { + private boolean activated = false; + private boolean showVersion = true; + + public MockThrowableRenderer() { + } + + @Override + public void activateOptions() { + activated = true; + } + + @Override + public String[] doRender(final Throwable t) { + return new String[0]; + } + + public boolean getShowVersion() { + return showVersion; + } + + public boolean isActivated() { + return activated; + } + + public void setShowVersion(final boolean v) { + showVersion = v; + } + } + + static String TEMP_A1 = "target/output/temp.A1"; + static String TEMP_A2 = "target/output/temp.A2"; + static String FILTERED_A1 = "target/output/filtered.A1"; + static String FILTERED_A2 = "target/output/filtered.A2"; + static String EXCEPTION1 = "java.lang.Exception: Just testing"; + static String EXCEPTION2 = "\\s*at .*\\(.*\\)"; + static String EXCEPTION3 = "\\s*at .*\\(Native Method\\)"; + static String EXCEPTION4 = "\\s*at .*\\(.*Compiled Code\\)"; + static String EXCEPTION5 = "\\s*at .*\\(.*libgcj.*\\)"; + static String TEST1_1A_PAT = "(TRACE|DEBUG|INFO |WARN |ERROR|FATAL) \\w*\\.\\w* - Message \\d"; + static String TEST1_1B_PAT = "(TRACE|DEBUG|INFO |WARN |ERROR|FATAL) root - Message \\d"; + static String TEST1_2_PAT = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} " + "\\[main]\\ (TRACE|DEBUG|INFO|WARN|ERROR|FATAL) .* - Message \\d"; + + private static final boolean Log4j1ActualAppender = false; + + private static final boolean AssumeThrowableRendererSupport = false; + + Logger root; + + Logger logger; + + void common() { + final String oldThreadName = Thread.currentThread().getName(); + Thread.currentThread().setName("main"); + + int i = -1; + + logger.trace("Message " + ++i); + root.trace("Message " + i); + + logger.debug("Message " + ++i); + root.debug("Message " + i); + + logger.info("Message " + ++i); + root.info("Message " + i); + + logger.warn("Message " + ++i); + root.warn("Message " + i); + + logger.error("Message " + ++i); + root.error("Message " + i); + + logger.log(Level.FATAL, "Message " + ++i); + root.log(Level.FATAL, "Message " + i); + + final Exception e = new Exception("Just testing"); + logger.debug("Message " + ++i, e); + root.debug("Message " + i, e); + + logger.error("Message " + ++i, e); + root.error("Message " + i, e); + + Thread.currentThread().setName(oldThreadName); + } + + @BeforeEach + public void setUp() { + root = Logger.getRootLogger(); + logger = Logger.getLogger(DOMTestCase.class); + } + + @AfterEach + public void tearDown() { + root.getLoggerRepository().resetConfiguration(); + } + + @Test + @Disabled + public void test1() throws Exception { + DOMConfigurator.configure("src/test/resources/log4j1-1.2.17/input/xml/DOMTestCase1.xml"); + common(); + + final ControlFilter cf1 = new ControlFilter(new String[] {TEST1_1A_PAT, TEST1_1B_PAT, EXCEPTION1, EXCEPTION2, EXCEPTION3, EXCEPTION4, EXCEPTION5}); + + final ControlFilter cf2 = new ControlFilter(new String[] {TEST1_2_PAT, EXCEPTION1, EXCEPTION2, EXCEPTION3, EXCEPTION4, EXCEPTION5}); + + Transformer.transform(TEMP_A1, FILTERED_A1, new Filter[] {cf1, new LineNumberFilter(), new SunReflectFilter(), new JunitTestRunnerFilter()}); + + Transformer.transform(TEMP_A2, FILTERED_A2, + new Filter[] {cf2, new LineNumberFilter(), new ISO8601Filter(), new SunReflectFilter(), new JunitTestRunnerFilter()}); + + assertTrue(Compare.compare(FILTERED_A1, "witness/dom.A1.1")); + assertTrue(Compare.compare(FILTERED_A2, "witness/dom.A2.1")); + } + + /** + * Tests processing of external entities in XML file. + */ + @Test + @Disabled + public void test4() throws Exception { + DOMConfigurator.configure("src/test/resources/log4j1-1.2.17/input/xml/DOMTest4.xml"); + common(); + + final ControlFilter cf1 = new ControlFilter(new String[] {TEST1_1A_PAT, TEST1_1B_PAT, EXCEPTION1, EXCEPTION2, EXCEPTION3, EXCEPTION4, EXCEPTION5}); + + final ControlFilter cf2 = new ControlFilter(new String[] {TEST1_2_PAT, EXCEPTION1, EXCEPTION2, EXCEPTION3, EXCEPTION4, EXCEPTION5}); + + Transformer.transform(TEMP_A1 + ".4", FILTERED_A1 + ".4", + new Filter[] {cf1, new LineNumberFilter(), new SunReflectFilter(), new JunitTestRunnerFilter()}); + + Transformer.transform(TEMP_A2 + ".4", FILTERED_A2 + ".4", + new Filter[] {cf2, new LineNumberFilter(), new ISO8601Filter(), new SunReflectFilter(), new JunitTestRunnerFilter()}); + + assertTrue(Compare.compare(FILTERED_A1 + ".4", "witness/dom.A1.4")); + assertTrue(Compare.compare(FILTERED_A2 + ".4", "witness/dom.A2.4")); + } + + /** + * Tests that loggers mentioned in logger elements use the specified categoryFactory. See bug 33708. + */ + @Test + @Disabled + public void testCategoryFactory1() { + DOMConfigurator.configure("src/test/resources/log4j1-1.2.17/input/xml/categoryfactory1.xml"); + // + // logger not explicitly mentioned in configuration, + // should use default factory + final Logger logger2 = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testCategoryFactory1.2"); + assertFalse(logger2.toString(), logger2 instanceof CustomLogger); + // + // logger explicitly mentioned in configuration, + // should be a CustomLogger + final Logger logger1 = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testCategoryFactory1.1"); + assertTrue(logger1.toString(), logger1 instanceof CustomLogger); + // + // logger not explicitly mentioned in configuration, + // should use default factory + final Logger logger2Bis = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testCategoryFactory1.2"); + assertFalse(logger2Bis.toString(), logger2Bis instanceof CustomLogger); + } + + /** + * Tests that loggers mentioned in logger-ref elements use the specified categoryFactory. See bug 33708. + */ + @Test + @Disabled + public void testCategoryFactory2() { + DOMConfigurator.configure("src/test/resources/log4j1-1.2.17/input/xml/categoryfactory2.xml"); + // + // logger not explicitly mentioned in configuration, + // should use default factory + final Logger logger2 = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testCategoryFactory2.2"); + assertFalse(logger2.toString(), logger2 instanceof CustomLogger); + // + // logger explicitly mentioned in configuration, + // should be a CustomLogger + final Logger logger1 = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testCategoryFactory2.1"); + assertTrue(logger1.toString(), logger1 instanceof CustomLogger); + // + // logger not explicitly mentioned in configuration, + // should use default factory + final Logger logger2Bis = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testCategoryFactory2.2"); + assertFalse(logger2Bis.toString(), logger2Bis instanceof CustomLogger); + } + + /** + * Test checks that configureAndWatch does initial configuration, see bug 33502. + * + * @throws Exception if IO error. + */ + @Test + public void testConfigureAndWatch() throws Exception { + DOMConfigurator.configureAndWatch("src/test/resources/log4j1-1.2.17/input/xml/DOMTestCase1.xml"); + assertNotNull(Logger.getRootLogger().getAppender("A1")); + } + + /** + * Test for bug 47465. configure(URL) did not close opened JarURLConnection. + * + * @throws IOException if IOException creating properties jar. + */ + @Test + public void testJarURL() throws IOException { + final File input = new File("src/test/resources/log4j1-1.2.17/input/xml/defaultInit.xml"); + System.out.println(input.getAbsolutePath()); + final File configJar = new File("target/output/xml.jar"); + final File dir = new File("target/output"); + dir.mkdirs(); + try (final InputStream inputStream = new FileInputStream(input); + final FileOutputStream out = new FileOutputStream(configJar); + final ZipOutputStream zos = new ZipOutputStream(out)) { + zos.putNextEntry(new ZipEntry("log4j.xml")); + int len; + final byte[] buf = new byte[1024]; + while ((len = inputStream.read(buf)) > 0) { + zos.write(buf, 0, len); + } + zos.closeEntry(); + } + final URL urlInJar = new URL("jar:" + configJar.toURL() + "!/log4j.xml"); + DOMConfigurator.configure(urlInJar); + assertTrue(configJar.delete()); + assertFalse(configJar.exists()); + } + + /** + * Tests that loggers mentioned in logger elements use the specified loggerFactory. See bug 33708. + */ + @Test + @Disabled("TODO") + public void testLoggerFactory1() { + DOMConfigurator.configure("src/test/resources/log4j1-1.2.17/input/xml/loggerfactory1.xml"); + // + // logger not explicitly mentioned in configuration, + // should use default factory + final Logger logger2 = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testLoggerFactory1.2"); + assertNotNull(logger2); + assertFalse(logger2.toString(), logger2 instanceof CustomLogger); + // + // logger explicitly mentioned in configuration, + // should be a CustomLogger + final Logger logger1 = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testLoggerFactory1.1"); + assertNotNull(logger1); + assertTrue(logger1.toString(), logger1 instanceof CustomLogger); + // + // logger not explicitly mentioned in configuration, + // should use default factory + final Logger logger2Bis = Logger.getLogger("org.apache.log4j.xml.DOMTestCase.testLoggerFactory1.2"); + assertNotNull(logger2Bis); + assertFalse(logger2Bis.toString(), logger2Bis instanceof CustomLogger); + } + + /** + * This test checks that the subst method of an extending class is checked when evaluating parameters. See bug 43325. + * + */ + @Test + public void testOverrideSubst() { + final DOMConfigurator configurator = new DOMConfigurator() { + protected String subst(final String value) { + if ("target/output/temp.A1".equals(value)) { + return "target/output/subst-test.A1"; + } + return value; + } + }; + configurator.doConfigure("src/test/resources/log4j1-1.2.17/input/xml/DOMTestCase1.xml", LogManager.getLoggerRepository()); + final String name = "A1"; + final Appender appender = Logger.getRootLogger().getAppender(name); + assertNotNull(name, appender); + if (Log4j1ActualAppender) { + final FileAppender a1 = (FileAppender) appender; + assertNotNull(name, a1); + final String file = a1.getFile(); + assertEquals("target/output/subst-test.A1", file); + } else { + final AppenderWrapper wrapper = (AppenderWrapper) appender; + assertNotNull(name, wrapper); + final org.apache.logging.log4j.core.appender.FileAppender a1 = (org.apache.logging.log4j.core.appender.FileAppender) wrapper.getAppender(); + assertNotNull(wrapper.toString(), a1); + final String file = a1.getFileName(); + assertNotNull(a1.toString(), file); + // TODO Support this or not? + // assertEquals("target/output/subst-test.A1", file); + } + } + + /** + * Tests that reset="true" on log4j:configuration element resets repository before configuration. + * + * @throws Exception thrown on error. + */ + @Test + public void testReset() throws Exception { + final VectorAppender appender = new VectorAppender(); + appender.setName("V1"); + Logger.getRootLogger().addAppender(appender); + DOMConfigurator.configure("src/test/resources/log4j1-1.2.17/input/xml/testReset.xml"); + assertNull(Logger.getRootLogger().getAppender("V1")); + } + + /** + * Test of log4j.throwableRenderer support. See bug 45721. + */ + @Test + public void testThrowableRenderer1() { + DOMConfigurator.configure("src/test/resources/log4j1-1.2.17/input/xml/throwableRenderer1.xml"); + final ThrowableRendererSupport repo = (ThrowableRendererSupport) LogManager.getLoggerRepository(); + final MockThrowableRenderer renderer = (MockThrowableRenderer) repo.getThrowableRenderer(); + LogManager.resetConfiguration(); + if (AssumeThrowableRendererSupport) { + assertNotNull(renderer); + assertEquals(true, renderer.isActivated()); + assertEquals(false, renderer.getShowVersion()); + } + } + +} diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/xml/XLevel.java b/log4j-1.2-api/src/test/java/org/apache/log4j/xml/XLevel.java new file mode 100644 index 00000000000..d90438c070a --- /dev/null +++ b/log4j-1.2-api/src/test/java/org/apache/log4j/xml/XLevel.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.xml; + +import org.apache.log4j.Level; + +/** + * This class introduces a new level called TRACE. TRACE has lower level than DEBUG. + */ +public class XLevel extends Level { + private static final long serialVersionUID = 7288304330257085144L; + + public static final int TRACE_INT = Level.DEBUG_INT - 1; + public static final int LETHAL_INT = Level.FATAL_INT + 1; + + private static String TRACE_STR = "TRACE"; + private static String LETHAL_STR = "LETHAL"; + + public static final XLevel TRACE = new XLevel(TRACE_INT, TRACE_STR, 7); + public static final XLevel LETHAL = new XLevel(LETHAL_INT, LETHAL_STR, 0); + + public static Level toLevel(final int i) throws IllegalArgumentException { + switch (i) { + case TRACE_INT: + return XLevel.TRACE; + case LETHAL_INT: + return XLevel.LETHAL; + } + return Level.toLevel(i); + } + + /** + * Convert the string passed as argument to a level. If the conversion fails, then this method returns {@link #TRACE}. + */ + public static Level toLevel(final String sArg) { + return toLevel(sArg, XLevel.TRACE); + } + + public static Level toLevel(final String sArg, final Level defaultValue) { + + if (sArg == null) { + return defaultValue; + } + final String stringVal = sArg.toUpperCase(); + + if (stringVal.equals(TRACE_STR)) { + return XLevel.TRACE; + } else if (stringVal.equals(LETHAL_STR)) { + return XLevel.LETHAL; + } + + return Level.toLevel(sArg, defaultValue); + } + + protected XLevel(final int level, final String strLevel, final int syslogEquiv) { + super(level, strLevel, syslogEquiv); + } + +} diff --git a/log4j-1.2-api/src/test/resources/L7D_en_US.properties b/log4j-1.2-api/src/test/resources/L7D_en_US.properties index c3c2802d525..d5775c616e8 100644 --- a/log4j-1.2-api/src/test/resources/L7D_en_US.properties +++ b/log4j-1.2-api/src/test/resources/L7D_en_US.properties @@ -1,3 +1,4 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -12,6 +13,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# + test=This is the English, US test. hello_world=Hello world. msg1=This is test number {0} with string argument {1}. diff --git a/log4j-1.2-api/src/test/resources/L7D_fr.properties b/log4j-1.2-api/src/test/resources/L7D_fr.properties index 25b878aebc6..9beb5b74193 100644 --- a/log4j-1.2-api/src/test/resources/L7D_fr.properties +++ b/log4j-1.2-api/src/test/resources/L7D_fr.properties @@ -1,3 +1,4 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -12,6 +13,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# + test=Ceci est le test en francais pour la France. hello_world=Bonjour la France. msg1=Ceci est le test numero {0} contenant l''argument {1}. diff --git a/log4j-1.2-api/src/test/resources/L7D_fr_CH.properties b/log4j-1.2-api/src/test/resources/L7D_fr_CH.properties index ba9b1ffbfc6..ef9d824e09a 100644 --- a/log4j-1.2-api/src/test/resources/L7D_fr_CH.properties +++ b/log4j-1.2-api/src/test/resources/L7D_fr_CH.properties @@ -1,3 +1,4 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. @@ -12,5 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# + test=Ceci est le test en francais pour la p'tite Suisse. hello world=Salut le monde. diff --git a/log4j-1.2-api/src/test/resources/LOG4J2-3247.properties b/log4j-1.2-api/src/test/resources/LOG4J2-3247.properties new file mode 100644 index 00000000000..da261165f80 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/LOG4J2-3247.properties @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.filter.1=org.apache.log4j.config.NeutralFilterFixture +log4j.appender.CONSOLE.filter.1.onMatch=neutral +log4j.appender.CONSOLE.Target=System.out +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n + +log4j.appender.A1=org.apache.log4j.FileAppender +log4j.appender.A1.File=target/temp.A1 +log4j.appender.A1.Append=false +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-5p %c{2} - %m%n +log4j.appender.A2=org.apache.log4j.FileAppender +log4j.appender.A2.File=target/temp.A2 +log4j.appender.A2.Append=false +log4j.appender.A2.layout=org.apache.log4j.TTCCLayout +log4j.appender.A2.layout.DateFormat=ISO8601 +log4j.logger.org.apache.log4j.xml=trace, A1 +log4j.rootLogger=trace, CONSOLE, A1, A2 diff --git a/log4j-1.2-api/src/test/resources/LOG4J2-3281.properties b/log4j-1.2-api/src/test/resources/LOG4J2-3281.properties new file mode 100644 index 00000000000..4f309104278 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/LOG4J2-3281.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.CUSTOM=org.apache.log4j.CustomNoopAppender +log4j.appender.CUSTOM.filter.1=org.apache.log4j.config.NeutralFilterFixture +log4j.appender.CUSTOM.filter.1.onMatch=neutral +log4j.appender.CUSTOM.Target=System.out +log4j.appender.CUSTOM.layout=org.apache.log4j.PatternLayout +log4j.appender.CUSTOM.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n + +log4j.logger.org.apache.log4j.xml=trace, CUSTOM +log4j.rootLogger=trace, CUSTOM diff --git a/log4j-1.2-api/src/test/resources/LOG4J2-3326.properties b/log4j-1.2-api/src/test/resources/LOG4J2-3326.properties new file mode 100644 index 00000000000..e21d0d394c2 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/LOG4J2-3326.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.CUSTOM=org.apache.log4j.CustomNoopAppender +log4j.appender.CUSTOM.filter.1=org.apache.log4j.varia.LevelRangeFilter +log4j.appender.CUSTOM.filter.1.levelMin=ALL +log4j.appender.CUSTOM.filter.2=org.apache.log4j.varia.LevelRangeFilter + +log4j.rootLogger=trace, CUSTOM diff --git a/log4j-1.2-api/src/test/resources/LOG4J2-3407.properties b/log4j-1.2-api/src/test/resources/LOG4J2-3407.properties new file mode 100644 index 00000000000..499a458ab51 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/LOG4J2-3407.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.filter.1=org.apache.log4j.config.NeutralFilterFixture +log4j.appender.CONSOLE.filter.1.onMatch=neutral +log4j.appender.CONSOLE.Target=System.out +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n + +log4j.logger.org.apache.log4j.xml=trace, A1 +log4j.rootLogger=trace, CONSOLE diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties index 5fa402026c2..8489a000a45 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties @@ -1,16 +1,20 @@ # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and -# limitations under the License. See accompanying LICENSE file. +# limitations under the License. # + log4j.appender.test=org.apache.log4j.ConsoleAppender log4j.appender.test.Target=System.out log4j.appender.test.layout=org.apache.log4j.PatternLayout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties index b08514c1703..265e1ee54d7 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties @@ -1,18 +1,19 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +# # Define some default values that can be overridden by system properties hadoop.root.logger=INFO,console diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties index ced0687caad..3ec3024e9f6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties @@ -1,14 +1,20 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # log4j configuration used during build and unit tests log4j.rootLogger=info,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties index b347d275e2f..9e7af6c2c6a 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties @@ -1,11 +1,10 @@ # -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties index 9efd671a087..230096ee0b2 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties @@ -1,11 +1,10 @@ # -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties index ced0687caad..3ec3024e9f6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties @@ -1,14 +1,20 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # log4j configuration used during build and unit tests log4j.rootLogger=info,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties index 73788467112..3253e4ea06e 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties @@ -1,19 +1,20 @@ # -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # + # log4j configuration used during build and unit tests log4j.rootLogger=info,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties index 52aac432644..c65f9038f27 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties @@ -1,22 +1,18 @@ # -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties index 73788467112..3253e4ea06e 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties @@ -1,19 +1,20 @@ # -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # + # log4j configuration used during build and unit tests log4j.rootLogger=info,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties index 1330ed1aef3..6876cf0851a 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties @@ -1,14 +1,20 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + # log4j configuration used during build and unit tests log4j.rootLogger=info,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties index 73ee3f9c6ce..43631108a79 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties @@ -1,19 +1,20 @@ # -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # + # log4j configuration used during build and unit tests log4j.rootLogger=INFO,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties index 6aeb41dcdd0..2744e4d52be 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties @@ -1,32 +1,19 @@ # -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests log4j.rootLogger=INFO,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties index cfd405b16e7..46bfc946989 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties @@ -1,16 +1,20 @@ # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and -# limitations under the License. See accompanying LICENSE file. +# limitations under the License. # + log4j.appender.test=org.apache.log4j.ConsoleAppender log4j.appender.test.Target=System.out log4j.appender.test.layout=org.apache.log4j.PatternLayout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/log4j.properties index e46856e7209..d1b0064579a 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/log4j.properties @@ -1,33 +1,20 @@ # -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - # log4j configuration used during build and unit tests log4j.rootLogger=INFO,stdout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties index bed1abcbd08..93ec4426993 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties @@ -1,18 +1,19 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties index c088bb7fdff..d1b0064579a 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase-tests/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties index 81a3f6ad5d2..189e39268b6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties @@ -1,14 +1,19 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. # log4j configuration used during build and unit tests diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.properties index 123a51db0b3..0af97ae6b71 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -13,10 +30,14 @@ log4j.rootLogger=TRACE, DRFA # log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender +log4j.appender.DRFA.Append=false +log4j.appender.DRFA.BufferedIO=true +log4j.appender.DRFA.BufferSize=1000 log4j.appender.DRFA.File=${hadoop.log.dir}/${hadoop.log.file} +log4j.appender.DRFA.ImmediateFlush=false # Rollover at midnight -log4j.appender.DRFA.DatePattern=.yyyy-MM-dd +log4j.appender.DRFA.DatePattern=.dd-MM-yyyy log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.xml new file mode 100644 index 00000000000..9076d32a16c --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-FileAppender-with-props.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-FileAppender-with-props.properties new file mode 100644 index 00000000000..e503ae1bd46 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-FileAppender-with-props.properties @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################### +# +# Log4J 1.2 Configuration. +# + +hadoop.log.file=hadoop.log + +log4j.rootLogger=TRACE, FILE_APPENDER + +# +# Rolling File Appender +# +hadoop.log.maxfilesize=256MB +hadoop.log.maxbackupindex=20 +log4j.appender.FILE_APPENDER=org.apache.log4j.FileAppender +log4j.appender.FILE_APPENDER.Append=false +log4j.appender.FILE_APPENDER.BufferedIO=true +log4j.appender.FILE_APPENDER.BufferSize=1000 +log4j.appender.FILE_APPENDER.File=${log4j.test.tmpdir}/${hadoop.log.file} +log4j.appender.FILE_APPENDER.ImmediateFlush=false +log4j.appender.FILE_APPENDER.layout=org.apache.log4j.PatternLayout + +# Pattern format: Date LogLevel LoggerName LogMessage +log4j.appender.FILE_APPENDER.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-NullAppender.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-NullAppender.properties index d89a4f4eadf..5d90e85e165 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-NullAppender.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-NullAppender.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-NullAppender.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-NullAppender.xml new file mode 100644 index 00000000000..7153cabaeaa --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-NullAppender.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender-with-props.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender-with-props.properties index b664bb8ccd0..fd6c6451d7f 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender-with-props.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender-with-props.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -14,8 +31,11 @@ log4j.rootLogger=TRACE, RFA hadoop.log.maxfilesize=256MB hadoop.log.maxbackupindex=20 log4j.appender.RFA=org.apache.log4j.RollingFileAppender +log4j.appender.RFA.Append=false +log4j.appender.RFA.BufferedIO=true +log4j.appender.RFA.BufferSize=1000 log4j.appender.RFA.File=${hadoop.log.dir}/${hadoop.log.file} - +log4j.appender.RFA.ImmediateFlush=false log4j.appender.RFA.MaxFileSize=${hadoop.log.maxfilesize} log4j.appender.RFA.MaxBackupIndex=${hadoop.log.maxbackupindex} diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender.properties index 55234bab0ba..310471a3969 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -9,8 +26,11 @@ log4j.rootLogger=TRACE, RFA # Rolling File Appender - cap space usage at 5gb. # log4j.appender.RFA=org.apache.log4j.RollingFileAppender +log4j.appender.RFA.Append=false +log4j.appender.RFA.BufferedIO=true +log4j.appender.RFA.BufferSize=1000 log4j.appender.RFA.File=target/hadoop.log - +log4j.appender.RFA.ImmediateFlush=false log4j.appender.RFA.MaxFileSize=256MB log4j.appender.RFA.MaxBackupIndex=20 diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender.xml new file mode 100644 index 00000000000..1ad1d4b1f59 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-RollingFileAppender.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-capitalization.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-capitalization.properties new file mode 100644 index 00000000000..89a5bcb24ce --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-capitalization.properties @@ -0,0 +1,44 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################### +# +# Log4J 1.2 Configuration. +# + +log4j.rootLogger=TRACE, ConsoleCapitalized, ConsoleJavaStyle + +############################################################################## +# +# The Console log +# + +log4j.appender.ConsoleCapitalized=org.apache.log4j.ConsoleAppender +log4j.appender.ConsoleCapitalized.Encoding=ISO-8859-1 +log4j.appender.ConsoleCapitalized.Follow=true +log4j.appender.ConsoleCapitalized.ImmediateFlush=false +log4j.appender.ConsoleCapitalized.Target=System.err +log4j.appender.ConsoleCapitalized.layout=org.apache.log4j.PatternLayout +log4j.appender.ConsoleCapitalized.layout.ConversionPattern=%d{ISO8601} [%t][%c] %-5p: %m%n + +log4j.appender.ConsoleJavaStyle=org.apache.log4j.ConsoleAppender +log4j.appender.ConsoleJavaStyle.Encoding=ISO-8859-1 +log4j.appender.ConsoleJavaStyle.Follow=true +log4j.appender.ConsoleJavaStyle.ImmediateFlush=false +log4j.appender.ConsoleJavaStyle.Target=System.err +log4j.appender.ConsoleJavaStyle.layout=org.apache.log4j.PatternLayout +log4j.appender.ConsoleJavaStyle.layout.ConversionPattern=%d{ISO8601} [%t][%c] %-5p: %m%n diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-capitalization.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-capitalization.xml new file mode 100644 index 00000000000..c353fe09d8a --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-capitalization.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.properties index 6793eb26ec2..ba1197c0147 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,6 +28,7 @@ log4j.rootLogger=TRACE, Console # log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.Follow=true log4j.appender.Console.Target=System.err log4j.appender.Console.layout=org.apache.log4j.EnhancedPatternLayout log4j.appender.Console.layout.ConversionPattern=%d{ISO8601} [%t][%c] %-5p %X %x: %m%n diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.xml new file mode 100644 index 00000000000..3e1b2a0ce36 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-HtmlLayout.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-HtmlLayout.properties index 216a12ebf58..ac94b1df1c9 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-HtmlLayout.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-HtmlLayout.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,6 +28,7 @@ log4j.rootLogger=TRACE, Console # log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.Follow=true log4j.appender.Console.Target=System.err log4j.appender.Console.layout=org.apache.log4j.HTMLLayout log4j.appender.Console.layout.Title=Headline diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-HtmlLayout.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-HtmlLayout.xml new file mode 100644 index 00000000000..f03131da8b6 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-HtmlLayout.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-PatternLayout.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-PatternLayout.properties index 810a494eb6a..679afd3fe44 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-PatternLayout.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-PatternLayout.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,6 +28,7 @@ log4j.rootLogger=TRACE, Console # log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.Follow=true log4j.appender.Console.Target=System.err log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d{ISO8601} [%t][%c] %-5p: %m%n diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-PatternLayout.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-PatternLayout.xml new file mode 100644 index 00000000000..65ae57fe14e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-PatternLayout.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-SimpleLayout.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-SimpleLayout.properties index 5a8ac4eb9a0..42f29d3956b 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-SimpleLayout.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-SimpleLayout.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,6 +28,7 @@ log4j.rootLogger=TRACE, Console # log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.Follow=true log4j.appender.Console.Target=System.err log4j.appender.Console.layout=org.apache.log4j.SimpleLayout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-SimpleLayout.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-SimpleLayout.xml new file mode 100644 index 00000000000..30263bf315a --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-SimpleLayout.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-TTCCLayout.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-TTCCLayout.properties index 80d38c2a53e..01cc0b0c010 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-TTCCLayout.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-TTCCLayout.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,9 +28,13 @@ log4j.rootLogger=TRACE, Console # log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.Follow=true log4j.appender.Console.Target=System.err log4j.appender.Console.layout=org.apache.log4j.TTCCLayout -log4j.appender.Console.layout.ThreadPrinting=true +log4j.appender.Console.layout.ThreadPrinting=false log4j.appender.Console.layout.CategoryPrefixing=false +log4j.appender.Console.layout.ContextPrinting=false +log4j.appender.Console.layout.DateFormat=ISO8601 +log4j.appender.Console.layout.TimeZone=CET log4j.logger.com.example.foo = DEBUG diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-TTCCLayout.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-TTCCLayout.xml new file mode 100644 index 00000000000..b0900dabbd3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-TTCCLayout.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-XmlLayout.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-XmlLayout.properties index c8190ecd8bd..f8a22af99c5 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-XmlLayout.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-console-XmlLayout.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,6 +28,7 @@ log4j.rootLogger=TRACE, Console # log4j.appender.Console=org.apache.log4j.ConsoleAppender +log4j.appender.Console.Follow=true log4j.appender.Console.Target=System.err log4j.appender.Console.layout=org.apache.log4j.xml.XMLLayout log4j.appender.Console.layout.LocationInfo=true diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-defaultValues.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-defaultValues.properties new file mode 100644 index 00000000000..15627a4d4a3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-defaultValues.properties @@ -0,0 +1,82 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Configuration file with minimal number of non-default values +# +log4j.rootLogger = TRACE, HTMLLayout, PatternLayout, TTCCLayout, XMLLayout,\ +ConsoleAppender, DailyRollingFileAppender, FileAppender, RollingFileAppender + +############################################################################## +# +# HTMLLayout +# +log4j.appender.HTMLLayout=org.apache.log4j.ConsoleAppender +log4j.appender.HTMLLayout.layout=org.apache.log4j.HTMLLayout + +############################################################################## +# +# PatternLayout +# +log4j.appender.PatternLayout=org.apache.log4j.ConsoleAppender +log4j.appender.PatternLayout.layout=org.apache.log4j.PatternLayout + +############################################################################## +# +# TTCCLayout +# +log4j.appender.TTCCLayout=org.apache.log4j.ConsoleAppender +log4j.appender.TTCCLayout.layout=org.apache.log4j.TTCCLayout + +############################################################################## +# +# XMLLayout +# +log4j.appender.XMLLayout=org.apache.log4j.ConsoleAppender +log4j.appender.XMLLayout.layout=org.apache.log4j.xml.XMLLayout + +############################################################################## +# +# ConsoleAppender +# +log4j.appender.ConsoleAppender=org.apache.log4j.ConsoleAppender +log4j.appender.ConsoleAppender.layout=org.apache.log4j.SimpleLayout + +############################################################################## +# +# DailyRollingFileAppender +# +log4j.appender.DailyRollingFileAppender=org.apache.log4j.DailyRollingFileAppender +log4j.appender.DailyRollingFileAppender.File=target/dailyRollingFileAppender +log4j.appender.DailyRollingFileAppender.layout=org.apache.log4j.SimpleLayout + +############################################################################## +# +# FileAppender +# +log4j.appender.FileAppender=org.apache.log4j.FileAppender +log4j.appender.FileAppender.File=target/fileAppender +log4j.appender.FileAppender.layout=org.apache.log4j.SimpleLayout + +############################################################################## +# +# RollingFileAppender +# +log4j.appender.RollingFileAppender=org.apache.log4j.RollingFileAppender +log4j.appender.RollingFileAppender.File=target/rollingFileAppender +log4j.appender.RollingFileAppender.layout=org.apache.log4j.SimpleLayout diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-defaultValues.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-defaultValues.xml new file mode 100644 index 00000000000..7d5d3576bab --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-defaultValues.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-file-SimpleLayout.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-file-SimpleLayout.properties index 4d3ec0d6af3..d42e654e795 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-file-SimpleLayout.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-file-SimpleLayout.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,7 +28,11 @@ log4j.rootLogger=TRACE, File # log4j.appender.File=org.apache.log4j.FileAppender +log4j.appender.File.Append=false +log4j.appender.File.BufferedIO=true +log4j.appender.File.BufferSize=1000 log4j.appender.File.File=target/mylog.txt +log4j.appender.File.ImmediateFlush=false log4j.appender.File.layout=org.apache.log4j.SimpleLayout log4j.logger.com.example.foo = DEBUG diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-file-SimpleLayout.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-file-SimpleLayout.xml new file mode 100644 index 00000000000..fdccce77acc --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-file-SimpleLayout.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-global-threshold.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-global-threshold.properties new file mode 100644 index 00000000000..f76d396f7f9 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-global-threshold.properties @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold = info +log4j.appender.LIST = org.apache.log4j.ListAppender +log4j.rootLogger = debug, LIST diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-global-threshold.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-global-threshold.xml new file mode 100644 index 00000000000..fb969cad411 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-global-threshold.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-1.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-1.properties index a82c4c37e9b..167cd86eb42 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-1.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-1.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -11,4 +28,10 @@ log4j.rootLogger=TRACE, RFA # Rolling File Appender # log4j.appender.RFA=org.apache.log4j.RollingFileAppender +log4j.appender.RFA.Append=false +log4j.appender.RFA.BufferedIO=true +log4j.appender.RFA.BufferSize=1000 log4j.appender.RFA.File=${java.io.tmpdir}/${hadoop.log.file} +log4j.appender.RFA.ImmediateFlush=false +log4j.appender.RFA.MaxBackupIndex=16 +log4j.appender.RFA.MaxFileSize=20 MB diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-1.xml b/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-1.xml new file mode 100644 index 00000000000..ccfd9e748ae --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-1.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-2.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-2.properties index 9228434054e..686bc77ecd6 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-2.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-system-properties-2.properties @@ -1,3 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################### # # Log4J 1.2 Configuration. @@ -12,4 +29,10 @@ log4j.rootLogger=TRACE, RFA # Rolling File Appender # log4j.appender.RFA=org.apache.log4j.RollingFileAppender +log4j.appender.RFA.Append=false +log4j.appender.RFA.BufferedIO=true +log4j.appender.RFA.BufferSize=1000 log4j.appender.RFA.File=${hadoop.log.dir}/${hadoop.log.file} +log4j.appender.RFA.ImmediateFlush=false +log4j.appender.RFA.MaxBackupIndex=16 +log4j.appender.RFA.MaxBackupSize=20 MB diff --git a/log4j-1.2-api/src/test/resources/config-1.2/log4j-untrimmed.properties b/log4j-1.2-api/src/test/resources/config-1.2/log4j-untrimmed.properties new file mode 100644 index 00000000000..66b2fbaa5de --- /dev/null +++ b/log4j-1.2-api/src/test/resources/config-1.2/log4j-untrimmed.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +### +# Warning: this file contains INTENTIONAL trailing spaces on all properties +# + +log4j.threshold = INFO + +log4j.appender.Console = org.apache.log4j.ConsoleAppender +log4j.appender.Console.layout = org.apache.log4j.SimpleLayout +log4j.appender.Console.filter.1 = org.apache.log4j.varia.DenyAllFilter + +log4j.rootLogger = DEBUG , Console \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/R/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/R/log4j.properties index cce8d9152d3..6b286c954a3 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/R/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/R/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties index e8da774f7ca..60a759aa691 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties index e73978908b6..42d49093d67 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties index fb9d9851cb4..db816d7ba64 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties index 1e3f163f95c..aaebccb88b2 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties index fd51f8faf56..49f20f081c2 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties index 3706a6e3613..636e5562e58 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties index 75e3b53a093..fe58d0ad132 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties index fd51f8faf56..49f20f081c2 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties index 4f5ea7bafe4..b0e9b37f04f 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties index 3706a6e3613..636e5562e58 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties index 3706a6e3613..636e5562e58 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties index 744c456cb29..debf8f6caa7 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties index fd51f8faf56..49f20f081c2 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties index 7665bd5e7c0..bc5933f8d81 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties index 3706a6e3613..636e5562e58 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties index 33b9ecf1e28..3b058c8599b 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties index fea3404769d..3f91a2b3734 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties index fd51f8faf56..49f20f081c2 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties b/log4j-1.2-api/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties index d13454d5ae5..d1632ee69e1 100644 --- a/log4j-1.2-api/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties +++ b/log4j-1.2-api/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties @@ -6,7 +6,7 @@ # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, diff --git a/log4j-1.2-api/src/test/resources/log-RouteWithMDC.xml b/log4j-1.2-api/src/test/resources/log-RouteWithMDC.xml index eb7e8a57ddf..17a263f878d 100644 --- a/log4j-1.2-api/src/test/resources/log-RouteWithMDC.xml +++ b/log4j-1.2-api/src/test/resources/log-RouteWithMDC.xml @@ -14,7 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - --> diff --git a/log4j-1.2-api/src/test/resources/log4j-multipleFilters.properties b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.properties new file mode 100644 index 00000000000..177d60ca3d3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.properties @@ -0,0 +1,69 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.LIST=org.apache.log4j.ListAppender +log4j.appender.LIST.Threshold=DEBUG +log4j.appender.LIST.filter.1=org.apache.log4j.config.StartsWithFilter +log4j.appender.LIST.filter.2=org.apache.log4j.varia.LevelMatchFilter +log4j.appender.LIST.filter.2.LevelToMatch=INFO +log4j.appender.LIST.filter.2.AcceptOnMatch=true +log4j.appender.LIST.filter.3=org.apache.log4j.varia.DenyAllFilter + +log4j.appender.LIST2=org.apache.logging.log4j.test.appender.ListAppender +log4j.appender.LIST2.Threshold=DEBUG +log4j.appender.LIST2.filter.1=org.apache.log4j.config.StartsWithFilter +log4j.appender.LIST2.filter.2=org.apache.log4j.varia.LevelMatchFilter +log4j.appender.LIST2.filter.2.LevelToMatch=INFO +log4j.appender.LIST2.filter.2.AcceptOnMatch=true +log4j.appender.LIST2.filter.3=org.apache.log4j.varia.DenyAllFilter + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Threshold=DEBUG +log4j.appender.CONSOLE.filter.1=org.apache.log4j.config.StartsWithFilter +log4j.appender.CONSOLE.filter.2=org.apache.log4j.varia.LevelMatchFilter +log4j.appender.CONSOLE.filter.2.LevelToMatch=INFO +log4j.appender.CONSOLE.filter.2.AcceptOnMatch=true +log4j.appender.CONSOLE.filter.3=org.apache.log4j.varia.DenyAllFilter + +log4j.appender.FILE=org.apache.log4j.FileAppender +log4j.appender.FILE.Threshold=DEBUG +log4j.appender.FILE.File=${test.tmpDir}/file-appender.log +log4j.appender.FILE.filter.1=org.apache.log4j.config.StartsWithFilter +log4j.appender.FILE.filter.2=org.apache.log4j.varia.LevelMatchFilter +log4j.appender.FILE.filter.2.LevelToMatch=INFO +log4j.appender.FILE.filter.2.AcceptOnMatch=true +log4j.appender.FILE.filter.3=org.apache.log4j.varia.DenyAllFilter + +log4j.appender.RFA=org.apache.log4j.RollingFileAppender +log4j.appender.RFA.Threshold=DEBUG +log4j.appender.RFA.File=${test.tmpDir}/rolling-file-appender.log +log4j.appender.RFA.filter.1=org.apache.log4j.config.StartsWithFilter +log4j.appender.RFA.filter.2=org.apache.log4j.varia.LevelMatchFilter +log4j.appender.RFA.filter.2.LevelToMatch=INFO +log4j.appender.RFA.filter.2.AcceptOnMatch=true +log4j.appender.RFA.filter.3=org.apache.log4j.varia.DenyAllFilter + +log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender +log4j.appender.DRFA.Threshold=DEBUG +log4j.appender.DRFA.File=${test.tmpDir}/daily-rolling-file-appender.log +log4j.appender.DRFA.filter.1=org.apache.log4j.config.StartsWithFilter +log4j.appender.DRFA.filter.2=org.apache.log4j.varia.LevelMatchFilter +log4j.appender.DRFA.filter.2.LevelToMatch=INFO +log4j.appender.DRFA.filter.2.AcceptOnMatch=true +log4j.appender.DRFA.filter.3=org.apache.log4j.varia.DenyAllFilter + +log4j.rootLogger=TRACE, LIST, LIST2, CONSOLE, FILE, RFA, DRFA \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j-multipleFilters.xml b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.xml new file mode 100644 index 00000000000..ea256e719f1 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j-multipleFilters.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j.xml b/log4j-1.2-api/src/test/resources/log4j.xml new file mode 100644 index 00000000000..b18ad75ea27 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/RFA1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/RFA1.properties new file mode 100644 index 00000000000..5cbd8a41564 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/RFA1.properties @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.RollingFileAppender +log4j.appender.testAppender.file=output/RFA-test1.log +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%m\n +log4j.appender.testAppender.maxFileSize=100 + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/defaultInit3.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/defaultInit3.properties new file mode 100644 index 00000000000..fe899d38dce --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/defaultInit3.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, D3 +log4j.appender.D3=org.apache.log4j.FileAppender +log4j.appender.D3.File=output/temp +log4j.appender.D3.Append=false +log4j.appender.D3.layout=org.apache.log4j.PatternLayout +log4j.appender.D3.layout.ConversionPattern=%d [%t] %-5p %.16c - %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/fallback1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/fallback1.properties new file mode 100644 index 00000000000..270c9960517 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/fallback1.properties @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.debug=true +log4j.appender.PRIMARY=org.apache.log4j.FileAppender +log4j.appender.PRIMARY.errorhandler=org.apache.log4j.varia.FallbackErrorHandler +log4j.appender.PRIMARY.errorhandler.root-ref=true +log4j.appender.PRIMARY.errorhandler.appender-ref=FALLBACK +log4j.appender.PRIMARY.file=/xyz/:x.log +log4j.appender.PRIMARY.append=false +log4j.appender.PRIMARY.layout=org.apache.log4j.PatternLayout +log4j.appender.PRIMARY.layout.conversionPattern=%-5p %c{2} - %m%n + +log4j.appender.FALLBACK=org.apache.log4j.FileAppender +log4j.appender.FALLBACK.File=output/temp +log4j.appender.FALLBACK.Append=false +log4j.appender.FALLBACK.layout=org.apache.log4j.PatternLayout +log4j.appender.FALLBACK.layout.ConversionPattern=FALLBACK - %c - %m%n + +log4j.rootLogger=DEBUG, PRIMARY diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/filter1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/filter1.properties new file mode 100644 index 00000000000..1a3eddbd437 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/filter1.properties @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.ROLLING=org.apache.log4j.PropertyConfiguratorTest$RollingFileAppender +log4j.appender.ROLLING.append=false +log4j.appender.ROLLING.rollingPolicy=org.apache.log4j.PropertyConfiguratorTest$FixedWindowRollingPolicy +log4j.appender.ROLLING.rollingPolicy.activeFileName=filterBase-test1.log +log4j.appender.ROLLING.rollingPolicy.fileNamePattern=filterBased-test1.%i +log4j.appender.ROLLING.rollingPolicy.minIndex=0 +log4j.appender.ROLLING.triggeringPolicy=org.apache.log4j.PropertyConfiguratorTest$FilterBasedTriggeringPolicy +log4j.appender.ROLLING.triggeringPolicy.filter=org.apache.log4j.varia.LevelRangeFilter +log4j.appender.ROLLING.triggeringPolicy.filter.levelMin=info +log4j.appender.ROLLING.layout=org.apache.log4j.PatternLayout +log4j.appender.ROLLING.layout.ConversionPattern=%m%n +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%m%n +log4j.logger.org.apache.log4j.PropertyConfiguratorTest=debug, ROLLING +log4j.additivity.org.apache.log4j.rolling.FilterBasedRollingTest=false +log4j.roolLogger=info, CONSOLE diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold1.properties new file mode 100644 index 00000000000..ce182724574 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold1.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=OFF +log4j.rootLogger=,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold2.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold2.properties new file mode 100644 index 00000000000..fb60102dddb --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold2.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=FATAL +log4j.rootLogger=,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold3.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold3.properties new file mode 100644 index 00000000000..0047e515b29 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold3.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=ERROR +log4j.rootLogger=,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold4.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold4.properties new file mode 100644 index 00000000000..e441527066e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold4.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=WARN +log4j.rootLogger=,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold5.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold5.properties new file mode 100644 index 00000000000..111ffc87f50 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold5.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=INFO +log4j.rootLogger=,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold6.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold6.properties new file mode 100644 index 00000000000..bfce3ae2419 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold6.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=DEBUG +log4j.rootLogger=,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold7.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold7.properties new file mode 100644 index 00000000000..04843e4a901 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold7.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=TRACE#org.apache.log4j.xml.XLevel +log4j.rootLogger=ALL,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold8.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold8.properties new file mode 100644 index 00000000000..b524a8e7709 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/hierarchyThreshold8.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.threshold=ALL +log4j.rootLogger=ALL,A +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.File=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%p [%t] %c{2} = %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout.mdc.1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout.mdc.1.properties new file mode 100644 index 00000000000..2f5edd37a2a --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout.mdc.1.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%-5p - %m %X%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout1.properties new file mode 100644 index 00000000000..0afcdce03f3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout1.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.file=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%-5p - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout10.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout10.properties new file mode 100644 index 00000000000..0acc4971c21 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout10.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append= false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %l: %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout11.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout11.properties new file mode 100644 index 00000000000..98b8a19674b --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout11.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%-5p [%t] %c{2}: %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout12.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout12.properties new file mode 100644 index 00000000000..eae07a67dd2 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout12.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %C.%M(%F:%L): %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout13.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout13.properties new file mode 100644 index 00000000000..687b8c42a88 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout13.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %C{3}.%M(%F:%L): %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout14.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout14.properties new file mode 100644 index 00000000000..e08ab00fc0f --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout14.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%-5p [%t] %c{1.}: %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout15.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout15.properties new file mode 100644 index 00000000000..2daac99948c --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout15.properties @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedMyPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%5p %-4# - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout16.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout16.properties new file mode 100644 index 00000000000..c97f824892c --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout16.properties @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/patternLayout16.log +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss}{GMT}Z %d{yyyy-MM-dd HH:mm:ss}{GMT-6}-0600 - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout2.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout2.properties new file mode 100644 index 00000000000..7f97accbeab --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout2.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append= false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %.16c - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout3.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout3.properties new file mode 100644 index 00000000000..b9ea02a8f10 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout3.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %.16c - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout4.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout4.properties new file mode 100644 index 00000000000..a9284ca4dbb --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout4.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{DATE} [%t] %-5p %.16c - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout5.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout5.properties new file mode 100644 index 00000000000..64da2f93fdf --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout5.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} [%t] %-5p %.16c - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout6.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout6.properties new file mode 100644 index 00000000000..982250ba6ee --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout6.properties @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{ABSOLUTE} [%t] %-5p %.16c - %m%n + + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout7.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout7.properties new file mode 100644 index 00000000000..188ddd82cfb --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout7.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%t] %-5p %.16c - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout8.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout8.properties new file mode 100644 index 00000000000..b9e07834e02 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout8.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%r [%t] %-5p %.16c - %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout9.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout9.properties new file mode 100644 index 00000000000..97b1d8152ff --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/pattern/enhancedPatternLayout9.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.EnhancedPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %.16c : %m%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout.mdc.1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout.mdc.1.properties new file mode 100644 index 00000000000..bbe1d0f912d --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout.mdc.1.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=DEBUG, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%-5p - %m %X%n + +# Prevent internal log4j DEBUG messages from polluting the output. +log4j.logger.org.apache.log4j.PropertyConfigurator=INFO +log4j.logger.org.apache.log4j.config.PropertySetter=INFO +log4j.logger.org.apache.log4j.FileAppender=INFO diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout1.properties new file mode 100644 index 00000000000..4d3be036cc6 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout1.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%-5p - %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout10.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout10.properties new file mode 100644 index 00000000000..fd6fabc1635 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout10.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File= output/temp +log4j.appender.testAppender.Append= false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %l: %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout11.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout11.properties new file mode 100644 index 00000000000..1de0dec5a67 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout11.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%-5p [%t] %c{2}: %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout12.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout12.properties new file mode 100644 index 00000000000..1f17f72da53 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout12.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %C.%M(%F:%L): %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout13.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout13.properties new file mode 100644 index 00000000000..10da01b77c9 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout13.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File= output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %C{3}.%M(%F:%L): %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout14.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout14.properties new file mode 100644 index 00000000000..56720864203 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout14.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File= output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.MyPatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%5p %-4# - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout2.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout2.properties new file mode 100644 index 00000000000..b2f79c49910 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout2.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append= false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %.16c - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout3.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout3.properties new file mode 100644 index 00000000000..98b2ac31db6 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout3.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %.16c - %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout4.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout4.properties new file mode 100644 index 00000000000..91df3c286f8 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout4.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{DATE} [%t] %-5p %.16c - %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout5.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout5.properties new file mode 100644 index 00000000000..3bcaf7b6b53 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout5.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss,SSS} [%t] %-5p %.16c - %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout6.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout6.properties new file mode 100644 index 00000000000..c1eeb6c057d --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout6.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{ABSOLUTE} [%t] %-5p %.16c - %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout7.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout7.properties new file mode 100644 index 00000000000..0af90f125d9 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout7.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%t] %-5p %.16c - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout8.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout8.properties new file mode 100644 index 00000000000..f43eca2b389 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout8.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=%r [%t] %-5p %.16c - %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout9.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout9.properties new file mode 100644 index 00000000000..045efcf50ec --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/patternLayout9.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=TRACE, testAppender +log4j.appender.testAppender=org.apache.log4j.FileAppender +log4j.appender.testAppender.File=output/temp +log4j.appender.testAppender.Append=false +log4j.appender.testAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.testAppender.layout.ConversionPattern=[%t] %-5p %.16c : %m%n \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer1.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer1.properties new file mode 100644 index 00000000000..0c99b0be151 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer1.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x [%t] %c %m%n + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer2.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer2.properties new file mode 100644 index 00000000000..077e8ec6f8c --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer2.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x [%t] %C (%F:%L) %m%n + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer3.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer3.properties new file mode 100644 index 00000000000..5476e85d287 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer3.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.Logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.Logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x [%t] %C (%F:%L) %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer4.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer4.properties new file mode 100644 index 00000000000..66a408dc36f --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer4.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.Logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.Logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x %X{key1}%X{key4} [%t] %c{1} - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer5.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer5.properties new file mode 100644 index 00000000000..aeea42cc91d --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer5.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.Logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.Logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x %X{key1}%X{key5} [%t] %c{1} - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer6.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer6.properties new file mode 100644 index 00000000000..e6966a2589f --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer6.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.Logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.Logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x %X{hostID} %X{key6} [%t] %c{1} - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer7.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer7.properties new file mode 100644 index 00000000000..c43d54ec6af --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer7.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.Logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.Logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x %X{hostID} %X{key7} [%t] %c{1} - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer8.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer8.properties new file mode 100644 index 00000000000..9b81ddb688e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/socketServer8.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=TRACE, A +log4j.Logger.org.apache.log4j.test.ShortSocketServer=WARN +log4j.Logger.org.apache.log4j.net.SocketNode=WARN +log4j.appender.A=org.apache.log4j.FileAppender +log4j.appender.A.file=output/temp +log4j.appender.A.Append=false +log4j.appender.A.layout=org.apache.log4j.PatternLayout +log4j.appender.A.layout.ConversionPattern=%5p %x %X{hostID} %X{key8} [%t] %c{1} - %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4.xml new file mode 100644 index 00000000000..b4535e2d0d8 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4.xml @@ -0,0 +1,44 @@ + + + +]> + + + &a1; + &a2; + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4_A1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4_A1.xml new file mode 100644 index 00000000000..9b945e60aab --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4_A1.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4_A2.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4_A2.xml new file mode 100644 index 00000000000..e5e26c02485 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTest4_A2.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTestCase1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTestCase1.xml new file mode 100644 index 00000000000..90917d1c4cd --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/DOMTestCase1.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/SocketAppenderTestConfig.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/SocketAppenderTestConfig.xml new file mode 100644 index 00000000000..8588e7f692b --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/SocketAppenderTestConfig.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/categoryfactory1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/categoryfactory1.xml new file mode 100644 index 00000000000..eba5a33181e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/categoryfactory1.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/categoryfactory2.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/categoryfactory2.xml new file mode 100644 index 00000000000..9f48b1ee96b --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/categoryfactory2.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel1.xml new file mode 100644 index 00000000000..5c798d2229c --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel1.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel2.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel2.xml new file mode 100644 index 00000000000..88735808cdb --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel2.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel3.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel3.xml new file mode 100644 index 00000000000..f8c014ad90b --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel3.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel4.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel4.xml new file mode 100644 index 00000000000..e1bd0ded9b3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLevel4.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger1.xml new file mode 100644 index 00000000000..7ccb8bd1ccc --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger1.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger2.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger2.xml new file mode 100644 index 00000000000..7065aace82c --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger2.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger3.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger3.xml new file mode 100644 index 00000000000..02c7e9f120b --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/customLogger3.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/defaultInit.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/defaultInit.xml new file mode 100644 index 00000000000..62da4c17593 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/defaultInit.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/fallback1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/fallback1.xml new file mode 100644 index 00000000000..dc542dffe59 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/fallback1.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/loggerfactory1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/loggerfactory1.xml new file mode 100644 index 00000000000..467245002af --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/loggerfactory1.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/smtpAppender1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/smtpAppender1.xml new file mode 100644 index 00000000000..1df1a91466d --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/smtpAppender1.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/testReset.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/testReset.xml new file mode 100644 index 00000000000..185209e64b2 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/testReset.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/throwableRenderer1.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/throwableRenderer1.xml new file mode 100644 index 00000000000..2496870e0a2 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/input/xml/throwableRenderer1.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_en_US.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_en_US.properties new file mode 100644 index 00000000000..d5775c616e8 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_en_US.properties @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +test=This is the English, US test. +hello_world=Hello world. +msg1=This is test number {0} with string argument {1}. diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_fr.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_fr.properties new file mode 100644 index 00000000000..9beb5b74193 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_fr.properties @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +test=Ceci est le test en francais pour la France. +hello_world=Bonjour la France. +msg1=Ceci est le test numero {0} contenant l''argument {1}. diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_fr_CH.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_fr_CH.properties new file mode 100644 index 00000000000..ef9d824e09a --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/L7D_fr_CH.properties @@ -0,0 +1,19 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +test=Ceci est le test en francais pour la p'tite Suisse. +hello world=Salut le monde. diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/TestLogSFPatterns.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/TestLogSFPatterns.properties new file mode 100644 index 00000000000..854127a5f7a --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/TestLogSFPatterns.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Iteration0=Iteration {} +Hello1=Hello, World +Malformed=Hello, {. +Hello2=Hello, {}World +Hello3=Hello, {} +Hello4={}, {}. +Hello5={}{} {}. +Hello6={}{} {}{} diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/TestLogMFPatterns.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/TestLogMFPatterns.properties new file mode 100644 index 00000000000..00c3d71833f --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/TestLogMFPatterns.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Iteration0=Iteration {0} +Hello1=Hello, World +Malformed=Hello, {. +Hello2=Hello, {0}World +Hello3=Hello, {0} +Hello4={1}, {0}. +Hello5={1}{2} {0}. +Hello6={1}{2} {0}{3} diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/TestLogSFPatterns.properties b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/TestLogSFPatterns.properties new file mode 100644 index 00000000000..854127a5f7a --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/TestLogSFPatterns.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Iteration0=Iteration {} +Hello1=Hello, World +Malformed=Hello, {. +Hello2=Hello, {}World +Hello3=Hello, {} +Hello4={}, {}. +Hello5={}{} {}. +Hello6={}{} {}{} diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/map.log b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/map.log new file mode 100644 index 00000000000..3ce933f832d --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/map.log @@ -0,0 +1,3 @@ +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2: Message 0 +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World {p1=Hello, p2=World, x1=Mundo} +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World Message 1 diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/map.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/map.xml new file mode 100644 index 00000000000..7cb60b7c850 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/map.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/property.log b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/property.log new file mode 100644 index 00000000000..9aa2c499137 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/property.log @@ -0,0 +1,2 @@ +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World Message 0 +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hola p2:World Message 1 diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/property.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/property.xml new file mode 100644 index 00000000000..13a04f8e2cf --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/property.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/reflection.log b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/reflection.log new file mode 100644 index 00000000000..da0b52f2dc4 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/reflection.log @@ -0,0 +1,3 @@ +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2: Message 0 +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2:Hello I am bean. +INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hola p2:Hello Welcome to The Hub diff --git a/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/reflection.xml b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/reflection.xml new file mode 100644 index 00000000000..643850b2ad9 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-1.2.17/resources/org/apache/log4j/rewrite/reflection.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-appenders-custom-1.properties b/log4j-1.2-api/src/test/resources/log4j1-appenders-custom-1.properties new file mode 100644 index 00000000000..05c7b652144 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-appenders-custom-1.properties @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Target=System.out +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +# +log4j.appender.A1=org.apache.log4j.CustomNoopAppender +log4j.appender.A1.booleanA=true +log4j.appender.A1.intA=1 +log4j.appender.A1.stringA=A +# +log4j.appender.A2=org.apache.log4j.CustomFileAppender +log4j.appender.A2.booleanA=true +log4j.appender.A2.intA=1 +log4j.appender.A2.stringA=A +log4j.appender.A2.File=target/temp.A2 +log4j.appender.A2.Append=false +log4j.appender.A2.layout=org.apache.log4j.TTCCLayout +log4j.appender.A2.layout.DateFormat=ISO8601 +# +log4j.logger.org.apache.log4j.xml=trace, A1 +log4j.rootLogger=trace, A1, A2 diff --git a/log4j-1.2-api/src/test/resources/log4j1-appenders-custom-2.properties b/log4j-1.2-api/src/test/resources/log4j1-appenders-custom-2.properties new file mode 100644 index 00000000000..6daf3fec6e4 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-appenders-custom-2.properties @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Target=System.out +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +# +log4j.appender.A1=org.apache.log4j.CustomNoopAppender +log4j.appender.A1.booleanA=false +log4j.appender.A1.intA=2 +log4j.appender.A1.stringA=B +# +log4j.appender.A2=org.apache.log4j.CustomFileAppender +log4j.appender.A2.booleanA=false +log4j.appender.A2.intA=2 +log4j.appender.A2.stringA=B +log4j.appender.A2.File=target/temp.A2 +log4j.appender.A2.Append=false +log4j.appender.A2.layout=org.apache.log4j.TTCCLayout +log4j.appender.A2.layout.DateFormat=ISO8601 +# +log4j.logger.org.apache.log4j.xml=trace, A1 +log4j.rootLogger=trace, A1, A2 diff --git a/log4j-1.2-api/src/test/resources/log4j1-async.properties b/log4j-1.2-api/src/test/resources/log4j1-async.properties new file mode 100644 index 00000000000..533346669b8 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-async.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.list=org.apache.log4j.ListAppender +log4j.appender.list.layout=org.apache.log4j.PatternLayout +log4j.appender.list.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +log4j.appender.async=org.apache.log4j.AsyncAppender +log4j.appender.async.appender-ref=list +log4j.rootLogger=trace, async diff --git a/log4j-1.2-api/src/test/resources/log4j1-async.xml b/log4j-1.2-api/src/test/resources/log4j1-async.xml new file mode 100644 index 00000000000..bdc8528a3a3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-async.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-file-1.properties b/log4j-1.2-api/src/test/resources/log4j1-file-1.properties new file mode 100644 index 00000000000..96822ec16fe --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-file-1.properties @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Target=System.out +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +# +log4j.appender.A1=org.apache.log4j.FileAppender +log4j.appender.A1.File=target/temp.A1 +log4j.appender.A1.Append=false +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-5p %c{2} - %m%n +# +log4j.appender.A2=org.apache.log4j.FileAppender +log4j.appender.A2.File=target/temp.A2 +log4j.appender.A2.Append=false +log4j.appender.A2.layout=org.apache.log4j.TTCCLayout +log4j.appender.A2.layout.DateFormat=ISO8601 +# +log4j.logger.org.apache.log4j.xml=trace, A1 +log4j.rootLogger=trace, A1, A2 diff --git a/log4j-1.2-api/src/test/resources/log4j1-file-2.properties b/log4j-1.2-api/src/test/resources/log4j1-file-2.properties new file mode 100644 index 00000000000..106df246bd7 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-file-2.properties @@ -0,0 +1,37 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This is the "same" as log4j1-file-1.properties but with some appender properties changes to test reconfiguration. + +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Target=System.out +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +# +log4j.appender.A1=org.apache.log4j.FileAppender +log4j.appender.A1.File=target/temp.A1 +log4j.appender.A1.Append=true +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-5p %c{2} - %m%n +# +log4j.appender.A2=org.apache.log4j.FileAppender +log4j.appender.A2.File=target/temp.A2 +log4j.appender.A2.Append=true +log4j.appender.A2.layout=org.apache.log4j.TTCCLayout +log4j.appender.A2.layout.DateFormat=ISO8601 +# +log4j.logger.org.apache.log4j.xml=trace, A1 +log4j.rootLogger=trace, A1, A2 diff --git a/log4j-1.2-api/src/test/resources/log4j1-file.xml b/log4j-1.2-api/src/test/resources/log4j1-file.xml new file mode 100644 index 00000000000..cad9c250eb7 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-file.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-list.properties b/log4j-1.2-api/src/test/resources/log4j1-list.properties new file mode 100644 index 00000000000..d903d02578e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-list.properties @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.appender.list=org.apache.log4j.ListAppender +log4j.appender.list.layout=org.apache.log4j.PatternLayout +log4j.appender.list.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +log4j.appender.events=org.apache.log4j.ListAppender +log4j.rootLogger=trace, list, events diff --git a/log4j-1.2-api/src/test/resources/log4j1-list.xml b/log4j-1.2-api/src/test/resources/log4j1-list.xml new file mode 100644 index 00000000000..b18ad75ea27 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-list.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-mapRewrite.xml b/log4j-1.2-api/src/test/resources/log4j1-mapRewrite.xml new file mode 100644 index 00000000000..8559058d2b3 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-mapRewrite.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-rewrite.xml b/log4j-1.2-api/src/test/resources/log4j1-rewrite.xml new file mode 100644 index 00000000000..aefb3d0171f --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-rewrite.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-rolling-properties.properties b/log4j-1.2-api/src/test/resources/log4j1-rolling-properties.properties new file mode 100644 index 00000000000..2066bbdd1f4 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-rolling-properties.properties @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.debug=true + +# Properties for substitution +somelogfile=somefile.log +maxfilesize=256MB +maxbackupindex=20 + +log4j.rootLogger=TRACE, RFA + +# Appender configuration with variables +log4j.appender.RFA=org.apache.log4j.RollingFileAppender +log4j.appender.RFA.File=${test.directory}/${somelogfile} +log4j.appender.RFA.MaxFileSize=${maxfilesize} +log4j.appender.RFA.MaxBackupIndex=${maxbackupindex} +log4j.appender.RFA.layout=org.apache.log4j.PatternLayout +log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n diff --git a/log4j-1.2-api/src/test/resources/log4j1-rolling-properties.xml b/log4j-1.2-api/src/test/resources/log4j1-rolling-properties.xml new file mode 100644 index 00000000000..f5b08aa9c99 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-rolling-properties.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-socket-xml-layout.properties b/log4j-1.2-api/src/test/resources/log4j1-socket-xml-layout.properties new file mode 100644 index 00000000000..35ceb970baf --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-socket-xml-layout.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=DEBUG,socket +log4j.appender.socket=org.apache.log4j.net.SocketAppender +log4j.appender.socket.remoteHost=localhost +log4j.appender.socket.port=9999 +log4j.appender.socket.reconnectionDelay=100 +log4j.appender.socket.layout=org.apache.log4j.xml.XMLLayout +log4j.appender.socket.Threshold=DEBUG diff --git a/log4j-1.2-api/src/test/resources/log4j1-socket.properties b/log4j-1.2-api/src/test/resources/log4j1-socket.properties new file mode 100644 index 00000000000..3072b46b17a --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-socket.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=DEBUG,socket +log4j.appender.socket=org.apache.log4j.net.SocketAppender +log4j.appender.socket.remoteHost=localhost +log4j.appender.socket.port=9999 +log4j.appender.socket.reconnectionDelay=100 +log4j.appender.socket.layout=org.apache.log4j.PatternLayout +log4j.appender.socket.layout.conversionPattern=Main[%pid] :%t: %c %-4p - %m\n +log4j.appender.socket.Threshold=DEBUG diff --git a/log4j-1.2-api/src/test/resources/log4j1-socket.xml b/log4j-1.2-api/src/test/resources/log4j1-socket.xml new file mode 100644 index 00000000000..eca3a0fe2f8 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-socket.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-default.properties b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-default.properties new file mode 100644 index 00000000000..2bfda78e8d5 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-default.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=DEBUG,syslog +log4j.appender.syslog=org.apache.log4j.net.SyslogAppender +log4j.appender.syslog.Threshold=DEBUG +log4j.appender.syslog.syslogHost=localhost:9999 +log4j.appender.syslog.header=true +log4j.appender.syslog.Facility=LOCAL3 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.conversionPattern=Main[%pid] :%t: %c %-4p - %m\n diff --git a/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-tcp.properties b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-tcp.properties new file mode 100644 index 00000000000..9a95669b823 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-tcp.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=DEBUG,syslog +log4j.appender.syslog=org.apache.log4j.net.SyslogAppender +log4j.appender.syslog.Threshold=DEBUG +log4j.appender.syslog.syslogHost=localhost:9999 +log4j.appender.syslog.protocol=TCP +log4j.appender.syslog.header=true +log4j.appender.syslog.Facility=LOCAL3 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.conversionPattern=Main[%pid] :%t: %c %-4p - %m\n diff --git a/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-tcp.xml b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-tcp.xml new file mode 100644 index 00000000000..47c3768709d --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-tcp.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-udp.properties b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-udp.properties new file mode 100644 index 00000000000..b7ab6a7937e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-udp.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootLogger=DEBUG,syslog +log4j.appender.syslog=org.apache.log4j.net.SyslogAppender +log4j.appender.syslog.Threshold=DEBUG +log4j.appender.syslog.syslogHost=localhost:9999 +log4j.appender.syslog.protocol=UDP +log4j.appender.syslog.header=true +log4j.appender.syslog.Facility=LOCAL3 +log4j.appender.syslog.layout=org.apache.log4j.PatternLayout +log4j.appender.syslog.layout.conversionPattern=Main[%pid] :%t: %c %-4p - %m\n diff --git a/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-udp.xml b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-udp.xml new file mode 100644 index 00000000000..279ec94cb9e --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-syslog-protocol-udp.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-1.2-api/src/test/resources/log4j1-syslog.xml b/log4j-1.2-api/src/test/resources/log4j1-syslog.xml new file mode 100644 index 00000000000..4fd96a8b180 --- /dev/null +++ b/log4j-1.2-api/src/test/resources/log4j1-syslog.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-1.2-api/src/test/resources/log4j2-config.xml b/log4j-1.2-api/src/test/resources/log4j2-config.xml index 2427af82cbe..11040d3aa39 100644 --- a/log4j-1.2-api/src/test/resources/log4j2-config.xml +++ b/log4j-1.2-api/src/test/resources/log4j2-config.xml @@ -14,7 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - --> diff --git a/log4j-1.2-api/src/test/resources/logWithMDC.xml b/log4j-1.2-api/src/test/resources/logWithMDC.xml index 1fe848295fe..97f3b86ed75 100644 --- a/log4j-1.2-api/src/test/resources/logWithMDC.xml +++ b/log4j-1.2-api/src/test/resources/logWithMDC.xml @@ -14,7 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - --> @@ -37,4 +36,4 @@ - \ No newline at end of file + diff --git a/log4j-api-java9/pom.xml b/log4j-api-java9/pom.xml deleted file mode 100644 index 183ad14dd3c..00000000000 --- a/log4j-api-java9/pom.xml +++ /dev/null @@ -1,153 +0,0 @@ - - - - 4.0.0 - - org.apache.logging.log4j - log4j - 2.10.1-SNAPSHOT - ../ - - log4j-api-java9 - pom - Apache Log4j API Java 9 support - The Apache Log4j API (Java 9) - - ${basedir}/.. - API Documentation - /api - - - - junit - junit - test - - - org.apache.maven - maven-core - test - - - - - - org.apache.maven.plugins - maven-toolchains-plugin - 1.1 - - - - toolchain - - - - - - - 9 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - default-compile - compile - - compile - - - - default-test-compile - test-compile - - testCompile - - - - - 9 - 9 - 9 - none - - - - org.apache.maven.plugins - maven-surefire-plugin - - 2.13 - - - test - test - - test - - - - - - true - - 2C - true - - **/Test*.java - **/*Test.java - - - **/*FuncTest.java - - - - - maven-assembly-plugin - - - zip - package - - single - - - log4j-api-java9-${project.version} - false - - src/assembly/java9.xml - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - ${deploy.plugin.version} - - true - - - - - - diff --git a/log4j-api-java9/src/assembly/java9.xml b/log4j-api-java9/src/assembly/java9.xml deleted file mode 100644 index 3e595612db4..00000000000 --- a/log4j-api-java9/src/assembly/java9.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - src - - zip - - / - - - ${project.build.outputDirectory} - /classes/META-INF/versions/9 - - **/*.class - - - module-info.class - **/Dummy.class - **/spi/Provider.class - **/util/PropertySource.class - **/message/ThreadDumpMessage.class - **/message/ThreadDumpMessage$ThreadInfoFactory.class - - - - ${project.build.outputDirectory} - /classes - - module-info.class - - - - diff --git a/log4j-api-java9/src/main/java/module-info.java b/log4j-api-java9/src/main/java/module-info.java deleted file mode 100644 index 3cb22e02da6..00000000000 --- a/log4j-api-java9/src/main/java/module-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -module org.apache.logging.log4j { - exports org.apache.logging.log4j; - exports org.apache.logging.log4j.message; - exports org.apache.logging.log4j.simple; - exports org.apache.logging.log4j.spi; - exports org.apache.logging.log4j.status; - exports org.apache.logging.log4j.util; - - uses org.apache.logging.log4j.spi.Provider; - uses org.apache.logging.log4j.util.PropertySource; - uses org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory; -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/Dummy.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/Dummy.java deleted file mode 100644 index 24012e6ae3a..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/Dummy.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class Dummy { -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java deleted file mode 100644 index f94d10a9b28..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/log4j/util/PropertySource.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class PropertySource { -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/message/Dummy.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/message/Dummy.java deleted file mode 100644 index 082e36ea3e7..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/message/Dummy.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.message; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class Dummy { -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java deleted file mode 100644 index 8b1af5dd565..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.message; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class ThreadDumpMessage { - public static interface ThreadInfoFactory { - - } -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/simple/Dummy.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/simple/Dummy.java deleted file mode 100644 index c3a24e282bf..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/simple/Dummy.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.simple; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class Dummy { -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/spi/Provider.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/spi/Provider.java deleted file mode 100644 index 65b86380e9e..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/spi/Provider.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.spi; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class Provider { -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/status/Dummy.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/status/Dummy.java deleted file mode 100644 index d53dc43e753..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/status/Dummy.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.status; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class Dummy { -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java deleted file mode 100644 index e467413db25..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -public class ProcessIdUtil { - - public static final String DEFAULT_PROCESSID = "-"; - - public static String getProcessId() { - try { - return Long.toString(ProcessHandle.current().pid()); - } catch(Exception ex) { - return DEFAULT_PROCESSID; - } - } -} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/StackLocator.java b/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/StackLocator.java deleted file mode 100644 index b0661d0fe14..00000000000 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/StackLocator.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import java.util.List; -import java.util.Stack; -import java.util.stream.Collectors; - -/** - * Consider this class private. Determines the caller's class. - */ -public class StackLocator { - - private final static StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); - - private final static StackWalker stackWalker = StackWalker.getInstance(); - - private final static StackLocator INSTANCE = new StackLocator(); - - - public static StackLocator getInstance() { - return INSTANCE; - } - - private StackLocator() { - } - - public Class getCallerClass(final String fqcn) { - return getCallerClass(fqcn, ""); - } - - public Class getCallerClass(final String fqcn, final String pkg) { - return walker.walk(s -> s.dropWhile(f -> !f.getClassName().equals(fqcn)). - dropWhile(f -> f.getClassName().equals(fqcn)).dropWhile(f -> !f.getClassName().startsWith(pkg)). - findFirst()).map(StackWalker.StackFrame::getDeclaringClass).orElse(null); - } - - public Class getCallerClass(final Class anchor) { - return walker.walk(s -> s.dropWhile(f -> !f.getDeclaringClass().equals(anchor)). - dropWhile(f -> f.getDeclaringClass().equals(anchor)).findFirst()). - map(StackWalker.StackFrame::getDeclaringClass).orElse(null); - } - - public Class getCallerClass(final int depth) { - ; - return walker.walk(s -> s.skip(depth).findFirst()).map(StackWalker.StackFrame::getDeclaringClass).orElse(null); - } - - public Stack> getCurrentStackTrace() { - Stack> stack = new Stack>(); - List> classes = walker.walk(s -> s.map(f -> f.getDeclaringClass()).collect(Collectors.toList())); - stack.addAll(classes); - return stack; - } - - public StackTraceElement calcLocation(final String fqcnOfLogger) { - return stackWalker.walk( - s -> s.dropWhile(f -> !f.getClassName().equals(fqcnOfLogger)) // drop the top frames until we reach the logger - .dropWhile(f -> f.getClassName().equals(fqcnOfLogger)) // drop the logger frames - .findFirst()) - .get() - .toStackTraceElement(); - } - - public StackTraceElement getStackTraceElement(final int depth) { - return stackWalker.walk(s -> s.skip(depth).findFirst()).get().toStackTraceElement(); - } -} diff --git a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java deleted file mode 100644 index 6b5368f5992..00000000000 --- a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import org.junit.Test; -import static org.junit.Assert.assertFalse; - -public class ProcessIdUtilTest { - - @Test - public void processIdTest() throws Exception { - String processId = ProcessIdUtil.getProcessId(); - assertFalse("ProcessId is default", processId.equals(ProcessIdUtil.DEFAULT_PROCESSID)); - } -} diff --git a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/StackLocatorTest.java b/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/StackLocatorTest.java deleted file mode 100644 index 77396c92fb3..00000000000 --- a/log4j-api-java9/src/test/java/org/apache/logging/log4j/util/StackLocatorTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.ParentRunner; - -import java.util.Stack; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; - -@RunWith(BlockJUnit4ClassRunner.class) -public class StackLocatorTest { - - private static StackLocator stackLocator; - - @BeforeClass - public static void setupClass() { - - stackLocator = StackLocator.getInstance(); - } - - @Test - public void testGetCallerClass() throws Exception { - final Class expected = StackLocatorTest.class; - final Class actual = stackLocator.getCallerClass(1); - assertSame(expected, actual); - } - - @Test - public void testGetCallerClassNameViaStackTrace() throws Exception { - final Class expected = StackLocatorTest.class; - final Class actual = Class.forName(new Throwable().getStackTrace()[0].getClassName()); - assertSame(expected, actual); - } - - @Test - public void testGetCurrentStackTrace() throws Exception { - final Stack> classes = stackLocator.getCurrentStackTrace(); - final Stack> reversed = new Stack<>(); - reversed.ensureCapacity(classes.size()); - while (!classes.empty()) { - reversed.push(classes.pop()); - } - while (reversed.peek() != StackLocator.class) { - reversed.pop(); - } - reversed.pop(); // ReflectionUtil - assertSame(StackLocatorTest.class, reversed.pop()); - } - - @Test - public void testGetCallerClassViaName() throws Exception { - final Class expected = BlockJUnit4ClassRunner.class; - final Class actual = stackLocator.getCallerClass("org.junit.runners.ParentRunner"); - // if this test fails in the future, it's probably because of a JUnit upgrade; check the new stack trace and - // update this test accordingly - assertSame(expected, actual); - } - - @Test - public void testGetCallerClassViaAnchorClass() throws Exception { - final Class expected = BlockJUnit4ClassRunner.class; - final Class actual = stackLocator.getCallerClass(ParentRunner.class); - // if this test fails in the future, it's probably because of a JUnit upgrade; check the new stack trace and - // update this test accordingly - assertSame(expected, actual); - } - - @Test - public void testLocateClass() { - ClassLocator locator = new ClassLocator(); - Class clazz = locator.locateClass(); - assertNotNull("Could not locate class", clazz); - assertEquals("Incorrect class", this.getClass(), clazz); - } - - private final class Foo { - - private StackTraceElement foo() { - return new Bar().bar(); - } - - } - - private final class Bar { - - private StackTraceElement bar() { - return baz(); - } - - private StackTraceElement baz() { - return quux(); - } - - } - - private StackTraceElement quux() { - return stackLocator.calcLocation("org.apache.logging.log4j.util.StackLocatorTest$Bar"); - } - - @Test - public void testCalcLocation() { - /* - * We are setting up a stack trace that looks like: - * - org.apache.logging.log4j.util.StackLocatorTest#quux(line:118) - * - org.apache.logging.log4j.util.StackLocatorTest$Bar#baz(line:112) - * - org.apache.logging.log4j.util.StackLocatorTest$Bar#bar(line:108) - * - org.apache.logging.log4j.util.StackLocatorTest$Foo(line:100) - * - * We are pretending that org.apache.logging.log4j.util.StackLocatorTest$Bar is the logging class, and - * org.apache.logging.log4j.util.StackLocatorTest$Foo is where the log line emanated. - */ - final StackTraceElement element = new Foo().foo(); - assertEquals("org.apache.logging.log4j.util.StackLocatorTest$Foo", element.getClassName()); - assertEquals(100, element.getLineNumber()); - } - - class ClassLocator { - - public Class locateClass() { - return stackLocator.getCallerClass(ClassLocator.class); - } - } - -} diff --git a/log4j-api-test/pom.xml b/log4j-api-test/pom.xml new file mode 100644 index 00000000000..da6ffa1860a --- /dev/null +++ b/log4j-api-test/pom.xml @@ -0,0 +1,338 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j + 3.0.0-SNAPSHOT + + log4j-api-test + jar + Apache Log4j API Tests + The Apache Log4j API Test + + ${basedir}/.. + API Documentation + /api + true + + + + org.apache.logging.log4j + log4j-api + + + org.assertj + assertj-core + + + org.apache.commons + commons-lang3 + + + org.hamcrest + hamcrest + + + + com.fasterxml.jackson.core + jackson-core + + + + com.fasterxml.jackson.core + jackson-databind + + + junit + junit + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.jupiter + junit-jupiter-params + + + org.junit-pioneer + junit-pioneer + + + org.junit.platform + junit-platform-commons + + + org.junit.platform + junit-platform-launcher + + + org.opentest4j + opentest4j + + + uk.org.webcompere + system-stubs-core + test + + + uk.org.webcompere + system-stubs-jupiter + test + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + add-source + + + + ${project.basedir}/src/main/module + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.apache.logging.log4j.* + + sun.reflect;resolution:=optional, + * + + org.apache.logging.log4j.util.Activator + <_fixupmessages>"Classes found in the wrong directory";is:=warning + + + + + + org.apache.maven.plugins + maven-clean-plugin + + + remove-module-info + process-classes + + clean + + + true + + + ${project.build.outputDirectory} + + module-info.class + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + + + + + compile-module-info + + compile + + prepare-package + + + ${project.basedir}/src/main/module + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + + false + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + true + true + random + + dynamic + 1 + true + same_thread + concurrent + + true + org.junit.jupiter.api.ClassOrderer$Random + + false + + + + + + + + org.apache.maven.plugins + maven-changes-plugin + + + + changes-report + + + + + %URL%/%ISSUE% + true + API + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + ${log4jParentDir}/checkstyle.xml + ${log4jParentDir}/checkstyle-suppressions.xml + false + basedir=${basedir} + licensedir=${log4jParentDir}/checkstyle-header.txt + + + + org.apache.maven.plugins + maven-javadoc-plugin + + <p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> + Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, + and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p> + + false + false + true + + https://www.osgi.org/javadoc/r4v43/core/ + + 8 + + + + non-aggregate + + javadoc + + + + + + org.apache.maven.plugins + maven-jxr-plugin + + + non-aggregate + + jxr + + + + aggregate + + aggregate + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + ${maven.compiler.target} + + + + com.github.spotbugs + spotbugs-maven-plugin + + + + + + parallel-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + + junit.jupiter.execution.parallel.enabled = true + junit.jupiter.execution.parallel.mode.default = concurrent + + + balanced + 1C + true + + + + + + + diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/AbstractSerializationTest.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/AbstractSerializationTest.java new file mode 100644 index 00000000000..06b38775cb9 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/AbstractSerializationTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test; + +import static org.apache.logging.log4j.test.SerializableMatchers.serializesRoundTrip; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.Serializable; +import java.util.stream.Stream; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Subclasses tests {@link Serializable} objects. + */ +@TestInstance(Lifecycle.PER_CLASS) +public abstract class AbstractSerializationTest { + + protected abstract Stream data(); + + @ParameterizedTest + @MethodSource("data") + public void testSerializationRoundtripEquals(Serializable serializable) { + assertThat(serializable, serializesRoundTrip(serializable)); + } + + @ParameterizedTest + @MethodSource("data") + public void testSerializationRoundtripNoException(Serializable serializable) { + assertThat(serializable, serializesRoundTrip()); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/SerializableMatchers.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/SerializableMatchers.java similarity index 98% rename from log4j-api/src/test/java/org/apache/logging/log4j/SerializableMatchers.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/SerializableMatchers.java index 7545b964a96..d98359d3d99 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/SerializableMatchers.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/SerializableMatchers.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.test; import java.io.Serializable; diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java similarity index 90% rename from log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java index 5f7abb7dbda..5b9c5b6ac92 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLogger.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.test; import java.io.ByteArrayOutputStream; import java.io.PrintStream; @@ -22,6 +22,9 @@ import java.util.List; import java.util.Map; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.spi.AbstractLogger; @@ -53,6 +56,12 @@ public List getEntries() { @Override public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) { + log(level, marker, fqcn, null, msg, throwable); + } + + @Override + protected void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location, + final Message message, final Throwable throwable) { final StringBuilder sb = new StringBuilder(); if (marker != null) { sb.append(marker); @@ -60,14 +69,18 @@ public void logMessage(final String fqcn, final Level level, final Marker marker sb.append(' '); sb.append(level.toString()); sb.append(' '); - sb.append(msg.getFormattedMessage()); + if (location != null) { + sb.append(location.toString()); + sb.append(' '); + } + sb.append(message.getFormattedMessage()); final Map mdc = ThreadContext.getImmutableContext(); if (mdc.size() > 0) { sb.append(' '); sb.append(mdc.toString()); sb.append(' '); } - final Object[] params = msg.getParameters(); + final Object[] params = message.getParameters(); Throwable t; if (throwable == null && params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) { t = (Throwable) params[params.length - 1]; @@ -81,7 +94,6 @@ public void logMessage(final String fqcn, final Level level, final Marker marker sb.append(baos.toString()); } list.add(sb.toString()); - //System.out.println(sb.toString()); } @Override diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContext.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContext.java similarity index 92% rename from log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContext.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContext.java index 7ca2fd69e79..906a2335b08 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContext.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContext.java @@ -14,20 +14,20 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.test; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.spi.LoggerContext; /** * */ public class TestLoggerContext implements LoggerContext { - private final Map map = new HashMap<>(); + private final Map map = new ConcurrentHashMap<>(); @Override public ExtendedLogger getLogger(final String name) { diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContextFactory.java similarity index 92% rename from log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContextFactory.java index 847fedd81c5..08185527439 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestLoggerContextFactory.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestLoggerContextFactory.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.test; import java.net.URI; @@ -43,4 +43,9 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin @Override public void removeContext(final LoggerContext context) { } + + @Override + public boolean isClassLoaderDependent() { + return false; + } } diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestProperties.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestProperties.java new file mode 100644 index 00000000000..49bb1e58775 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/TestProperties.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test; + +/** + * A container for per-test properties. + */ +public interface TestProperties { + + String getProperty(final String key); + + boolean containsProperty(final String key); + + void setProperty(final String key, final String value); + + default void setProperty(final String key, final boolean value) { + setProperty(key, value ? "true" : "false"); + } + + default void setProperty(final String key, final int value) { + setProperty(key, Integer.toString(value)); + } + + void clearProperty(final String key); +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextHolder.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextHolder.java similarity index 96% rename from log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextHolder.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextHolder.java index 4c85bde3671..35182a4524f 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextHolder.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextHolder.java @@ -15,10 +15,11 @@ * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.test; import java.util.Map; +import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.ThreadContext.ContextStack; /** diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextUtilityClass.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java similarity index 83% rename from log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextUtilityClass.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java index c6e1826fad4..d39dbdc4406 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextUtilityClass.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/ThreadContextUtilityClass.java @@ -14,19 +14,23 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.test; import java.util.Map; -import org.apache.logging.log4j.Timer; import org.apache.logging.log4j.ThreadContext; -import static org.junit.Assert.*; - +import org.apache.logging.log4j.util.Timer; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ThreadContextUtilityClass { - public static void perfTest() throws Exception { + public static void perfTest() { ThreadContext.clearMap(); final Timer complete = new Timer("ThreadContextTest"); complete.start(); @@ -92,13 +96,13 @@ public static void testGetImmutableContextReturnsImmutableMapIfNonEmpty() { ThreadContext.clearMap(); ThreadContext.put("key", "val"); final Map immutable = ThreadContext.getImmutableContext(); - immutable.put("otherkey", "otherval"); + assertThrows(UnsupportedOperationException.class, () -> immutable.put("otherkey", "otherval")); } public static void testGetImmutableContextReturnsImmutableMapIfEmpty() { ThreadContext.clearMap(); final Map immutable = ThreadContext.getImmutableContext(); - immutable.put("otherkey", "otherval"); + assertThrows(UnsupportedOperationException.class, () -> immutable.put("otherkey", "otherval")); } public static void testGetImmutableStackReturnsEmptyStackIfEmpty() { @@ -113,4 +117,8 @@ public static void testPut() { ThreadContext.put("testKey", "testValue"); assertEquals("testValue", ThreadContext.get("testKey")); } + + public static void reset() { + ThreadContext.init(); + } } diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractExternalFileCleaner.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractExternalFileCleaner.java new file mode 100644 index 00000000000..c144b356ef8 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractExternalFileCleaner.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.junit.Assert; +import org.junit.rules.ExternalResource; + +/** + * This class should not perform logging using Log4j to avoid accidentally + * loading or re-loading Log4j configurations. + */ +public abstract class AbstractExternalFileCleaner extends ExternalResource { + + protected static final String CLEANER_MARKER = "CLEANER"; + + private static final int SLEEP_RETRY_MILLIS = 200; + private final boolean cleanAfter; + private final boolean cleanBefore; + private final Set files; + private final int maxTries; + private final PrintStream printStream; + + public AbstractExternalFileCleaner(final boolean before, final boolean after, final int maxTries, + final PrintStream logger, final File... files) { + this.cleanBefore = before; + this.cleanAfter = after; + this.maxTries = maxTries; + this.files = new HashSet<>(files.length); + this.printStream = logger; + for (final File file : files) { + this.files.add(file.toPath()); + } + } + + public AbstractExternalFileCleaner(final boolean before, final boolean after, final int maxTries, + final PrintStream logger, final Path... files) { + this.cleanBefore = before; + this.cleanAfter = after; + this.maxTries = maxTries; + this.printStream = logger; + this.files = new HashSet<>(Arrays.asList(files)); + } + + public AbstractExternalFileCleaner(final boolean before, final boolean after, final int maxTries, + final PrintStream logger, final String... fileNames) { + this.cleanBefore = before; + this.cleanAfter = after; + this.maxTries = maxTries; + this.printStream = logger; + this.files = new HashSet<>(fileNames.length); + for (final String fileName : fileNames) { + this.files.add(Paths.get(fileName)); + } + } + + @Override + protected void after() { + if (cleanAfter()) { + this.clean(); + } + } + + @Override + protected void before() { + if (cleanBefore()) { + this.clean(); + } + } + + protected void clean() { + final Map failures = new HashMap<>(); + // Clean and gather failures + for (final Path path : getPaths()) { + if (Files.exists(path)) { + for (int i = 0; i < getMaxTries(); i++) { + try { + if (clean(path, i)) { + if (failures.containsKey(path)) { + failures.remove(path); + } + break; + } + } catch (final IOException e) { + println(CLEANER_MARKER + ": Caught exception cleaning: " + this); + printStackTrace(e); + // We will try again. + failures.put(path, e); + } + try { + Thread.sleep(SLEEP_RETRY_MILLIS); + } catch (final InterruptedException ignored) { + // ignore + } + } + } + } + // Fail on failures + if (failures.size() > 0) { + final StringBuilder sb = new StringBuilder(); + boolean first = true; + for (final Map.Entry failure : failures.entrySet()) { + failure.getValue().printStackTrace(); + if (!first) { + sb.append(", "); + } + sb.append(failure.getKey()).append(" failed with ").append(failure.getValue()); + first = false; + } + Assert.fail(sb.toString()); + } + } + + protected abstract boolean clean(Path path, int tryIndex) throws IOException; + + public boolean cleanAfter() { + return cleanAfter; + } + + public boolean cleanBefore() { + return cleanBefore; + } + + public int getMaxTries() { + return maxTries; + } + + public Set getPaths() { + return files; + } + + public PrintStream getPrintStream() { + return printStream; + } + + protected void printf(final String format, final Object... args) { + if (printStream != null) { + printStream.printf(format, args); + } + } + + protected void println(final String msg) { + if (printStream != null) { + printStream.println(msg); + } + } + + protected void printStackTrace(final Throwable t) { + if (printStream != null) { + t.printStackTrace(printStream); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [files=" + files + ", cleanAfter=" + cleanAfter + ", cleanBefore=" + + cleanBefore + "]"; + } + +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractFileCleaner.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractFileCleaner.java new file mode 100644 index 00000000000..351361d0b9d --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/AbstractFileCleaner.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.fail; + +abstract class AbstractFileCleaner implements BeforeEachCallback { + + private static final int MAX_TRIES = Integer.getInteger("log4j2.junit.fileCleanerMaxTries", 10); + + private static final int SLEEP_PERIOD_MILLIS = Integer.getInteger("log4j2.junit.fileCleanerSleepPeriodMillis", 200); + private static final int SLEEP_BASE_PERIOD_MILLIS = SLEEP_PERIOD_MILLIS / MAX_TRIES; + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + clean(context); + } + + private void clean(final ExtensionContext context) { + final Collection paths = getPathsForTest(context); + if (paths.isEmpty()) { + return; + } + final Map failures = new ConcurrentHashMap<>(); + for (final Path path : paths) { + if (Files.exists(path)) { + for (int i = 0, sleepMillis = SLEEP_BASE_PERIOD_MILLIS; i < MAX_TRIES; i++, sleepMillis <<= 1) { + try { + if (delete(path)) { + failures.remove(path); + break; + } + } catch (final IOException e) { + failures.put(path, e); + } + LockSupport.parkNanos(path, TimeUnit.MILLISECONDS.toNanos(sleepMillis)); + if (Thread.interrupted()) { + failures.put(path, new InterruptedIOException()); + Thread.currentThread().interrupt(); + break; + } + } + } + } + if (!failures.isEmpty()) { + final String message = failures.entrySet().stream() + .map(e -> e.getKey() + " failed with " + e.getValue()) + .collect(Collectors.joining(", ")); + fail(message); + } + } + + abstract Collection getPathsForTest(final ExtensionContext context); + + abstract boolean delete(final Path path) throws IOException; +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ClassMatchers.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ClassMatchers.java new file mode 100644 index 00000000000..d83ee72fd23 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ClassMatchers.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.util.LoaderUtil; +import org.hamcrest.CustomTypeSafeMatcher; +import org.hamcrest.Matcher; + +public final class ClassMatchers { + + private static final Matcher LOADABLE_CLASS_NAME = new CustomTypeSafeMatcher("a loadable class name") { + @Override + protected boolean matchesSafely(final String item) { + return LoaderUtil.isClassAvailable(item); + } + }; + + public static Matcher loadableClassName() { + return LOADABLE_CLASS_NAME; + } + +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanFiles.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanFiles.java new file mode 100644 index 00000000000..1782fa02cc7 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanFiles.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * A JUnit test rule to automatically delete files after a test is run. + *

+ * For example: + *

+ * + *
+ * @Rule
+ * public CleanFiles files = new CleanFiles("path/to/file.txt");
+ * 
+ *

+ * This class should not perform logging using Log4j to avoid accidentally + * loading or re-loading Log4j configurations. + *

+ * + */ +public class CleanFiles extends AbstractExternalFileCleaner { + private static final int MAX_TRIES = 10; + + public CleanFiles(final boolean before, final boolean after, final int maxTries, final File... files) { + super(before, after, maxTries, null, files); + } + + public CleanFiles(final boolean before, final boolean after, final int maxTries, final String... fileNames) { + super(before, after, maxTries, null, fileNames); + } + + public CleanFiles(final File... files) { + super(true, true, MAX_TRIES, null, files); + } + + public CleanFiles(final Path... paths) { + super(true, true, MAX_TRIES, null, paths); + } + + public CleanFiles(final String... fileNames) { + super(true, true, MAX_TRIES, null, fileNames); + } + + @Override + protected boolean clean(final Path path, final int tryIndex) throws IOException { + return Files.deleteIfExists(path); + } + +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanFolders.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanFolders.java new file mode 100644 index 00000000000..4099eec0f45 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanFolders.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * A JUnit test rule to automatically delete folders recursively before + * (optional) and after (optional) a test is run. + *

+ * This class should not perform logging using Log4j to avoid accidentally + * loading or re-loading Log4j configurations. + *

+ */ +public class CleanFolders extends AbstractExternalFileCleaner { + + public static final class DeleteAllFileVisitor extends SimpleFileVisitor { + + private final PrintStream printStream; + + public DeleteAllFileVisitor(final PrintStream logger) { + this.printStream = logger; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { + printf("%s Deleting directory %s\n", CLEANER_MARKER, dir); + final boolean deleted = Files.deleteIfExists(dir); + printf("%s Deleted directory %s: %s\n", CLEANER_MARKER, dir, deleted); + return FileVisitResult.CONTINUE; + } + + protected void printf(final String format, final Object... args) { + if (printStream != null) { + printStream.printf(format, args); + } + } + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + printf("%s Deleting file %s with %s\n", CLEANER_MARKER, file, attrs); + final boolean deleted = Files.deleteIfExists(file); + printf(CLEANER_MARKER, "%s Deleted file %s: %s\n", file, deleted); + return FileVisitResult.CONTINUE; + } + } + + private static final int MAX_TRIES = 10; + + public CleanFolders(final boolean before, final boolean after, final int maxTries, final File... files) { + super(before, after, maxTries, null, files); + } + + public CleanFolders(final boolean before, final boolean after, final int maxTries, final Path... paths) { + super(before, after, maxTries, null, paths); + } + + public CleanFolders(final boolean before, final boolean after, final int maxTries, final String... fileNames) { + super(before, after, maxTries, null, fileNames); + } + + public CleanFolders(final File... folders) { + super(true, true, MAX_TRIES, null, folders); + } + + public CleanFolders(final Path... paths) { + super(true, true, MAX_TRIES, null, paths); + } + + public CleanFolders(final PrintStream logger, final File... folders) { + super(true, true, MAX_TRIES, logger, folders); + } + + public CleanFolders(final String... folderNames) { + super(true, true, MAX_TRIES, null, folderNames); + } + + @Override + protected boolean clean(final Path path, final int tryIndex) throws IOException { + cleanFolder(path, tryIndex); + return true; + } + + private void cleanFolder(final Path folder, final int tryIndex) throws IOException { + if (Files.exists(folder) && Files.isDirectory(folder)) { + Files.walkFileTree(folder, new DeleteAllFileVisitor(getPrintStream())); + } + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpDirectories.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpDirectories.java new file mode 100644 index 00000000000..bdc185e133c --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpDirectories.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * JUnit extension to automatically clean up a list of directories and their contents before and after test execution. + * This will automatically retry deletion up to 10 times per file while pausing for 200ms each time. + * These can be overridden with system properties {@code log4j2.junit.fileCleanerMaxTries} and + * {@code log4j2.junit.fileCleanerSleepPeriodMillis}. + * + * @see DirectoryCleaner + * @see CleanUpFiles + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Inherited +@ExtendWith(DirectoryCleaner.class) +public @interface CleanUpDirectories { + String[] value(); +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpFiles.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpFiles.java new file mode 100644 index 00000000000..1ad6bcaf277 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/CleanUpFiles.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * JUnit extension to automatically clean up a list of files before and after test execution. + * This will automatically retry deletion up to 10 times per file while pausing for 200ms each time. + * These can be overridden with system properties {@code log4j2.junit.fileCleanerMaxTries} and + * {@code log4j2.junit.fileCleanerSleepPeriodMillis}. + * + * @see FileCleaner + * @see CleanUpDirectories + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Inherited +@ExtendWith(FileCleaner.class) +public @interface CleanUpFiles { + String[] value(); +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/DirectoryCleaner.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/DirectoryCleaner.java new file mode 100644 index 00000000000..a0f8e02dea7 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/DirectoryCleaner.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; +import java.util.HashSet; + +import org.junit.jupiter.api.extension.ExtensionContext; + +class DirectoryCleaner extends AbstractFileCleaner { + @Override + Collection getPathsForTest(final ExtensionContext context) { + final Collection paths = new HashSet<>(); + final CleanUpDirectories testClassAnnotation = context.getRequiredTestClass().getAnnotation(CleanUpDirectories.class); + if (testClassAnnotation != null) { + for (final String path : testClassAnnotation.value()) { + paths.add(Paths.get(path)); + } + } + final CleanUpDirectories testMethodAnnotation = context.getRequiredTestMethod().getAnnotation(CleanUpDirectories.class); + if (testMethodAnnotation != null) { + for (final String path : testMethodAnnotation.value()) { + paths.add(Paths.get(path)); + } + } + return paths; + } + + @Override + boolean delete(final Path path) throws IOException { + if (Files.exists(path) && Files.isDirectory(path)) { + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { + Files.deleteIfExists(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { + Files.deleteIfExists(dir); + return FileVisitResult.CONTINUE; + } + }); + } + return true; + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.java new file mode 100644 index 00000000000..0b4cc50d609 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ExtensionContextAnchor.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +public class ExtensionContextAnchor + implements BeforeAllCallback, BeforeEachCallback, AfterAllCallback, AfterEachCallback { + + public static Namespace LOG4J2_NAMESPACE = Namespace.create("org.apache.logging.log4j.junit"); + private static final ThreadLocal EXTENSION_CONTEXT = new InheritableThreadLocal<>(); + + private static void bind(ExtensionContext context) { + EXTENSION_CONTEXT.set(context); + } + + private static void unbind(ExtensionContext context) { + EXTENSION_CONTEXT.set(context.getParent().orElse(null)); + } + + public static ExtensionContext getContext() { + return EXTENSION_CONTEXT.get(); + } + + public static ExtensionContext getContext(ExtensionContext context) { + return context != null ? context : EXTENSION_CONTEXT.get(); + } + + static T getAttribute(Object key, Class clazz, ExtensionContext context) { + final ExtensionContext actualContext = getContext(context); + assertNotNull(actualContext, "missing ExtensionContext"); + return actualContext.getStore(LOG4J2_NAMESPACE).get(key, clazz); + } + + static void setAttribute(Object key, Object value, ExtensionContext context) { + final ExtensionContext actualContext = getContext(context); + assertNotNull(actualContext, "missing ExtensionContext"); + actualContext.getStore(LOG4J2_NAMESPACE).put(key, value); + } + + static void removeAttribute(Object key, ExtensionContext context) { + final ExtensionContext actualContext = getContext(context); + if (actualContext != null) { + actualContext.getStore(LOG4J2_NAMESPACE).remove(key); + } + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + unbind(context); + } + + @Override + public void afterAll(ExtensionContext context) throws Exception { + unbind(context); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + bind(context); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + bind(context); + } + +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/FileCleaner.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/FileCleaner.java new file mode 100644 index 00000000000..3ca16ac7172 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/FileCleaner.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; + +import org.junit.jupiter.api.extension.ExtensionContext; + +class FileCleaner extends AbstractFileCleaner { + @Override + Collection getPathsForTest(final ExtensionContext context) { + final Collection paths = new HashSet<>(); + final CleanUpFiles testClassAnnotation = context.getRequiredTestClass().getAnnotation(CleanUpFiles.class); + if (testClassAnnotation != null) { + for (final String path : testClassAnnotation.value()) { + paths.add(Paths.get(path)); + } + } + final CleanUpFiles testMethodAnnotation = context.getRequiredTestMethod().getAnnotation(CleanUpFiles.class); + if (testMethodAnnotation != null) { + for (final String path : testMethodAnnotation.value()) { + paths.add(Paths.get(path)); + } + } + return paths; + } + + @Override + boolean delete(final Path path) throws IOException { + return Files.deleteIfExists(path); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/InitializesThreadContext.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/InitializesThreadContext.java new file mode 100644 index 00000000000..33a8d9cc399 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/InitializesThreadContext.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.logging.log4j.ThreadContext; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Marks a test class that initializes the {@link ThreadContext} class; + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Inherited +@ExtendWith(ThreadContextInitializer.class) +@ResourceLock(value = Resources.THREAD_CONTEXT, mode = ResourceAccessMode.READ_WRITE) +public @interface InitializesThreadContext { +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Log4j2LauncherSessionListener.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Log4j2LauncherSessionListener.java new file mode 100644 index 00000000000..51abd82afe3 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Log4j2LauncherSessionListener.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; + +/** + * Global Log4j2 test setup. + */ +public class Log4j2LauncherSessionListener implements LauncherSessionListener { + + @Override + public void launcherSessionOpened(LauncherSession session) { + // Prevents `PropertiesUtil` from initializing (and caching the results) + // in the middle of a test. + PropertiesUtil.getProperties(); + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/LogManagerLoggerContextFactoryRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LogManagerLoggerContextFactoryRule.java similarity index 93% rename from log4j-api/src/test/java/org/apache/logging/log4j/junit/LogManagerLoggerContextFactoryRule.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LogManagerLoggerContextFactoryRule.java index 98735ab8552..6b4d0d9825a 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/LogManagerLoggerContextFactoryRule.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LogManagerLoggerContextFactoryRule.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.test.junit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.spi.LoggerContextFactory; @@ -23,7 +23,10 @@ /** * Sets the {@link LogManager}'s {@link LoggerContextFactory} to the given instance before the test and restores it to * the original value after the test. + * + * @deprecated Use {@link LoggerContextFactoryExtension} with JUnit 5 */ +@Deprecated public class LogManagerLoggerContextFactoryRule extends ExternalResource { private final LoggerContextFactory loggerContextFactory; diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LoggerContextFactoryExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LoggerContextFactoryExtension.java new file mode 100644 index 00000000000..89d412eb39b --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/LoggerContextFactoryExtension.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * JUnit 5 extension that sets a particular {@link LoggerContextFactory} for the entire run of tests in a class. + * + * @since 2.14.0 + */ +public class LoggerContextFactoryExtension implements BeforeAllCallback, AfterAllCallback { + + private static final String KEY = "previousFactory"; + private final LoggerContextFactory loggerContextFactory; + + public LoggerContextFactoryExtension(LoggerContextFactory loggerContextFactory) { + this.loggerContextFactory = loggerContextFactory; + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + getStore(context).put(KEY, LogManager.getFactory()); + LogManager.setFactory(loggerContextFactory); + } + + @Override + public void afterAll(ExtensionContext context) throws Exception { + LogManager.setFactory(getStore(context).get(KEY, LoggerContextFactory.class)); + } + + private ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestClass())); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/Mutable.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Mutable.java similarity index 95% rename from log4j-api/src/test/java/org/apache/logging/log4j/junit/Mutable.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Mutable.java index b81a12b420d..2e89bd1b799 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/Mutable.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Mutable.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.test.junit; /** * Helper class for JUnit tests. diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Resources.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Resources.java new file mode 100644 index 00000000000..4f85703d858 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/Resources.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Constants to use the the {@link ResourceLock} annotation. + * + */ +public class Resources { + + public static final String THREAD_CONTEXT = "log4j2.ThreadContext"; + + public static final String MARKER_MANAGER = "log4j2.MarkerManager"; +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SecurityManagerTestRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SecurityManagerTestRule.java new file mode 100644 index 00000000000..57a9f713a5e --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SecurityManagerTestRule.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * Sets a security manager for a test run. The current security manager is first saved then restored after the test is + * run. + *

+ * Using a security manager can mess up other tests so this is best used from integration tests (classes that end in + * "IT" instead of "Test" and "TestCase".) + *

+ * + *

+ * When this test rule is evaluated, it will: + *

+ *
    + *
  1. Save the current SecurityManager.
  2. + *
  3. Set the SecurityManager to the instance supplied to this rule.
  4. + *
  5. Evaluate the test statement.
  6. + *
  7. Reset the current SecurityManager to the one from step (1).
  8. + *
+ * + * @since 2.11.0 + */ +public class SecurityManagerTestRule implements TestRule { + + /** + * Constructs a new instance with the given {@link SecurityManager}. + *

+ * When this test rule is evaluated, it will: + *

+ *
    + *
  1. Save the current SecurityManager.
  2. + *
  3. Set the SecurityManager to the instance supplied to this rule.
  4. + *
  5. Evaluate the test statement.
  6. + *
  7. Reset the current SecurityManager to the one from step (1).
  8. + *
+ * + * @param securityManager + * the {@link SecurityManager} to use while running a test. + */ + public SecurityManagerTestRule(final SecurityManager securityManager) { + super(); + this.securityManager = securityManager; + } + + private SecurityManager securityManagerBefore; + private final SecurityManager securityManager; + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + before(); + try { + base.evaluate(); + } finally { + after(); + } + } + + private void after() { + System.setSecurityManager(securityManagerBefore); + } + + private void before() { + securityManagerBefore = System.getSecurityManager(); + System.setSecurityManager(securityManager); + + } + }; + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/SerialUtil.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java similarity index 95% rename from log4j-api/src/test/java/org/apache/logging/log4j/junit/SerialUtil.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java index 211eca6b44d..5fe8e2d05dc 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/SerialUtil.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SerialUtil.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.test.junit; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -52,7 +52,7 @@ public static byte[] serialize(final Serializable obj) { * @param data byte array representing the serialized object * @return the deserialized object */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "BanSerializableRead"}) public static T deserialize(final byte[] data) { try { final ByteArrayInputStream bas = new ByteArrayInputStream(data); diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SetTestProperty.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SetTestProperty.java new file mode 100644 index 00000000000..aa8a81e943f --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/SetTestProperty.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.ReadsEnvironmentVariable; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Registers a Log4j2 system property with the {@link TestPropertySource}. The + * property will also be available in configuration files using the + * {@code ${test:...} lookup. + * + */ +@Retention(RUNTIME) +@Target({ TYPE, METHOD }) +@Inherited +@Documented +@ExtendWith(ExtensionContextAnchor.class) +@ExtendWith(TestPropertyResolver.class) +@Repeatable(SetTestProperty.SetTestProperties.class) +@ReadsSystemProperty +@ReadsEnvironmentVariable +public @interface SetTestProperty { + + String key(); + + String value(); + + @Retention(RUNTIME) + @Target({ TYPE, METHOD }) + @Documented + @Inherited + public @interface SetTestProperties { + + SetTestProperty[] value(); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevel.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevel.java new file mode 100644 index 00000000000..dcceba9432e --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevel.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JUnit 5 test extension that sets a specific StatusLogger logging level for each test. + * + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Inherited +@ExtendWith(StatusLoggerLevelExtension.class) +@ResourceLock("log4j2.StatusLogger") +public @interface StatusLoggerLevel { + /** Name of {@link org.apache.logging.log4j.Level} to use for status logger. */ + String value(); +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevelExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevelExtension.java new file mode 100644 index 00000000000..13a86fa8455 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerLevelExtension.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +class StatusLoggerLevelExtension implements BeforeEachCallback, AfterEachCallback { + + private static final String KEY = "previousLevel"; + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + final StatusLoggerLevel annotation = context.getRequiredTestClass().getAnnotation(StatusLoggerLevel.class); + if (annotation == null) { + return; + } + final StatusLogger logger = StatusLogger.getLogger(); + getStore(context).put(KEY, logger.getLevel()); + logger.setLevel(Level.valueOf(annotation.value())); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + StatusLogger.getLogger().setLevel(getStore(context).get(KEY, Level.class)); + } + + private ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace + .create(getClass(), context.getRequiredTestInstance())); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerRule.java similarity index 93% rename from log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerRule.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerRule.java index 11f945a4333..ab41d2c570a 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/StatusLoggerRule.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/StatusLoggerRule.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.test.junit; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.status.StatusLogger; @@ -25,7 +25,9 @@ * Log4j configuration file. * * @since 2.8 + * @deprecated Use {@link StatusLoggerLevel} with JUnit 5 */ +@Deprecated public class StatusLoggerRule extends ExternalResource { private final StatusLogger statusLogger = StatusLogger.getLogger(); diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertyResolver.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertyResolver.java new file mode 100644 index 00000000000..abb659f2556 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertyResolver.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.test.TestProperties; +import org.apache.logging.log4j.util.ReflectionUtil; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; + +public class TestPropertyResolver extends TypeBasedParameterResolver + implements BeforeAllCallback, BeforeEachCallback { + + public TestPropertyResolver() { + super(TestProperties.class); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + final TestProperties props = TestPropertySource.createProperties(context); + final SetTestProperty[] setProperties = context.getRequiredTestMethod() + .getAnnotationsByType(SetTestProperty.class); + if (setProperties.length > 0) { + for (final SetTestProperty setProperty : setProperties) { + props.setProperty(setProperty.key(), setProperty.value()); + } + } + final Class testClass = context.getRequiredTestClass(); + Object testInstance = context.getRequiredTestInstance(); + ReflectionSupport + .findFields(testClass, + field -> ModifierSupport.isNotStatic(field) + && field.getType().isAssignableFrom(TestProperties.class), + HierarchyTraversalMode.BOTTOM_UP) + .forEach(field -> ReflectionUtil.setFieldValue(field, testInstance, props)); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + final TestProperties props = TestPropertySource.createProperties(context); + final SetTestProperty[] setProperties = context.getRequiredTestClass() + .getAnnotationsByType(SetTestProperty.class); + if (setProperties.length > 0) { + for (final SetTestProperty setProperty : setProperties) { + props.setProperty(setProperty.key(), setProperty.value()); + } + } + final Class testClass = context.getRequiredTestClass(); + ReflectionSupport + .findFields(testClass, + field -> ModifierSupport.isStatic(field) + && field.getType().isAssignableFrom(TestProperties.class), + HierarchyTraversalMode.BOTTOM_UP) + .forEach(field -> ReflectionUtil.setStaticFieldValue(field, props)); + } + + @Override + public TestProperties resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return TestPropertySource.createProperties(extensionContext); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertySource.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertySource.java new file mode 100644 index 00000000000..1ad7e93c1eb --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TestPropertySource.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.test.TestProperties; +import org.apache.logging.log4j.util.PropertySource; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +public class TestPropertySource implements PropertySource { + + private static final String PREFIX = "log4j2."; + private static final Namespace NAMESPACE = ExtensionContextAnchor.LOG4J2_NAMESPACE.append("properties"); + private static final TestProperties EMPTY_PROPERTIES = new EmptyTestProperties(); + + @Override + public int getPriority() { + // Highest priority + return Integer.MIN_VALUE; + } + + public static TestProperties createProperties(ExtensionContext context) { + TestProperties props = getProperties(context); + // Make sure that the properties do not come from the parent ExtensionContext + if (props instanceof JUnitTestProperties && context.equals(((JUnitTestProperties) props).getContext())) { + return props; + } + props = new JUnitTestProperties(context); + ExtensionContextAnchor.setAttribute(TestProperties.class, props, context); + return props; + } + + public static TestProperties getProperties() { + return getProperties(null); + } + + private static TestProperties getProperties(ExtensionContext context) { + final ExtensionContext actualContext = context != null ? context : ExtensionContextAnchor.getContext(); + if (actualContext != null) { + TestProperties props = ExtensionContextAnchor.getAttribute(TestProperties.class, TestProperties.class, + actualContext); + if (props != null) { + return props; + } + } + return EMPTY_PROPERTIES; + } + + @Override + public CharSequence getNormalForm(Iterable tokens) { + final CharSequence camelCase = Util.joinAsCamelCase(tokens); + // Do not use Strings to prevent recursive initialization + return camelCase.length() > 0 ? PREFIX + camelCase.toString() : null; + } + + @Override + public String getProperty(String key) { + return getProperties().getProperty(key); + } + + @Override + public boolean containsProperty(String key) { + return getProperties().containsProperty(key); + } + + private static class JUnitTestProperties implements TestProperties { + + private final ExtensionContext context; + private final Store store; + + public JUnitTestProperties(ExtensionContext context) { + this.context = context; + this.store = context.getStore(NAMESPACE); + } + + public ExtensionContext getContext() { + return context; + } + + @Override + public String getProperty(String key) { + return store.get(key, String.class); + } + + @Override + public boolean containsProperty(String key) { + return getProperty(key) != null; + } + + @Override + public void setProperty(String key, String value) { + store.put(key, value); + } + + @Override + public void clearProperty(String key) { + store.remove(key, String.class); + } + + } + + private static class EmptyTestProperties implements TestProperties { + + @Override + public String getProperty(String key) { + return null; + } + + @Override + public boolean containsProperty(String key) { + return false; + } + + @Override + public void setProperty(String key, String value) { + throw new UnsupportedOperationException(); + } + + @Override + public void clearProperty(String key) { + throw new UnsupportedOperationException(); + } + + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextInitializer.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextInitializer.java new file mode 100644 index 00000000000..c246c3ce5fc --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextInitializer.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.test.ThreadContextUtilityClass; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +import org.junit.platform.commons.support.AnnotationSupport; + + +class ThreadContextInitializer implements BeforeAllCallback, BeforeEachCallback { + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + if (AnnotationSupport.isAnnotated(context.getRequiredTestClass(), InitializesThreadContext.class)) { + resetThreadContext(context); + } + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + if (AnnotationSupport.isAnnotated(context.getRequiredTestMethod(), InitializesThreadContext.class)) { + resetThreadContext(context); + } + } + + private void resetThreadContext(ExtensionContext context) { + ThreadContextUtilityClass.reset(); + // We use `CloseableResource` instead of `afterAll` to reset the + // ThreadContextFactory + // *after* the `@SetSystemProperty` extension has restored the properties + ExtensionContextAnchor.setAttribute(ThreadContext.class, new CloseableResource() { + @Override + public void close() throws Throwable { + ThreadContextUtilityClass.reset(); + } + + }, context); + } + +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapExtension.java new file mode 100644 index 00000000000..cb68eb295dc --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapExtension.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import java.util.Map; + +import org.apache.logging.log4j.ThreadContext; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +class ThreadContextMapExtension implements BeforeEachCallback { + private static class ThreadContextMapStore implements ExtensionContext.Store.CloseableResource { + private final Map previousMap = ThreadContext.getImmutableContext(); + + private ThreadContextMapStore() { + ThreadContext.clearMap(); + } + + @Override + public void close() throws Throwable { + // TODO LOG4J2-1517 Add ThreadContext.setContext(Map) + ThreadContext.clearMap(); + ThreadContext.putAll(previousMap); + } + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + context.getStore(ExtensionContextAnchor.LOG4J2_NAMESPACE) + .getOrComputeIfAbsent(ThreadContextMapStore.class); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapRule.java similarity index 90% rename from log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapRule.java index 406630382af..deb7c772368 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextMapRule.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextMapRule.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.test.junit; /** * Restores the ThreadContext to it's initial map values after a JUnit test. @@ -25,7 +25,10 @@ * @Rule * public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule(); * + * + * @deprecated use {@link UsingThreadContextMap} with JUnit 5 */ +@Deprecated public class ThreadContextMapRule extends ThreadContextRule { /** diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextRule.java similarity index 92% rename from log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextRule.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextRule.java index dad7b5b32bf..1ec1789ca8b 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextRule.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextRule.java @@ -14,10 +14,10 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.test.junit; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.ThreadContextHolder; +import org.apache.logging.log4j.test.ThreadContextHolder; import org.junit.rules.ExternalResource; /** @@ -29,7 +29,10 @@ * @Rule * public final ThreadContextRule threadContextRule = new ThreadContextRule(); * + * + * @deprecated use {@link UsingAnyThreadContext} with JUnit 5 */ +@Deprecated public class ThreadContextRule extends ExternalResource { private final boolean restoreMap; diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackExtension.java new file mode 100644 index 00000000000..c3ebe7b484b --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackExtension.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.ThreadContext; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +import java.util.Collection; + +class ThreadContextStackExtension implements BeforeEachCallback { + private static class ThreadContextStackStore implements Store.CloseableResource { + private final Collection previousStack = ThreadContext.getImmutableStack(); + + private ThreadContextStackStore() { + ThreadContext.clearStack(); + } + + @Override + public void close() throws Throwable { + ThreadContext.setStack(previousStack); + } + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + context.getStore(Namespace.create(ThreadContext.class, context.getRequiredTestClass(), context.getRequiredTestInstance())) + .getOrComputeIfAbsent(ThreadContextStackStore.class); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackRule.java similarity index 91% rename from log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java rename to log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackRule.java index 1581dc8a7c4..77c74cd475d 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ThreadContextStackRule.java +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/ThreadContextStackRule.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.test.junit; /** * Restores the ThreadContext to it's initial stack values after a JUnit test. @@ -25,7 +25,9 @@ * @Rule * public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule(); * + * @deprecated use {@link UsingThreadContextStack} with JUnit 5 */ +@Deprecated public class ThreadContextStackRule extends ThreadContextRule { /** diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TypeBasedParameterResolver.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TypeBasedParameterResolver.java new file mode 100644 index 00000000000..8cf338493b0 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/TypeBasedParameterResolver.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import java.lang.reflect.Type; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +public abstract class TypeBasedParameterResolver implements ParameterResolver { + + private final Type supportedParameterType; + + public TypeBasedParameterResolver(Type supportedParameterType) { + this.supportedParameterType = supportedParameterType; + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return this.supportedParameterType.equals(parameterContext.getParameter().getParameterizedType()); + } + + @Override + public abstract T resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException; +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/URLStreamHandlerFactories.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/URLStreamHandlerFactories.java new file mode 100644 index 00000000000..aa11328f703 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/URLStreamHandlerFactories.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.opentest4j.TestAbortedException; + +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLStreamHandlerFactory; +import java.util.Hashtable; +import java.util.stream.Stream; + +public class URLStreamHandlerFactories { + private static final Field FACTORY_FIELD = Stream.of(URL.class.getDeclaredFields()) + .filter(field -> URLStreamHandlerFactory.class.equals(field.getType())) + .findFirst() + .orElseThrow(() -> new TestAbortedException("java.net.URL does not declare a java.net.URLStreamHandlerFactory field")); + private static final Field HANDLERS_FIELD = FieldUtils.getDeclaredField(URL.class, "handlers", true); + + public static URLStreamHandlerFactory getURLStreamHandlerFactory() { + try { + return (URLStreamHandlerFactory) FieldUtils.readStaticField(FACTORY_FIELD, true); + } catch (IllegalAccessException e) { + throw new IllegalAccessError(e.getMessage()); + } + } + + public static void setURLStreamHandlerFactory(final URLStreamHandlerFactory factory) { + try { + final Object handlers = HANDLERS_FIELD.get(null); + if (handlers instanceof Hashtable) { + ((Hashtable) handlers).clear(); + } + FieldUtils.writeStaticField(FACTORY_FIELD, null, true); + } catch (IllegalAccessException e) { + throw new IllegalAccessError(e.getMessage()); + } + if (factory != null) { + URL.setURLStreamHandlerFactory(factory); + } + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/URLStreamHandlerFactoryExtension.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/URLStreamHandlerFactoryExtension.java new file mode 100644 index 00000000000..2450657628b --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/URLStreamHandlerFactoryExtension.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.util.ReflectionUtil; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.net.URLStreamHandlerFactory; + +public class URLStreamHandlerFactoryExtension implements BeforeAllCallback { + @Override + public void beforeAll(final ExtensionContext context) throws Exception { + final Class testClass = context.getRequiredTestClass(); + final URLStreamHandlerFactory factory = AnnotationSupport.findAnnotation(testClass, UsingURLStreamHandlerFactory.class) + .map(UsingURLStreamHandlerFactory::value) + .map(ReflectionUtil::instantiate) + .orElseThrow(); + final URLStreamHandlerFactory oldFactory = URLStreamHandlerFactories.getURLStreamHandlerFactory(); + URLStreamHandlerFactories.setURLStreamHandlerFactory(factory); + context.getStore(ExtensionContext.Namespace.create(getClass(), testClass)) + .put(URLStreamHandlerFactory.class, (ExtensionContext.Store.CloseableResource) () -> + URLStreamHandlerFactories.setURLStreamHandlerFactory(oldFactory)); + } +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingAnyThreadContext.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingAnyThreadContext.java new file mode 100644 index 00000000000..e88914707c6 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingAnyThreadContext.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test class as using {@link org.apache.logging.log4j.ThreadContext} APIs. This will automatically clear and restore + * both the thread context map and stack for each test invocation. + * + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Documented +@Inherited +@UsingThreadContextMap +@UsingThreadContextStack +public @interface UsingAnyThreadContext { +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingTestProperties.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingTestProperties.java new file mode 100644 index 00000000000..cc696522ab7 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingTestProperties.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.apache.logging.log4j.test.TestProperties; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.ReadsEnvironmentVariable; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A field or method parameter of type {@link TestProperties} will be injected with a per-test source of Log4j2's + * system properties. + */ +@Retention(RUNTIME) +@Target({ TYPE, METHOD }) +@Inherited +@Documented +@ExtendWith(ExtensionContextAnchor.class) +@ExtendWith(TestPropertyResolver.class) +@ReadsSystemProperty +@ReadsEnvironmentVariable +public @interface UsingTestProperties { +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextMap.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextMap.java new file mode 100644 index 00000000000..41ba316e9a8 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextMap.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test class as using {@link org.apache.logging.log4j.spi.ThreadContextMap} APIs. This will automatically clear and + * restore the thread context map (MDC) for each test invocation. + * + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Documented +@Inherited +@ExtendWith(ThreadContextMapExtension.class) +@ReadsSystemProperty +@ResourceLock(value = Resources.THREAD_CONTEXT, mode = ResourceAccessMode.READ) +public @interface UsingThreadContextMap { +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextStack.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextStack.java new file mode 100644 index 00000000000..6d8251a6714 --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingThreadContextStack.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test class as using {@link org.apache.logging.log4j.spi.ThreadContextStack} APIs. This will automatically clear and + * restore the thread context stack (NDC) for each test invocation. + * + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Documented +@Inherited +@ExtendWith(ThreadContextStackExtension.class) +@ReadsSystemProperty +public @interface UsingThreadContextStack { +} diff --git a/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingURLStreamHandlerFactory.java b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingURLStreamHandlerFactory.java new file mode 100644 index 00000000000..25137a7758f --- /dev/null +++ b/log4j-api-test/src/main/java/org/apache/logging/log4j/test/junit/UsingURLStreamHandlerFactory.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test.junit; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Isolated; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.net.URLStreamHandlerFactory; + +/** + * Test extension to use a custom {@link URLStreamHandlerFactory} for all tests in the annotated class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Inherited +@ExtendWith(URLStreamHandlerFactoryExtension.class) +@Isolated +public @interface UsingURLStreamHandlerFactory { + Class value(); +} diff --git a/log4j-api-test/src/main/module/module-info.java b/log4j-api-test/src/main/module/module-info.java new file mode 100644 index 00000000000..4cf9336ae38 --- /dev/null +++ b/log4j-api-test/src/main/module/module-info.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +import org.apache.logging.log4j.util.PropertySource; +import org.apache.logging.log4j.test.junit.TestPropertySource; +module org.apache.logging.log4j.test { + exports org.apache.logging.log4j.test; + exports org.apache.logging.log4j.test.junit; + + opens org.apache.logging.log4j.test.junit to org.junit.platform.commons; + + requires transitive org.apache.logging.log4j; + requires static org.apache.commons.lang3; + requires static org.assertj.core; + requires static org.hamcrest; + requires static org.junit.jupiter.api; + requires static org.junit.jupiter.params; + requires static org.junit.platform.commons; + requires static org.junit.platform.launcher; + requires static org.junitpioneer; + requires static junit; + + provides PropertySource with TestPropertySource; +} diff --git a/log4j-api-test/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource b/log4j-api-test/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource new file mode 100644 index 00000000000..2c7cb498d92 --- /dev/null +++ b/log4j-api-test/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource @@ -0,0 +1,15 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +org.apache.logging.log4j.test.junit.TestPropertySource diff --git a/log4j-api-test/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/log4j-api-test/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..ca7ce84edd3 --- /dev/null +++ b/log4j-api-test/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1,15 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +org.apache.logging.log4j.test.junit.ExtensionContextAnchor \ No newline at end of file diff --git a/log4j-api-test/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/log4j-api-test/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 00000000000..e492405c761 --- /dev/null +++ b/log4j-api-test/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +org.apache.logging.log4j.test.junit.Log4j2LauncherSessionListener \ No newline at end of file diff --git a/log4j-api-test/src/site/markdown/index.md b/log4j-api-test/src/site/markdown/index.md new file mode 100644 index 00000000000..f83cc0d7a61 --- /dev/null +++ b/log4j-api-test/src/site/markdown/index.md @@ -0,0 +1,26 @@ + + + +# Log4j 2 API + +The Log4j 2 API provides the interface that applications should code to and provides the +adapter components required for implementers to create a logging implementation. + +## Requirements + +As of version 2.4, the Log4j 2 API requires Java 7. Versions 2.3 and earlier require Java 6. diff --git a/log4j-api-test/src/site/site.xml b/log4j-api-test/src/site/site.xml new file mode 100644 index 00000000000..e0a45af2225 --- /dev/null +++ b/log4j-api-test/src/site/site.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java new file mode 100644 index 00000000000..6a49e73ccef --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java @@ -0,0 +1,1319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import org.apache.logging.log4j.test.junit.Resources; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.MessageSupplier; +import org.apache.logging.log4j.util.Supplier; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; + +import java.util.List; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("WARN") +@ResourceLock(value = Resources.MARKER_MANAGER, mode = ResourceAccessMode.READ) +public class AbstractLoggerTest { + + private static final StringBuilder CHAR_SEQ = new StringBuilder("CharSeq"); + + // TODO add proper tests for ReusableMessage + + @SuppressWarnings("ThrowableInstanceNeverThrown") + private static final Throwable t = new UnsupportedOperationException("Test"); + + private static final Class obj = AbstractLogger.class; + private static final String pattern = "{}, {}"; + private static final String p1 = "Long Beach"; + + private static final String p2 = "California"; + private static final Message charSeq = new SimpleMessage(CHAR_SEQ); + private static final Message simple = new SimpleMessage("Hello"); + private static final Message object = new ObjectMessage(obj); + + private static final Message param = new ParameterizedMessage(pattern, p1, p2); + + private final Marker MARKER = MarkerManager.getMarker("TEST"); + private static final String MARKER_NAME = "TEST"; + + private static final LogEvent[] EVENTS = new LogEvent[] { + new LogEvent(null, simple, null), + new LogEvent(MARKER_NAME, simple, null), + new LogEvent(null, simple, t), + new LogEvent(MARKER_NAME, simple, t), + + new LogEvent(null, object, null), + new LogEvent(MARKER_NAME, object, null), + new LogEvent(null, object, t), + new LogEvent(MARKER_NAME, object, t), + + new LogEvent(null, param, null), + new LogEvent(MARKER_NAME, param, null), + + new LogEvent(null, simple, null), + new LogEvent(null, simple, t), + new LogEvent(MARKER_NAME, simple, null), + new LogEvent(MARKER_NAME, simple, t), + new LogEvent(MARKER_NAME, simple, null), + + new LogEvent(null, charSeq, null), + new LogEvent(null, charSeq, t), + new LogEvent(MARKER_NAME, charSeq, null), + new LogEvent(MARKER_NAME, charSeq, t), + }; + + + @Test + public void testDebug() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.DEBUG); + + logger.setCurrentEvent(EVENTS[0]); + logger.debug("Hello"); + logger.debug((Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.debug(MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.debug("Hello", t); + logger.debug((Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.debug(MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.debug(obj); + logger.setCurrentEvent(EVENTS[5]); + logger.debug(MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.debug(obj, t); + logger.debug((Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.debug(MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.debug(pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.debug(MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.debug(simple); + logger.debug((Marker) null, simple); + logger.debug((Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.debug(simple, t); + logger.debug((Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.debug(MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.debug(MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.debug(MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.debug(CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.debug(CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.debug(MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.debug(MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testError() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.ERROR); + + logger.setCurrentEvent(EVENTS[0]); + logger.error("Hello"); + logger.error((Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.error(MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.error("Hello", t); + logger.error((Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.error(MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.error(obj); + logger.setCurrentEvent(EVENTS[5]); + logger.error(MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.error(obj, t); + logger.error((Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.error(MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.error(pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.error(MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.error(simple); + logger.error((Marker) null, simple); + logger.error((Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.error(simple, t); + logger.error((Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.error(MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.error(MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.error(MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.error(CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.error(CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.error(MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.error(MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testFatal() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.FATAL); + + logger.setCurrentEvent(EVENTS[0]); + logger.fatal("Hello"); + logger.fatal((Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.fatal(MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.fatal("Hello", t); + logger.fatal((Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.fatal(MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.fatal(obj); + logger.setCurrentEvent(EVENTS[5]); + logger.fatal(MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.fatal(obj, t); + logger.fatal((Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.fatal(MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.fatal(pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.fatal(MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.fatal(simple); + logger.fatal((Marker) null, simple); + logger.fatal((Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.fatal(simple, t); + logger.fatal((Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.fatal(MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.fatal(MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.fatal(MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.fatal(CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.fatal(CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.fatal(MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.fatal(MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testInfo() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.INFO); + + logger.setCurrentEvent(EVENTS[0]); + logger.info("Hello"); + logger.info((Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.info(MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.info("Hello", t); + logger.info((Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.info(MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.info(obj); + logger.setCurrentEvent(EVENTS[5]); + logger.info(MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.info(obj, t); + logger.info((Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.info(MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.info(pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.info(MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.info(simple); + logger.info((Marker) null, simple); + logger.info((Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.info(simple, t); + logger.info((Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.info(MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.info(MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.info(MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.info(CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.info(CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.info(MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.info(MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testLogDebug() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.DEBUG); + + logger.setCurrentEvent(EVENTS[0]); + logger.log(Level.DEBUG, "Hello"); + logger.log(Level.DEBUG, (Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.log(Level.DEBUG, MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.log(Level.DEBUG, "Hello", t); + logger.log(Level.DEBUG, (Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.log(Level.DEBUG, MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.log(Level.DEBUG, obj); + logger.setCurrentEvent(EVENTS[5]); + logger.log(Level.DEBUG, MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.log(Level.DEBUG, obj, t); + logger.log(Level.DEBUG, (Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.log(Level.DEBUG, MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.log(Level.DEBUG, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.log(Level.DEBUG, MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.log(Level.DEBUG, simple); + logger.log(Level.DEBUG, (Marker) null, simple); + logger.log(Level.DEBUG, (Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.log(Level.DEBUG, simple, t); + logger.log(Level.DEBUG, (Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.log(Level.DEBUG, MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.log(Level.DEBUG, MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.log(Level.DEBUG, MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.log(Level.DEBUG, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.log(Level.DEBUG, CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.log(Level.DEBUG, MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.log(Level.DEBUG, MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testLogError() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.ERROR); + + logger.setCurrentEvent(EVENTS[0]); + logger.log(Level.ERROR, "Hello"); + logger.log(Level.ERROR, (Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.log(Level.ERROR, MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.log(Level.ERROR, "Hello", t); + logger.log(Level.ERROR, (Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.log(Level.ERROR, MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.log(Level.ERROR, obj); + logger.setCurrentEvent(EVENTS[5]); + logger.log(Level.ERROR, MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.log(Level.ERROR, obj, t); + logger.log(Level.ERROR, (Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.log(Level.ERROR, MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.log(Level.ERROR, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.log(Level.ERROR, MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.log(Level.ERROR, simple); + logger.log(Level.ERROR, (Marker) null, simple); + logger.log(Level.ERROR, (Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.log(Level.ERROR, simple, t); + logger.log(Level.ERROR, (Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.log(Level.ERROR, MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.log(Level.ERROR, MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.log(Level.ERROR, MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.log(Level.ERROR, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.log(Level.ERROR, CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.log(Level.ERROR, MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.log(Level.ERROR, MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testLogFatal() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.FATAL); + + logger.setCurrentEvent(EVENTS[0]); + logger.log(Level.FATAL, "Hello"); + logger.log(Level.FATAL, (Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.log(Level.FATAL, MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.log(Level.FATAL, "Hello", t); + logger.log(Level.FATAL, (Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.log(Level.FATAL, MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.log(Level.FATAL, obj); + logger.setCurrentEvent(EVENTS[5]); + logger.log(Level.FATAL, MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.log(Level.FATAL, obj, t); + logger.log(Level.FATAL, (Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.log(Level.FATAL, MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.log(Level.FATAL, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.log(Level.FATAL, MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.log(Level.FATAL, simple); + logger.log(Level.FATAL, (Marker) null, simple); + logger.log(Level.FATAL, (Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.log(Level.FATAL, simple, t); + logger.log(Level.FATAL, (Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.log(Level.FATAL, MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.log(Level.FATAL, MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.log(Level.FATAL, MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.log(Level.FATAL, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.log(Level.FATAL, CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.log(Level.FATAL, MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.log(Level.FATAL, MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testLogInfo() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.INFO); + + logger.setCurrentEvent(EVENTS[0]); + logger.log(Level.INFO, "Hello"); + logger.log(Level.INFO, (Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.log(Level.INFO, MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.log(Level.INFO, "Hello", t); + logger.log(Level.INFO, (Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.log(Level.INFO, MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.log(Level.INFO, obj); + logger.setCurrentEvent(EVENTS[5]); + logger.log(Level.INFO, MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.log(Level.INFO, obj, t); + logger.log(Level.INFO, (Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.log(Level.INFO, MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.log(Level.INFO, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.log(Level.INFO, MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.log(Level.INFO, simple); + logger.log(Level.INFO, (Marker) null, simple); + logger.log(Level.INFO, (Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.log(Level.INFO, simple, t); + logger.log(Level.INFO, (Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.log(Level.INFO, MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.log(Level.INFO, MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.log(Level.INFO, MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.log(Level.INFO, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.log(Level.INFO, CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.log(Level.INFO, MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.log(Level.INFO, MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testLogTrace() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.TRACE); + + logger.setCurrentEvent(EVENTS[0]); + logger.log(Level.TRACE, "Hello"); + logger.log(Level.TRACE, (Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.log(Level.TRACE, MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.log(Level.TRACE, "Hello", t); + logger.log(Level.TRACE, (Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.log(Level.TRACE, MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.log(Level.TRACE, obj); + logger.setCurrentEvent(EVENTS[5]); + logger.log(Level.TRACE, MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.log(Level.TRACE, obj, t); + logger.log(Level.TRACE, (Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.log(Level.TRACE, MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.log(Level.TRACE, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.log(Level.TRACE, MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.log(Level.TRACE, simple); + logger.log(Level.TRACE, (Marker) null, simple); + logger.log(Level.TRACE, (Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.log(Level.TRACE, simple, t); + logger.log(Level.TRACE, (Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.log(Level.TRACE, MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.log(Level.TRACE, MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.log(Level.TRACE, MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.log(Level.TRACE, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.log(Level.TRACE, CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.log(Level.TRACE, MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.log(Level.TRACE, MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testLogWarn() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.WARN); + + logger.setCurrentEvent(EVENTS[0]); + logger.log(Level.WARN, "Hello"); + logger.log(Level.WARN, (Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.log(Level.WARN, MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.log(Level.WARN, "Hello", t); + logger.log(Level.WARN, (Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.log(Level.WARN, MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.log(Level.WARN, obj); + logger.setCurrentEvent(EVENTS[5]); + logger.log(Level.WARN, MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.log(Level.WARN, obj, t); + logger.log(Level.WARN, (Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.log(Level.WARN, MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.log(Level.WARN, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.log(Level.WARN, MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.log(Level.WARN, simple); + logger.log(Level.WARN, (Marker) null, simple); + logger.log(Level.WARN, (Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.log(Level.WARN, simple, t); + logger.log(Level.WARN, (Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.log(Level.WARN, MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.log(Level.WARN, MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.log(Level.WARN, MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.log(Level.WARN, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.log(Level.WARN, CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.log(Level.WARN, MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.log(Level.WARN, MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testTrace() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.TRACE); + + logger.setCurrentEvent(EVENTS[0]); + logger.trace("Hello"); + logger.trace((Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.trace(MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.trace("Hello", t); + logger.trace((Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.trace(MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.trace(obj); + logger.setCurrentEvent(EVENTS[5]); + logger.trace(MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.trace(obj, t); + logger.trace((Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.trace(MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.trace(pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.trace(MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.trace(simple); + logger.trace((Marker) null, simple); + logger.trace((Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.trace(simple, t); + logger.trace((Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.trace(MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.trace(MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.trace(MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.trace(CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.trace(CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.trace(MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.trace(MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testWarn() { + final CountingLogger logger = new CountingLogger(); + logger.setCurrentLevel(Level.WARN); + + logger.setCurrentEvent(EVENTS[0]); + logger.warn("Hello"); + logger.warn((Marker) null, "Hello"); + logger.setCurrentEvent(EVENTS[1]); + logger.warn(MARKER, "Hello"); + logger.setCurrentEvent(EVENTS[2]); + logger.warn("Hello", t); + logger.warn((Marker) null, "Hello", t); + logger.setCurrentEvent(EVENTS[3]); + logger.warn(MARKER, "Hello", t); + logger.setCurrentEvent(EVENTS[4]); + logger.warn(obj); + logger.setCurrentEvent(EVENTS[5]); + logger.warn(MARKER, obj); + logger.setCurrentEvent(EVENTS[6]); + logger.warn(obj, t); + logger.warn((Marker) null, obj, t); + logger.setCurrentEvent(EVENTS[7]); + logger.warn(MARKER, obj, t); + logger.setCurrentEvent(EVENTS[8]); + logger.warn(pattern, p1, p2); + logger.setCurrentEvent(EVENTS[9]); + logger.warn(MARKER, pattern, p1, p2); + logger.setCurrentEvent(EVENTS[10]); + logger.warn(simple); + logger.warn((Marker) null, simple); + logger.warn((Marker) null, simple, null); + logger.setCurrentEvent(EVENTS[11]); + logger.warn(simple, t); + logger.warn((Marker) null, simple, t); + logger.setCurrentEvent(EVENTS[12]); + logger.warn(MARKER, simple, null); + logger.setCurrentEvent(EVENTS[13]); + logger.warn(MARKER, simple, t); + logger.setCurrentEvent(EVENTS[14]); + logger.warn(MARKER, simple); + + logger.setCurrentEvent(EVENTS[15]); + logger.warn(CHAR_SEQ); + logger.setCurrentEvent(EVENTS[16]); + logger.warn(CHAR_SEQ, t); + logger.setCurrentEvent(EVENTS[17]); + logger.warn(MARKER, CHAR_SEQ); + logger.setCurrentEvent(EVENTS[18]); + logger.warn(MARKER, CHAR_SEQ, t); + + assertEquals(4, logger.getCharSeqCount(), "log(CharSeq) invocations"); + assertEquals(5, logger.getObjectCount(), "log(Object) invocations"); + } + + @Test + public void testMessageWithThrowable() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(true); + final ThrowableMessage message = new ThrowableMessage(t); + + logger.debug(message); + logger.error(message); + logger.fatal(message); + logger.info(message); + logger.trace(message); + logger.warn(message); + logger.log(Level.INFO, message); + + logger.debug(MARKER, message); + logger.error(MARKER, message); + logger.fatal(MARKER, message); + logger.info(MARKER, message); + logger.trace(MARKER, message); + logger.warn(MARKER, message); + logger.log(Level.INFO, MARKER, message); + } + + @Test + public void testMessageWithoutThrowable() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); + final ThrowableMessage message = new ThrowableMessage(null); + + logger.debug(message); + logger.error(message); + logger.fatal(message); + logger.info(message); + logger.trace(message); + logger.warn(message); + logger.log(Level.INFO, message); + + logger.debug(MARKER, message); + logger.error(MARKER, message); + logger.fatal(MARKER, message); + logger.info(MARKER, message); + logger.trace(MARKER, message); + logger.warn(MARKER, message); + logger.log(Level.INFO, MARKER, message); + } + + @Test + public void testMessageSupplierWithThrowable() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(true); + final ThrowableMessage message = new ThrowableMessage(t); + final MessageSupplier supplier = () -> message; + + logger.debug(supplier); + logger.error(supplier); + logger.fatal(supplier); + logger.info(supplier); + logger.trace(supplier); + logger.warn(supplier); + logger.log(Level.INFO, supplier); + + logger.debug(MARKER, supplier); + logger.error(MARKER, supplier); + logger.fatal(MARKER, supplier); + logger.info(MARKER, supplier); + logger.trace(MARKER, supplier); + logger.warn(MARKER, supplier); + logger.log(Level.INFO, MARKER, supplier); + } + + @Test + public void testMessageSupplierWithoutThrowable() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); + final ThrowableMessage message = new ThrowableMessage(null); + final MessageSupplier supplier = () -> message; + + logger.debug(supplier); + logger.error(supplier); + logger.fatal(supplier); + logger.info(supplier); + logger.trace(supplier); + logger.warn(supplier); + logger.log(Level.INFO, supplier); + + logger.debug(MARKER, supplier); + logger.error(MARKER, supplier); + logger.fatal(MARKER, supplier); + logger.info(MARKER, supplier); + logger.trace(MARKER, supplier); + logger.warn(MARKER, supplier); + logger.log(Level.INFO, MARKER, supplier); + } + + @Test + public void testSupplierWithThrowable() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(true); + final ThrowableMessage message = new ThrowableMessage(t); + final Supplier supplier = () -> message; + + logger.debug(supplier); + logger.error(supplier); + logger.fatal(supplier); + logger.info(supplier); + logger.trace(supplier); + logger.warn(supplier); + logger.log(Level.INFO, supplier); + + logger.debug(MARKER, supplier); + logger.error(MARKER, supplier); + logger.fatal(MARKER, supplier); + logger.info(MARKER, supplier); + logger.trace(MARKER, supplier); + logger.warn(MARKER, supplier); + logger.log(Level.INFO, MARKER, supplier); + } + + @Test + public void testSupplierWithoutThrowable() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); + final ThrowableMessage message = new ThrowableMessage(null); + final Supplier supplier = () -> message; + + logger.debug(supplier); + logger.error(supplier); + logger.fatal(supplier); + logger.info(supplier); + logger.trace(supplier); + logger.warn(supplier); + logger.log(Level.INFO, supplier); + + logger.debug(MARKER, supplier); + logger.error(MARKER, supplier); + logger.fatal(MARKER, supplier); + logger.info(MARKER, supplier); + logger.trace(MARKER, supplier); + logger.warn(MARKER, supplier); + logger.log(Level.INFO, MARKER, supplier); + } + + @Test + @ResourceLock("log4j2.StatusLogger") + public void testMessageThrows() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); + logger.error(new TestMessage(() -> { + throw new IllegalStateException("Oops!"); + }, "Message Format")); + List statusDatalist = StatusLogger.getLogger().getStatusData(); + StatusData mostRecent = statusDatalist.get(statusDatalist.size() - 1); + assertEquals(Level.WARN, mostRecent.getLevel()); + assertThat(mostRecent.getFormattedStatus(), containsString( + "org.apache.logging.log4j.spi.AbstractLogger caught " + + "java.lang.IllegalStateException logging TestMessage: Message Format")); + } + + @Test + @ResourceLock("log4j2.StatusLogger") + public void testMessageThrowsAndNullFormat() { + final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); + logger.error(new TestMessage(() -> { + throw new IllegalStateException("Oops!"); + }, null /* format */)); + List statusDatalist = StatusLogger.getLogger().getStatusData(); + StatusData mostRecent = statusDatalist.get(statusDatalist.size() - 1); + assertEquals(Level.WARN, mostRecent.getLevel()); + assertThat(mostRecent.getFormattedStatus(), containsString( + "org.apache.logging.log4j.spi.AbstractLogger caught " + + "java.lang.IllegalStateException logging TestMessage: ")); + } + + private static final class TestMessage implements Message { + private static final long serialVersionUID = 1L; + private final FormattedMessageSupplier formattedMessageSupplier; + private final String format; + TestMessage(FormattedMessageSupplier formattedMessageSupplier, String format) { + this.formattedMessageSupplier = formattedMessageSupplier; + this.format = format; + } + + @Override + public String getFormattedMessage() { + return formattedMessageSupplier.getFormattedMessage(); + } + + @Override + public String getFormat() { + return format; + } + + @Override + public Object[] getParameters() { + return new Object[0]; + } + + @Override + public Throwable getThrowable() { + return null; + } + + interface FormattedMessageSupplier { + String getFormattedMessage(); + } + } + + private static class CountingLogger extends AbstractLogger { + private static final long serialVersionUID = -3171452617952475480L; + + private Level currentLevel; + private LogEvent currentEvent; + private int charSeqCount; + private int objectCount; + + CountingLogger() { + super("CountingLogger", ParameterizedMessageFactory.INSTANCE); + } + + void setCurrentLevel(final Level currentLevel) { + this.currentLevel = currentLevel; + } + + void setCurrentEvent(final LogEvent currentEvent) { + this.currentEvent = currentEvent; + } + + int getCharSeqCount() { + return charSeqCount; + } + + int getObjectCount() { + return objectCount; + } + + @Override + public Level getLevel() { + return currentLevel; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) { + assertEquals(level, currentLevel, "Incorrect Level. Expected " + currentLevel + ", actual " + level); + if (marker == null) { + if (currentEvent.markerName != null) { + fail("Incorrect marker. Expected " + currentEvent.markerName + ", actual is null"); + } + } else { + if (currentEvent.markerName == null) { + fail("Incorrect marker. Expected null. Actual is " + marker.getName()); + } else { + assertEquals(currentEvent.markerName, marker.getName(), + "Incorrect marker. Expected " + currentEvent.markerName + ", actual " + marker.getName()); + } + } + if (data == null) { + if (currentEvent.data != null) { + fail("Incorrect message. Expected " + currentEvent.data + ", actual is null"); + } + } else { + if (currentEvent.data == null) { + fail("Incorrect message. Expected null. Actual is " + data.getFormattedMessage()); + } else { + assertTrue( + data.getClass().isAssignableFrom(currentEvent.data.getClass()), + "Incorrect message type. Expected " + currentEvent.data + ", actual " + data); + assertEquals(currentEvent.data.getFormattedMessage(), data.getFormattedMessage(), + "Incorrect message. Expected " + currentEvent.data.getFormattedMessage() + ", actual " + + data.getFormattedMessage()); + } + } + if (t == null) { + if (currentEvent.t != null) { + fail("Incorrect Throwable. Expected " + currentEvent.t + ", actual is null"); + } + } else { + if (currentEvent.t == null) { + fail("Incorrect Throwable. Expected null. Actual is " + t); + } else { + assertEquals(currentEvent.t, t, "Incorrect Throwable. Expected " + currentEvent.t + ", actual " + t); + } + } + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final CharSequence data, final Throwable t) { + charSeqCount++; + return isEnabled(level, marker, (Message) new SimpleMessage(data), t); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) { + objectCount++; + return isEnabled(level, marker, new ObjectMessage(data), t); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String data) { + return isEnabled(level, marker, (Message) new SimpleMessage(data), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) { + return isEnabled(level, marker, new ParameterizedMessage(data, p1), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2, final Object p3) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2, final Object p3, + final Object p4) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8), null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, + final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9), + null); + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) { + return isEnabled(level, marker, (Message) new SimpleMessage(data), t); + } + + @Override + public void logMessage(final String fqcn, final Level level, final Marker marker, final Message data, final Throwable t) { + assertEquals(level, currentLevel, "Incorrect Level. Expected " + currentLevel + ", actual " + level); + if (marker == null) { + if (currentEvent.markerName != null) { + fail("Incorrect marker. Expected " + currentEvent.markerName + ", actual is null"); + } + } else { + if (currentEvent.markerName == null) { + fail("Incorrect marker. Expected null. Actual is " + marker.getName()); + } else { + assertEquals(currentEvent.markerName, marker.getName(), + "Incorrect marker. Expected " + currentEvent.markerName + ", actual " + marker.getName()); + } + } + if (data == null) { + if (currentEvent.data != null) { + fail("Incorrect message. Expected " + currentEvent.data + ", actual is null"); + } + } else { + if (currentEvent.data == null) { + fail("Incorrect message. Expected null. Actual is " + data.getFormattedMessage()); + } else { + assertTrue( + data.getClass().isAssignableFrom(currentEvent.data.getClass()), + "Incorrect message type. Expected " + currentEvent.data + ", actual " + data); + assertEquals(currentEvent.data.getFormattedMessage(), data.getFormattedMessage(), + "Incorrect message. Expected " + currentEvent.data.getFormattedMessage() + ", actual " + + data.getFormattedMessage()); + } + } + if (t == null) { + if (currentEvent.t != null) { + fail("Incorrect Throwable. Expected " + currentEvent.t + ", actual is null"); + } + } else { + if (currentEvent.t == null) { + fail("Incorrect Throwable. Expected null. Actual is " + t); + } else { + assertEquals(currentEvent.t, t, "Incorrect Throwable. Expected " + currentEvent.t + ", actual " + t); + } + } + } + } + + private static class LogEvent { + String markerName; + Message data; + Throwable t; + + public LogEvent(final String markerName, final Message data, final Throwable t) { + this.markerName = markerName; + this.data = data; + this.t = t; + } + } + + private static class ThrowableExpectingLogger extends AbstractLogger { + private static final long serialVersionUID = -7218195998038685039L; + private final boolean expectingThrowables; + + ThrowableExpectingLogger(final boolean expectingThrowables) { + super("ThrowableExpectingLogger", ParameterizedMessageFactory.INSTANCE); + this.expectingThrowables = expectingThrowables; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) { + return true; + } + + @Override + public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) { + return true; + } + + @Override + public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { + if(expectingThrowables) { + assertNotNull(t, "Expected a Throwable but received null!"); + } else { + assertNull(t, "Expected null but received a Throwable! "+t); + } + if (message != null) { + message.getFormattedMessage(); + } + } + + @Override + public Level getLevel() { + return Level.INFO; + } + } + + private static class ThrowableMessage implements Message { + private static final long serialVersionUID = 1L; + private final Throwable throwable; + + public ThrowableMessage(final Throwable throwable) { + this.throwable = throwable; + } + + @Override + public String getFormattedMessage() { + return null; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public Object[] getParameters() { + return new Object[0]; + } + + @Override + public Throwable getThrowable() { + return throwable; + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java new file mode 100644 index 00000000000..bc92763e1f9 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests {@link CloseableThreadContext}. + * + * @since 2.6 + */ +@ReadsSystemProperty +@UsingAnyThreadContext +public class CloseableThreadContextTest { + + private final String key = "key"; + private final String value = "value"; + + @Test + public void shouldAddAnEntryToTheMap() { + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + } + } + + @Test + public void shouldAddTwoEntriesToTheMap() { + final String key2 = "key2"; + final String value2 = "value2"; + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value).put(key2, value2)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + assertEquals(value2, ThreadContext.get(key2)); + } + } + + @Test + public void shouldNestEntries() { + final String oldValue = "oldValue"; + final String innerValue = "innerValue"; + ThreadContext.put(key, oldValue); + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + try (final CloseableThreadContext.Instance ignored2 = CloseableThreadContext.put(key, innerValue)) { + assertNotNull(ignored2); + assertEquals(innerValue, ThreadContext.get(key)); + } + assertEquals(value, ThreadContext.get(key)); + } + assertEquals(oldValue, ThreadContext.get(key)); + } + + @Test + public void shouldPreserveOldEntriesFromTheMapWhenAutoClosed() { + final String oldValue = "oldValue"; + ThreadContext.put(key, oldValue); + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + } + assertEquals(oldValue, ThreadContext.get(key)); + } + + @Test + public void ifTheSameKeyIsAddedTwiceTheOriginalShouldBeUsed() { + final String oldValue = "oldValue"; + final String secondValue = "innerValue"; + ThreadContext.put(key, oldValue); + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value).put(key, secondValue)) { + assertNotNull(ignored); + assertEquals(secondValue, ThreadContext.get(key)); + } + assertEquals(oldValue, ThreadContext.get(key)); + } + + @Test + public void shouldPushAndPopAnEntryToTheStack() { + final String message = "message"; + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.push(message)) { + assertNotNull(ignored); + assertEquals(message, ThreadContext.peek()); + } + assertEquals("", ThreadContext.peek()); + } + + @Test + public void shouldPushAndPopTwoEntriesToTheStack() { + final String message1 = "message1"; + final String message2 = "message2"; + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.push(message1).push(message2)) { + assertNotNull(ignored); + assertEquals(message2, ThreadContext.peek()); + } + assertEquals("", ThreadContext.peek()); + } + + @Test + public void shouldPushAndPopAParameterizedEntryToTheStack() { + final String parameterizedMessage = "message {}"; + final String parameterizedMessageParameter = "param"; + final String formattedMessage = parameterizedMessage.replace("{}", parameterizedMessageParameter); + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.push(parameterizedMessage, + parameterizedMessageParameter)) { + assertNotNull(ignored); + assertEquals(formattedMessage, ThreadContext.peek()); + } + assertEquals("", ThreadContext.peek()); + } + + @Test + public void shouldRemoveAnEntryFromTheMapWhenAutoClosed() { + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + } + assertFalse(ThreadContext.containsKey(key)); + } + + @Test + public void shouldAddEntriesToBothStackAndMap() { + final String stackValue = "something"; + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value).push(stackValue)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + assertEquals(stackValue, ThreadContext.peek()); + } + assertFalse(ThreadContext.containsKey(key)); + assertEquals("", ThreadContext.peek()); + } + + @Test + public void canReuseCloseableThreadContext() { + final String stackValue = "something"; + // Create a ctc and close it + final CloseableThreadContext.Instance ctc = CloseableThreadContext.push(stackValue).put(key, value); + assertNotNull(ctc); + assertEquals(value, ThreadContext.get(key)); + assertEquals(stackValue, ThreadContext.peek()); + ctc.close(); + + assertFalse(ThreadContext.containsKey(key)); + assertEquals("", ThreadContext.peek()); + + final String anotherKey = "key2"; + final String anotherValue = "value2"; + final String anotherStackValue = "something else"; + // Use it again + ctc.push(anotherStackValue).put(anotherKey, anotherValue); + assertEquals(anotherValue, ThreadContext.get(anotherKey)); + assertEquals(anotherStackValue, ThreadContext.peek()); + ctc.close(); + + assertFalse(ThreadContext.containsKey(anotherKey)); + assertEquals("", ThreadContext.peek()); + } + + @Test + public void closeIsIdempotent() { + + final String originalMapValue = "map to keep"; + final String originalStackValue = "stack to keep"; + ThreadContext.put(key, originalMapValue); + ThreadContext.push(originalStackValue); + + final String newMapValue = "temp map value"; + final String newStackValue = "temp stack to keep"; + final CloseableThreadContext.Instance ctc = CloseableThreadContext.push(newStackValue).put(key, newMapValue); + assertNotNull(ctc); + + ctc.close(); + assertEquals(originalMapValue, ThreadContext.get(key)); + assertEquals(originalStackValue, ThreadContext.peek()); + + ctc.close(); + assertEquals(originalMapValue, ThreadContext.get(key)); + assertEquals(originalStackValue, ThreadContext.peek()); + } + + @Test + public void putAllWillPutAllValues() { + + final String oldValue = "oldValue"; + ThreadContext.put(key, oldValue); + + final Map valuesToPut = new HashMap<>(); + valuesToPut.put(key, value); + + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.putAll(valuesToPut)) { + assertNotNull(ignored); + assertEquals(value, ThreadContext.get(key)); + } + assertEquals(oldValue, ThreadContext.get(key)); + + } + + @Test + public void pushAllWillPushAllValues() { + + ThreadContext.push(key); + final List messages = ThreadContext.getImmutableStack().asList(); + ThreadContext.pop(); + + try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.pushAll(messages)) { + assertNotNull(ignored); + assertEquals(key, ThreadContext.peek()); + } + assertEquals("", ThreadContext.peek()); + + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/EventLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/EventLoggerTest.java similarity index 80% rename from log4j-api/src/test/java/org/apache/logging/log4j/EventLoggerTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/EventLoggerTest.java index 181a34481ed..c8d504c20ec 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/EventLoggerTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/EventLoggerTest.java @@ -16,26 +16,25 @@ */ package org.apache.logging.log4j; -import java.util.List; -import java.util.Locale; - import org.apache.logging.log4j.message.StructuredDataMessage; -import org.junit.Before; -import org.junit.Test; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; -import static org.hamcrest.CoreMatchers.*; +import java.util.List; +import java.util.Locale; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.assertThat; -/** - * - */ +@UsingThreadContextMap public class EventLoggerTest { TestLogger logger = (TestLogger) LogManager.getLogger("EventLogger"); List results = logger.getEntries(); - @Before + @BeforeEach public void setup() { results.clear(); } @@ -51,9 +50,9 @@ public void structuredData() { msg.put("Amount", "200.00"); EventLogger.logEvent(msg); ThreadContext.clearMap(); - assertEquals(1, results.size()); + assertThat(results).hasSize(1); final String expected = "EVENT OFF Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"] Transfer Complete"; - assertThat("Incorrect structured data", results.get(0), startsWith(expected)); + assertThat(results.get(0)).startsWith(expected); } } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/LambdaLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LambdaLoggerTest.java similarity index 98% rename from log4j-api/src/test/java/org/apache/logging/log4j/LambdaLoggerTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/LambdaLoggerTest.java index f6d1623d682..c0587edad40 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/LambdaLoggerTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LambdaLoggerTest.java @@ -17,17 +17,18 @@ package org.apache.logging.log4j; -import java.util.ArrayList; -import java.util.List; - import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ReusableMessage; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.util.Supplier; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the AbstractLogger implementation of the Logger2 interface. @@ -46,7 +47,9 @@ public LogEvent(final String fqcn, final Level level, final Marker marker, final this.fqcn = fqcn; this.level = level; this.marker = marker; - this.message = message; + this.message = (message instanceof ReusableMessage) ? + ((ReusableMessage) message).memento() : + message; this.throwable = t; } } @@ -206,10 +209,10 @@ public String get() { final MySupplier supplier = new MySupplier(); final MySupplier supplier2 = new MySupplier(); - final Supplier[] supplierArray1 = new Supplier[] {supplier}; - final Supplier[] supplierArray2 = new Supplier[] {supplier, supplier2}; + final Supplier[] supplierArray1 = new Supplier[] {supplier}; + final Supplier[] supplierArray2 = new Supplier[] {supplier, supplier2}; - @Before + @BeforeEach public void beforeEachTest() { logger2.list.clear(); supplier.invoked = false; diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/LevelTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LevelTest.java new file mode 100644 index 00000000000..4ecf62c2b8b --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LevelTest.java @@ -0,0 +1,285 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class LevelTest { + + @Test + public void testDefault() { + final Level level = Level.toLevel("Information", Level.ERROR); + assertNotNull(level); + assertEquals(Level.ERROR, level); + } + + @Test + public void testForNameEquals() { + final String name = "Foo"; + final int intValue = 1; + final Level level = Level.forName(name, intValue); + assertNotNull(level); + assertEquals(level, Level.forName(name, intValue)); + assertEquals(level, Level.getLevel(name)); + assertEquals(level, Level.toLevel(name)); + assertEquals(intValue, Level.getLevel(name).intLevel()); + } + + @Test + public void testThrowsOnNull() { + assertThrowsExactly(IllegalArgumentException.class, () -> Level.forName(null, 100)); + assertThrowsExactly(IllegalArgumentException.class, () -> Level.getLevel(null)); + // the intLevel should be checked only if we create a new level + assertNull(Level.getLevel("Bar")); + assertThrowsExactly(IllegalArgumentException.class, () -> Level.forName("Bar", -1)); + } + + @Test + public void testGoodLevels() { + final Level level = Level.toLevel("INFO"); + assertNotNull(level); + assertEquals(Level.INFO, level); + } + + @Test + public void testLevelsWithSpaces() { + Level level = Level.toLevel(" INFO "); + assertNotNull(level); + assertEquals(Level.INFO, level); + + level = Level.valueOf(" INFO "); + assertNotNull(level); + assertEquals(Level.INFO, level); + } + + @Test + public void testIsInRangeErrorToDebug() { + assertFalse(Level.OFF.isInRange(Level.ERROR, Level.DEBUG)); + assertFalse(Level.FATAL.isInRange(Level.ERROR, Level.DEBUG)); + assertTrue(Level.ERROR.isInRange(Level.ERROR, Level.DEBUG)); + assertTrue(Level.WARN.isInRange(Level.ERROR, Level.DEBUG)); + assertTrue(Level.INFO.isInRange(Level.ERROR, Level.DEBUG)); + assertTrue(Level.DEBUG.isInRange(Level.ERROR, Level.DEBUG)); + assertFalse(Level.TRACE.isInRange(Level.ERROR, Level.DEBUG)); + assertFalse(Level.ALL.isInRange(Level.ERROR, Level.DEBUG)); + } + + @Test + public void testIsInRangeFatalToTrace() { + assertFalse(Level.OFF.isInRange(Level.FATAL, Level.TRACE)); + assertTrue(Level.FATAL.isInRange(Level.FATAL, Level.TRACE)); + assertTrue(Level.ERROR.isInRange(Level.FATAL, Level.TRACE)); + assertTrue(Level.WARN.isInRange(Level.FATAL, Level.TRACE)); + assertTrue(Level.INFO.isInRange(Level.FATAL, Level.TRACE)); + assertTrue(Level.DEBUG.isInRange(Level.FATAL, Level.TRACE)); + assertTrue(Level.TRACE.isInRange(Level.FATAL, Level.TRACE)); + assertFalse(Level.ALL.isInRange(Level.FATAL, Level.TRACE)); + } + + @Test + public void testIsInRangeOffToAll() { + assertTrue(Level.OFF.isInRange(Level.OFF, Level.ALL)); + assertTrue(Level.FATAL.isInRange(Level.OFF, Level.ALL)); + assertTrue(Level.ERROR.isInRange(Level.OFF, Level.ALL)); + assertTrue(Level.WARN.isInRange(Level.OFF, Level.ALL)); + assertTrue(Level.INFO.isInRange(Level.OFF, Level.ALL)); + assertTrue(Level.DEBUG.isInRange(Level.OFF, Level.ALL)); + assertTrue(Level.TRACE.isInRange(Level.OFF, Level.ALL)); + assertTrue(Level.ALL.isInRange(Level.OFF, Level.ALL)); + } + + @Test + public void testIsInRangeSameLevels() { + // Level.OFF + assertTrue(Level.OFF.isInRange(Level.OFF, Level.OFF)); + assertFalse(Level.OFF.isInRange(Level.FATAL, Level.FATAL)); + assertFalse(Level.OFF.isInRange(Level.ERROR, Level.ERROR)); + assertFalse(Level.OFF.isInRange(Level.WARN, Level.WARN)); + assertFalse(Level.OFF.isInRange(Level.INFO, Level.INFO)); + assertFalse(Level.OFF.isInRange(Level.DEBUG, Level.DEBUG)); + assertFalse(Level.OFF.isInRange(Level.TRACE, Level.TRACE)); + assertFalse(Level.OFF.isInRange(Level.ALL, Level.ALL)); + // Level.FATAL + assertFalse(Level.FATAL.isInRange(Level.OFF, Level.OFF)); + assertTrue(Level.FATAL.isInRange(Level.FATAL, Level.FATAL)); + assertFalse(Level.FATAL.isInRange(Level.ERROR, Level.ERROR)); + assertFalse(Level.FATAL.isInRange(Level.WARN, Level.WARN)); + assertFalse(Level.FATAL.isInRange(Level.INFO, Level.INFO)); + assertFalse(Level.FATAL.isInRange(Level.DEBUG, Level.DEBUG)); + assertFalse(Level.FATAL.isInRange(Level.TRACE, Level.TRACE)); + assertFalse(Level.FATAL.isInRange(Level.ALL, Level.ALL)); + // Level.ERROR + assertFalse(Level.ERROR.isInRange(Level.OFF, Level.OFF)); + assertFalse(Level.ERROR.isInRange(Level.FATAL, Level.FATAL)); + assertTrue(Level.ERROR.isInRange(Level.ERROR, Level.ERROR)); + assertFalse(Level.ERROR.isInRange(Level.WARN, Level.WARN)); + assertFalse(Level.ERROR.isInRange(Level.INFO, Level.INFO)); + assertFalse(Level.ERROR.isInRange(Level.DEBUG, Level.DEBUG)); + assertFalse(Level.ERROR.isInRange(Level.TRACE, Level.TRACE)); + assertFalse(Level.ERROR.isInRange(Level.ALL, Level.ALL)); + // Level.WARN + assertFalse(Level.WARN.isInRange(Level.OFF, Level.OFF)); + assertFalse(Level.WARN.isInRange(Level.FATAL, Level.FATAL)); + assertFalse(Level.WARN.isInRange(Level.ERROR, Level.ERROR)); + assertTrue(Level.WARN.isInRange(Level.WARN, Level.WARN)); + assertFalse(Level.WARN.isInRange(Level.INFO, Level.INFO)); + assertFalse(Level.WARN.isInRange(Level.DEBUG, Level.DEBUG)); + assertFalse(Level.WARN.isInRange(Level.TRACE, Level.TRACE)); + assertFalse(Level.WARN.isInRange(Level.ALL, Level.ALL)); + // Level.INFO + assertFalse(Level.INFO.isInRange(Level.OFF, Level.OFF)); + assertFalse(Level.INFO.isInRange(Level.FATAL, Level.FATAL)); + assertFalse(Level.INFO.isInRange(Level.ERROR, Level.ERROR)); + assertFalse(Level.INFO.isInRange(Level.WARN, Level.WARN)); + assertTrue(Level.INFO.isInRange(Level.INFO, Level.INFO)); + assertFalse(Level.INFO.isInRange(Level.DEBUG, Level.DEBUG)); + assertFalse(Level.INFO.isInRange(Level.TRACE, Level.TRACE)); + assertFalse(Level.INFO.isInRange(Level.ALL, Level.ALL)); + // Level.DEBUG + assertFalse(Level.DEBUG.isInRange(Level.OFF, Level.OFF)); + assertFalse(Level.DEBUG.isInRange(Level.FATAL, Level.FATAL)); + assertFalse(Level.DEBUG.isInRange(Level.ERROR, Level.ERROR)); + assertFalse(Level.DEBUG.isInRange(Level.WARN, Level.WARN)); + assertFalse(Level.DEBUG.isInRange(Level.INFO, Level.INFO)); + assertTrue(Level.DEBUG.isInRange(Level.DEBUG, Level.DEBUG)); + assertFalse(Level.DEBUG.isInRange(Level.TRACE, Level.TRACE)); + assertFalse(Level.DEBUG.isInRange(Level.ALL, Level.ALL)); + // Level.TRACE + assertFalse(Level.TRACE.isInRange(Level.OFF, Level.OFF)); + assertFalse(Level.TRACE.isInRange(Level.FATAL, Level.FATAL)); + assertFalse(Level.TRACE.isInRange(Level.ERROR, Level.ERROR)); + assertFalse(Level.TRACE.isInRange(Level.WARN, Level.WARN)); + assertFalse(Level.TRACE.isInRange(Level.INFO, Level.INFO)); + assertFalse(Level.TRACE.isInRange(Level.DEBUG, Level.DEBUG)); + assertTrue(Level.TRACE.isInRange(Level.TRACE, Level.TRACE)); + assertFalse(Level.TRACE.isInRange(Level.ALL, Level.ALL)); + // Level.ALL + assertFalse(Level.ALL.isInRange(Level.OFF, Level.OFF)); + assertFalse(Level.ALL.isInRange(Level.FATAL, Level.FATAL)); + assertFalse(Level.ALL.isInRange(Level.ERROR, Level.ERROR)); + assertFalse(Level.ALL.isInRange(Level.WARN, Level.WARN)); + assertFalse(Level.ALL.isInRange(Level.INFO, Level.INFO)); + assertFalse(Level.ALL.isInRange(Level.DEBUG, Level.DEBUG)); + assertFalse(Level.ALL.isInRange(Level.TRACE, Level.TRACE)); + assertTrue(Level.ALL.isInRange(Level.ALL, Level.ALL)); + } + + @Test + public void testIsInRangeWarnToInfo() { + assertFalse(Level.OFF.isInRange(Level.WARN, Level.INFO)); + assertFalse(Level.FATAL.isInRange(Level.WARN, Level.INFO)); + assertFalse(Level.ERROR.isInRange(Level.WARN, Level.INFO)); + assertTrue(Level.WARN.isInRange(Level.WARN, Level.INFO)); + assertTrue(Level.INFO.isInRange(Level.WARN, Level.INFO)); + assertFalse(Level.DEBUG.isInRange(Level.WARN, Level.INFO)); + assertFalse(Level.TRACE.isInRange(Level.WARN, Level.INFO)); + assertFalse(Level.ALL.isInRange(Level.WARN, Level.INFO)); + } + + @Test + public void testIsLessSpecificThan() { + // Level.OFF + assertTrue(Level.OFF.isLessSpecificThan(Level.OFF)); + assertFalse(Level.OFF.isLessSpecificThan(Level.FATAL)); + assertFalse(Level.OFF.isLessSpecificThan(Level.ERROR)); + assertFalse(Level.OFF.isLessSpecificThan(Level.WARN)); + assertFalse(Level.OFF.isLessSpecificThan(Level.INFO)); + assertFalse(Level.OFF.isLessSpecificThan(Level.DEBUG)); + assertFalse(Level.OFF.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.OFF.isLessSpecificThan(Level.ALL)); + // Level.FATAL + assertTrue(Level.FATAL.isLessSpecificThan(Level.OFF)); + assertTrue(Level.FATAL.isLessSpecificThan(Level.FATAL)); + assertFalse(Level.FATAL.isLessSpecificThan(Level.ERROR)); + assertFalse(Level.FATAL.isLessSpecificThan(Level.WARN)); + assertFalse(Level.FATAL.isLessSpecificThan(Level.INFO)); + assertFalse(Level.FATAL.isLessSpecificThan(Level.DEBUG)); + assertFalse(Level.FATAL.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.FATAL.isLessSpecificThan(Level.ALL)); + // Level.ERROR + assertTrue(Level.ERROR.isLessSpecificThan(Level.OFF)); + assertTrue(Level.ERROR.isLessSpecificThan(Level.FATAL)); + assertTrue(Level.ERROR.isLessSpecificThan(Level.ERROR)); + assertFalse(Level.ERROR.isLessSpecificThan(Level.WARN)); + assertFalse(Level.ERROR.isLessSpecificThan(Level.INFO)); + assertFalse(Level.ERROR.isLessSpecificThan(Level.DEBUG)); + assertFalse(Level.ERROR.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.ERROR.isLessSpecificThan(Level.ALL)); + // Level.ERROR + assertTrue(Level.WARN.isLessSpecificThan(Level.OFF)); + assertTrue(Level.WARN.isLessSpecificThan(Level.FATAL)); + assertTrue(Level.WARN.isLessSpecificThan(Level.ERROR)); + assertTrue(Level.WARN.isLessSpecificThan(Level.WARN)); + assertFalse(Level.WARN.isLessSpecificThan(Level.INFO)); + assertFalse(Level.WARN.isLessSpecificThan(Level.DEBUG)); + assertFalse(Level.WARN.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.WARN.isLessSpecificThan(Level.ALL)); + // Level.WARN + assertTrue(Level.WARN.isLessSpecificThan(Level.OFF)); + assertTrue(Level.WARN.isLessSpecificThan(Level.FATAL)); + assertTrue(Level.WARN.isLessSpecificThan(Level.ERROR)); + assertTrue(Level.WARN.isLessSpecificThan(Level.WARN)); + assertFalse(Level.WARN.isLessSpecificThan(Level.INFO)); + assertFalse(Level.WARN.isLessSpecificThan(Level.DEBUG)); + assertFalse(Level.WARN.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.WARN.isLessSpecificThan(Level.ALL)); + // Level.INFO + assertTrue(Level.INFO.isLessSpecificThan(Level.OFF)); + assertTrue(Level.INFO.isLessSpecificThan(Level.FATAL)); + assertTrue(Level.INFO.isLessSpecificThan(Level.ERROR)); + assertTrue(Level.INFO.isLessSpecificThan(Level.WARN)); + assertTrue(Level.INFO.isLessSpecificThan(Level.INFO)); + assertFalse(Level.INFO.isLessSpecificThan(Level.DEBUG)); + assertFalse(Level.INFO.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.INFO.isLessSpecificThan(Level.ALL)); + // Level.DEBUG + assertTrue(Level.DEBUG.isLessSpecificThan(Level.OFF)); + assertTrue(Level.DEBUG.isLessSpecificThan(Level.FATAL)); + assertTrue(Level.DEBUG.isLessSpecificThan(Level.ERROR)); + assertTrue(Level.DEBUG.isLessSpecificThan(Level.WARN)); + assertTrue(Level.DEBUG.isLessSpecificThan(Level.INFO)); + assertTrue(Level.DEBUG.isLessSpecificThan(Level.DEBUG)); + assertFalse(Level.DEBUG.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.DEBUG.isLessSpecificThan(Level.ALL)); + // Level.TRACE + assertTrue(Level.TRACE.isLessSpecificThan(Level.OFF)); + assertTrue(Level.TRACE.isLessSpecificThan(Level.FATAL)); + assertTrue(Level.TRACE.isLessSpecificThan(Level.ERROR)); + assertTrue(Level.TRACE.isLessSpecificThan(Level.WARN)); + assertTrue(Level.TRACE.isLessSpecificThan(Level.INFO)); + assertTrue(Level.TRACE.isLessSpecificThan(Level.DEBUG)); + assertTrue(Level.TRACE.isLessSpecificThan(Level.TRACE)); + assertFalse(Level.TRACE.isLessSpecificThan(Level.ALL)); + // Level.ALL + assertTrue(Level.ALL.isLessSpecificThan(Level.OFF)); + assertTrue(Level.ALL.isLessSpecificThan(Level.FATAL)); + assertTrue(Level.ALL.isLessSpecificThan(Level.ERROR)); + assertTrue(Level.ALL.isLessSpecificThan(Level.WARN)); + assertTrue(Level.ALL.isLessSpecificThan(Level.INFO)); + assertTrue(Level.ALL.isLessSpecificThan(Level.DEBUG)); + assertTrue(Level.ALL.isLessSpecificThan(Level.TRACE)); + assertTrue(Level.ALL.isLessSpecificThan(Level.ALL)); + } + +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/LogManagerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LogManagerTest.java new file mode 100644 index 00000000000..845eb9b9b3d --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LogManagerTest.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.spi.LoggerContext; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import java.io.Closeable; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ReadsSystemProperty +public class LogManagerTest { + + @SuppressWarnings("InnerClassMayBeStatic") + class Inner { + final Logger LOGGER = LogManager.getLogger(); + } + + @SuppressWarnings("InnerClassMayBeStatic") + class InnerByClass { + final Logger LOGGER = LogManager.getLogger(InnerByClass.class); + } + + static class StaticInner { + final static Logger LOGGER = LogManager.getLogger(); + } + + static class StaticInnerByClass { + final static Logger LOGGER = LogManager.getLogger(StaticInnerByClass.class); + } + + @Test + public void testGetLogger() { + Logger logger = LogManager.getLogger(); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + logger = LogManager.getLogger(ParameterizedMessageFactory.INSTANCE); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + logger = LogManager.getLogger((Class) null); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + logger = LogManager.getLogger((Class) null, ParameterizedMessageFactory.INSTANCE); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + logger = LogManager.getLogger((String) null); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + logger = LogManager.getLogger((String) null, ParameterizedMessageFactory.INSTANCE); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + logger = LogManager.getLogger((Object) null); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + logger = LogManager.getLogger((Object) null, ParameterizedMessageFactory.INSTANCE); + assertNotNull(logger, "No Logger returned"); + assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName()); + } + + @Test + public void testGetLoggerForAnonymousInnerClass1() throws IOException { + final Closeable closeable = new Closeable() { + + final Logger LOGGER = LogManager.getLogger(); + + @Override + public void close() throws IOException { + assertEquals("org.apache.logging.log4j.LogManagerTest$1", LOGGER.getName()); + } + }; + closeable.close(); + } + + @Test + public void testGetLoggerForAnonymousInnerClass2() throws IOException { + final Closeable closeable = new Closeable() { + + final Logger LOGGER = LogManager.getLogger(getClass()); + + @Override + public void close() throws IOException { + assertEquals("org.apache.logging.log4j.LogManagerTest$2", LOGGER.getName()); + } + }; + closeable.close(); + } + + @Test + public void testGetLoggerForInner() { + assertEquals("org.apache.logging.log4j.LogManagerTest.Inner", new Inner().LOGGER.getName()); + } + + @Test + public void testGetLoggerForInnerByClass() { + assertEquals("org.apache.logging.log4j.LogManagerTest.InnerByClass", new InnerByClass().LOGGER.getName()); + } + + @Test + public void testGetLoggerForStaticInner() { + assertEquals("org.apache.logging.log4j.LogManagerTest.StaticInner", StaticInner.LOGGER.getName()); + } + + @Test + public void testGetLoggerForStaticInnerByClass() { + assertEquals("org.apache.logging.log4j.LogManagerTest.StaticInnerByClass", StaticInnerByClass.LOGGER.getName()); + } + + @Test + public void testShutdown() { + final LoggerContext loggerContext = LogManager.getContext(false); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerSupplierTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerSupplierTest.java new file mode 100644 index 00000000000..81111808cac --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerSupplierTest.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import java.util.List; +import java.util.Locale; +import java.util.Properties; + +import org.apache.logging.log4j.message.FormattedMessage; +import org.apache.logging.log4j.message.JsonMessage; +import org.apache.logging.log4j.message.LocalizedMessage; +import org.apache.logging.log4j.message.MessageFormatMessage; +import org.apache.logging.log4j.message.ObjectArrayMessage; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.StringFormattedMessage; +import org.apache.logging.log4j.message.ThreadDumpMessage; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.apache.logging.log4j.util.Supplier; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests Logger APIs with {@link Supplier}. + */ +@ResourceLock(Resources.LOCALE) +@UsingThreadContextMap +public class LoggerSupplierTest { + + private final TestLogger logger = (TestLogger) LogManager.getLogger(LoggerSupplierTest.class); + + private final List results = logger.getEntries(); + + Locale defaultLocale; + + @Test + public void flowTracing_SupplierOfFormattedMessage() { + logger.traceEntry(() -> new FormattedMessage("int foo={}", 1234567890)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1234567890)") + .doesNotContain("FormattedMessage"); + } + + @Test + public void flowTracing_SupplierOfJsonMessage() { + Properties props = new Properties(); + props.setProperty("foo", "bar"); + logger.traceEntry(() -> new JsonMessage(props)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("\"foo\":\"bar\"") + .doesNotContain("JsonMessage"); + } + + @Test + public void flowTracing_SupplierOfLocalizedMessage() { + logger.traceEntry(() -> new LocalizedMessage("int foo={}", 1234567890)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1234567890)") + .doesNotContain("LocalizedMessage"); + } + + @Test + public void flowTracing_SupplierOfLong() { + logger.traceEntry(() -> 1234567890L); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("SimpleMessage"); + } + + @Test + public void flowTracing_SupplierOfMessageFormatMessage() { + logger.traceEntry(() -> new MessageFormatMessage("int foo={0}", 1234567890)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1,234,567,890)") + .doesNotContain("MessageFormatMessage"); + } + + @Test + public void flowTracing_SupplierOfObjectArrayMessage() { + logger.traceEntry(() -> new ObjectArrayMessage(1234567890)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("([1234567890])") + .doesNotContain("ObjectArrayMessage"); + } + + @Test + public void flowTracing_SupplierOfObjectMessage() { + logger.traceEntry(() -> new ObjectMessage(1234567890)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("ObjectMessage"); + } + + @Test + public void flowTracing_SupplierOfParameterizedMessage() { + logger.traceEntry(() -> new ParameterizedMessage("int foo={}", 1234567890)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1234567890)") + .doesNotContain("ParameterizedMessage"); + } + + @Test + public void flowTracing_SupplierOfSimpleMessage() { + logger.traceEntry(() -> new SimpleMessage("1234567890")); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("SimpleMessage"); + } + + @Test + public void flowTracing_SupplierOfString() { + logger.traceEntry(() -> "1234567890"); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(1234567890)") + .doesNotContain("SimpleMessage"); + } + + @Test + public void flowTracing_SupplierOfStringFormattedMessage() { + logger.traceEntry(() -> new StringFormattedMessage("int foo=%,d", 1234567890)); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter") + .contains("(int foo=1,234,567,890)") + .doesNotContain("StringFormattedMessage"); + } + + @Test + public void flowTracing_SupplierOfThreadDumpMessage() { + logger.traceEntry(() -> new ThreadDumpMessage("Title of ...")); + assertThat(results).hasSize(1); + String entry = results.get(0); + assertThat(entry).startsWith("ENTER[ FLOW ] TRACE Enter").contains("RUNNABLE", "Title of ...", getClass().getName()); + } + + @BeforeEach + public void setup() { + results.clear(); + defaultLocale = Locale.getDefault(Locale.Category.FORMAT); + Locale.setDefault(Locale.Category.FORMAT, java.util.Locale.US); + } + + @AfterEach + public void tearDown() { + Locale.setDefault(Locale.Category.FORMAT, defaultLocale); + } + +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java new file mode 100644 index 00000000000..a59505acb39 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java @@ -0,0 +1,648 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Properties; + +import org.apache.logging.log4j.message.EntryMessage; +import org.apache.logging.log4j.message.JsonMessage; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.SimpleMessageFactory; +import org.apache.logging.log4j.message.StringFormatterMessageFactory; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.junit.Resources; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.Supplier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ResourceLock(value = Resources.MARKER_MANAGER, mode = ResourceAccessMode.READ) +@ReadsSystemProperty +public class LoggerTest { + + private static class TestParameterizedMessageFactory { + // empty + } + + private static class TestStringFormatterMessageFactory { + // empty + } + + private final TestLogger logger = (TestLogger) LogManager.getLogger(LoggerTest.class); + private final Marker marker = MarkerManager.getMarker("test"); + private final List results = logger.getEntries(); + + @Test + public void builder() { + logger.atDebug().withLocation().log("Hello"); + logger.atError().withMarker(marker).log("Hello {}", "John"); + logger.atWarn().withThrowable(new Throwable("This is a test")).log((Message) new SimpleMessage("Log4j rocks!")); + assertEquals(3, results.size()); + assertThat("Incorrect message 1", results.get(0), + equalTo(" DEBUG org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:72) Hello")); + assertThat("Incorrect message 2", results.get(1), equalTo("test ERROR Hello John")); + assertThat("Incorrect message 3", results.get(2), + startsWith(" WARN Log4j rocks! java.lang.Throwable: This is a test")); + assertThat("Throwable incorrect in message 3", results.get(2), + containsString("org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:74)")); + } + + @Test + public void basicFlow() { + logger.traceEntry(); + logger.traceExit(); + assertEquals(2, results.size()); + assertThat(results.get(0)).isEqualTo("ENTER[ FLOW ] TRACE Enter"); + assertThat(results.get(1)).isEqualTo("EXIT[ FLOW ] TRACE Exit"); + + } + + @Test + public void flowTracingMessage() { + Properties props = new Properties(); + props.setProperty("foo", "bar"); + logger.traceEntry(new JsonMessage(props)); + final Response response = new Response(-1, "Generic error"); + logger.traceExit(new JsonMessage(response), response); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter").contains("\"foo\":\"bar\""); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit").contains("\"message\":\"Generic error\""); + } + + @Test + public void flowTracingString_ObjectArray1() { + logger.traceEntry("doFoo(a={}, b={})", 1, 2); + logger.traceExit("doFoo(a=1, b=2): {}", 3); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter").contains("doFoo(a=1, b=2)"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit").contains("doFoo(a=1, b=2): 3"); + } + + @Test + public void flowTracingExitValueOnly() { + logger.traceEntry("doFoo(a={}, b={})", 1, 2); + logger.traceExit(3); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter").contains("doFoo(a=1, b=2)"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit").contains("3"); + } + + @Test + public void flowTracingString_ObjectArray2() { + final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", 1, 2); + logger.traceExit(msg, 3); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter").contains("doFoo(a=1, b=2)"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit").contains("doFoo(a=1, b=2): 3"); + } + + @Test + public void flowTracingVoidReturn() { + final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", 1, 2); + logger.traceExit(msg); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter").contains("doFoo(a=1, b=2)"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit").endsWith("doFoo(a=1, b=2)"); + } + + @Test + public void flowTracingNoExitArgs() { + logger.traceEntry(); + logger.traceExit(); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit"); + } + + @Test + public void flowTracingNoArgs() { + final EntryMessage message = logger.traceEntry(); + logger.traceExit(message); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit"); + } + + @Test + public void flowTracingString_SupplierOfObjectMessages() { + final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", new Supplier() { + @Override + public Message get() { + return new ObjectMessage(1); + } + }, new Supplier() { + @Override + public Message get() { + return new ObjectMessage(2); + } + }); + logger.traceExit(msg, 3); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter").contains("doFoo(a=1, b=2)"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit").contains("doFoo(a=1, b=2): 3"); + } + + @Test + public void flowTracingString_SupplierOfStrings() { + final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", new Supplier() { + @Override + public String get() { + return "1"; + } + }, new Supplier() { + @Override + public String get() { + return "2"; + } + }); + logger.traceExit(msg, 3); + assertThat(results).hasSize(2); + assertThat(results.get(0)).startsWith("ENTER[ FLOW ] TRACE Enter").contains("doFoo(a=1, b=2)"); + assertThat(results.get(1)).startsWith("EXIT[ FLOW ] TRACE Exit").contains("doFoo(a=1, b=2): 3"); + } + + @Test + public void flowTracingNoFormat() { + logger.traceEntry(null, 1, "2", new ObjectMessage(3)); + logger.traceExit((String) null, 4); + assertThat(results).hasSize(2); + assertThat(results.get(0)).isEqualTo("ENTER[ FLOW ] TRACE Enter params(1, 2, 3)"); + assertThat(results.get(1)).isEqualTo("EXIT[ FLOW ] TRACE Exit with(4)"); + } + + @Test + public void catching() { + try { + throw new NullPointerException(); + } catch (final Exception e) { + logger.catching(e); + assertEquals(1, results.size()); + assertThat("Incorrect Catching", + results.get(0), startsWith("CATCHING[ EXCEPTION ] ERROR Catching java.lang.NullPointerException")); + } + } + + @Test + public void debug() { + logger.debug("Debug message"); + assertEquals(1, results.size()); + assertTrue(results.get(0).startsWith(" DEBUG Debug message"), "Incorrect message"); + } + + @Test + public void debugObject() { + logger.debug(new Date()); + assertEquals(1, results.size()); + assertTrue(results.get(0).length() > 7, "Invalid length"); + } + + @Test + public void debugWithParms() { + logger.debug("Hello, {}", "World"); + assertEquals(1, results.size()); + assertTrue(results.get(0).startsWith(" DEBUG Hello, World"), "Incorrect substitution"); + } + + @Test + public void debugWithParmsAndThrowable() { + logger.debug("Hello, {}", "World", new RuntimeException("Test Exception")); + assertEquals(1, results.size()); + assertTrue( + results.get(0).startsWith(" DEBUG Hello, World java.lang.RuntimeException: Test Exception"), + "Unexpected results: " + results.get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getFormatterLogger() { + // The TestLogger logger was already created in an instance variable for this class. + // The message factory is only used when the logger is created. + final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger(); + final TestLogger altLogger = (TestLogger) LogManager.getFormatterLogger(getClass()); + assertEquals(testLogger.getName(), altLogger.getName()); + assertNotNull(testLogger); + assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); + assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getFormatterLogger_Class() { + // The TestLogger logger was already created in an instance variable for this class. + // The message factory is only used when the logger is created. + final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger(TestStringFormatterMessageFactory.class); + assertNotNull(testLogger); + assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); + assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + private static void assertMessageFactoryInstanceOf(MessageFactory factory, final Class cls) { + assertTrue(factory.getClass().isAssignableFrom(cls)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getFormatterLogger_Object() { + // The TestLogger logger was already created in an instance variable for this class. + // The message factory is only used when the logger is created. + final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger(new TestStringFormatterMessageFactory()); + assertNotNull(testLogger); + assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); + assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getFormatterLogger_String() { + final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger("getLogger_String_StringFormatterMessageFactory"); + assertNotNull(testLogger); + assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); + assertEqualMessageFactory(messageFactory, testLogger); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_Class_ParameterizedMessageFactory() { + // The TestLogger logger was already created in an instance variable for this class. + // The message factory is only used when the logger is created. + final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getLogger(TestParameterizedMessageFactory.class, + messageFactory); + assertNotNull(testLogger); + assertEqualMessageFactory(messageFactory, testLogger); + testLogger.debug("{}", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(" DEBUG " + Integer.MAX_VALUE, testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_Class_StringFormatterMessageFactory() { + // The TestLogger logger was already created in an instance variable for this class. + // The message factory is only used when the logger is created. + final TestLogger testLogger = (TestLogger) LogManager.getLogger(TestStringFormatterMessageFactory.class, + StringFormatterMessageFactory.INSTANCE); + assertNotNull(testLogger); + assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_Object_ParameterizedMessageFactory() { + // The TestLogger logger was already created in an instance variable for this class. + // The message factory is only used when the logger is created. + final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getLogger(new TestParameterizedMessageFactory(), + messageFactory); + assertNotNull(testLogger); + assertEqualMessageFactory(messageFactory, testLogger); + testLogger.debug("{}", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(" DEBUG " + Integer.MAX_VALUE, testLogger.getEntries().get(0)); + } + + private void assertEqualMessageFactory(final MessageFactory messageFactory, final TestLogger testLogger) { + MessageFactory actual = testLogger.getMessageFactory(); + assertEquals(messageFactory, actual); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_Object_StringFormatterMessageFactory() { + // The TestLogger logger was already created in an instance variable for this class. + // The message factory is only used when the logger is created. + final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getLogger(new TestStringFormatterMessageFactory(), + messageFactory); + assertNotNull(testLogger); + assertEqualMessageFactory(messageFactory, testLogger); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_String_MessageFactoryMismatch() { + final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_MessageFactoryMismatch", + messageFactory); + assertNotNull(testLogger); + assertEqualMessageFactory(messageFactory, testLogger); + final TestLogger testLogger2 = (TestLogger) LogManager.getLogger("getLogger_String_MessageFactoryMismatch", + ParameterizedMessageFactory.INSTANCE); + assertNotNull(testLogger2); + //TODO: How to test? + //This test context always creates new loggers, other test context impls I tried fail other tests. + //assertEquals(messageFactory, testLogger2.getMessageFactory()); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_String_ParameterizedMessageFactory() { + final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_ParameterizedMessageFactory", + messageFactory); + assertNotNull(testLogger); + assertEqualMessageFactory(messageFactory, testLogger); + testLogger.debug("{}", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(" DEBUG " + Integer.MAX_VALUE, testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_String_SimpleMessageFactory() { + final SimpleMessageFactory messageFactory = SimpleMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_StringFormatterMessageFactory", + messageFactory); + assertNotNull(testLogger); + assertEqualMessageFactory(messageFactory, testLogger); + testLogger.debug("{} %,d {foo}", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(" DEBUG {} %,d {foo}", testLogger.getEntries().get(0)); + } + + @Test + @ResourceLock("log4j2.TestLogger") + public void getLogger_String_StringFormatterMessageFactory() { + final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; + final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_StringFormatterMessageFactory", + messageFactory); + assertNotNull(testLogger); + assertEqualMessageFactory(messageFactory, testLogger); + testLogger.debug("%,d", Integer.MAX_VALUE); + assertEquals(1, testLogger.getEntries().size()); + assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); + } + + @Test + public void getLoggerByClass() { + final Logger classLogger = LogManager.getLogger(LoggerTest.class); + assertNotNull(classLogger); + } + + @Test + public void getLoggerByNullClass() { + // Returns a SimpleLogger + assertNotNull(LogManager.getLogger((Class) null)); + } + + @Test + public void getLoggerByNullObject() { + // Returns a SimpleLogger + assertNotNull(LogManager.getLogger((Object) null)); + } + + @Test + public void getLoggerByNullString() { + // Returns a SimpleLogger + assertNotNull(LogManager.getLogger((String) null)); + } + + @Test + public void getLoggerByObject() { + final Logger classLogger = LogManager.getLogger(this); + assertNotNull(classLogger); + assertEquals(classLogger, LogManager.getLogger(LoggerTest.class)); + } + + @Test + public void getRootLogger() { + assertNotNull(LogManager.getRootLogger()); + assertNotNull(LogManager.getLogger(Strings.EMPTY)); + assertNotNull(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME)); + assertEquals(LogManager.getRootLogger(), LogManager.getLogger(Strings.EMPTY)); + assertEquals(LogManager.getRootLogger(), LogManager.getLogger(LogManager.ROOT_LOGGER_NAME)); + } + + @Test + public void isAllEnabled() { + assertTrue(logger.isEnabled(Level.ALL), "Incorrect level"); + } + + @Test + public void isDebugEnabled() { + assertTrue(logger.isDebugEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.DEBUG), "Incorrect level"); + } + + @Test + public void isErrorEnabled() { + assertTrue(logger.isErrorEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.ERROR), "Incorrect level"); + } + + @Test + public void isFatalEnabled() { + assertTrue(logger.isFatalEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.FATAL), "Incorrect level"); + } + + @Test + public void isInfoEnabled() { + assertTrue(logger.isInfoEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.INFO), "Incorrect level"); + } + + @Test + public void isOffEnabled() { + assertTrue(logger.isEnabled(Level.OFF), "Incorrect level"); + } + + @Test + public void isTraceEnabled() { + assertTrue(logger.isTraceEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.TRACE), "Incorrect level"); + } + + @Test + public void isWarnEnabled() { + assertTrue(logger.isWarnEnabled(), "Incorrect level"); + assertTrue(logger.isEnabled(Level.WARN), "Incorrect level"); + } + + @Test + public void isAllEnabledWithMarker() { + assertTrue(logger.isEnabled(Level.ALL, marker), "Incorrect level"); + } + + @Test + public void isDebugEnabledWithMarker() { + assertTrue(logger.isDebugEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.DEBUG, marker), "Incorrect level"); + } + + @Test + public void isErrorEnabledWithMarker() { + assertTrue(logger.isErrorEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.ERROR, marker), "Incorrect level"); + } + + @Test + public void isFatalEnabledWithMarker() { + assertTrue(logger.isFatalEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.FATAL, marker), "Incorrect level"); + } + + @Test + public void isInfoEnabledWithMarker() { + assertTrue(logger.isInfoEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.INFO, marker), "Incorrect level"); + } + + @Test + public void isOffEnabledWithMarker() { + assertTrue(logger.isEnabled(Level.OFF, marker), "Incorrect level"); + } + + @Test + public void isTraceEnabledWithMarker() { + assertTrue(logger.isTraceEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.TRACE, marker), "Incorrect level"); + } + + @Test + public void isWarnEnabledWithMarker() { + assertTrue(logger.isWarnEnabled(marker), "Incorrect level"); + assertTrue(logger.isEnabled(Level.WARN, marker), "Incorrect level"); + } + + @Test + @UsingThreadContextMap + public void mdc() { + ThreadContext.put("TestYear", Integer.valueOf(2010).toString()); + logger.debug("Debug message"); + String testYear = ThreadContext.get("TestYear"); + assertNotNull(testYear, "Test Year is null"); + assertEquals("2010", testYear, "Incorrect test year: " + testYear); + ThreadContext.clearMap(); + logger.debug("Debug message"); + assertEquals(2, results.size()); + System.out.println("Log line 1: " + results.get(0)); + System.out.println("log line 2: " + results.get(1)); + assertTrue( + results.get(0).startsWith(" DEBUG Debug message {TestYear=2010}"), "Incorrect MDC: " + results.get(0)); + assertTrue( + results.get(1).startsWith(" DEBUG Debug message"), "MDC not cleared?: " + results.get(1)); + } + + @Test + public void printf() { + logger.printf(Level.DEBUG, "Debug message %d", 1); + logger.printf(Level.DEBUG, MarkerManager.getMarker("Test"), "Debug message %d", 2); + assertEquals(2, results.size()); + assertThat("Incorrect message", results.get(0), startsWith(" DEBUG Debug message 1")); + assertThat("Incorrect message", results.get(1), startsWith("Test DEBUG Debug message 2")); + } + + @BeforeEach + public void setup() { + results.clear(); + } + + @Test + public void structuredData() { + ThreadContext.put("loginId", "JohnDoe"); + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + final StructuredDataMessage msg = new StructuredDataMessage("Audit@18060", "Transfer Complete", "Transfer"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + logger.info(MarkerManager.getMarker("EVENT"), msg); + ThreadContext.clearMap(); + assertEquals(1, results.size()); + assertThat("Incorrect structured data: ", results.get(0), startsWith( + "EVENT INFO Transfer [Audit@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"] Transfer Complete")); + } + + @Test + public void throwing() { + logger.throwing(new IllegalArgumentException("Test Exception")); + assertEquals(1, results.size()); + assertThat("Incorrect Throwing", + results.get(0), startsWith("THROWING[ EXCEPTION ] ERROR Throwing java.lang.IllegalArgumentException: Test Exception")); + } + + + private static class Response { + int status; + String message; + + public Response(final int status, final String message) { + this.status = status; + this.message = message; + } + + public int getStatus() { + return status; + } + + public void setStatus(final int status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/MarkerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/MarkerTest.java new file mode 100644 index 00000000000..c07f94f2596 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/MarkerTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.logging.log4j.test.junit.Resources; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; + +@ResourceLock(value = Resources.MARKER_MANAGER, mode = ResourceAccessMode.READ_WRITE) +public class MarkerTest { + + @BeforeEach + public void setUp() { + MarkerManager.clear(); + } + + @Test + public void testGetMarker() { + final Marker expected = MarkerManager.getMarker("A"); + assertNull(expected.getParents()); + } + + @Test + public void testGetMarkerWithParents() { + final Marker expected = MarkerManager.getMarker("A"); + final Marker p1 = MarkerManager.getMarker("P1"); + p1.addParents(MarkerManager.getMarker("PP1")); + final Marker p2 = MarkerManager.getMarker("P2"); + expected.addParents(p1); + expected.addParents(p2); + assertEquals(2, expected.getParents().length); + } + + @Test + public void testHasParents() { + final Marker parent = MarkerManager.getMarker("PARENT"); + final Marker existing = MarkerManager.getMarker("EXISTING"); + assertFalse(existing.hasParents()); + existing.setParents(parent); + assertTrue(existing.hasParents()); + } + + @Test + public void testMarker() { + final Marker parent = MarkerManager.getMarker("PARENT"); + final Marker test1 = MarkerManager.getMarker("TEST1").setParents(parent); + final Marker test2 = MarkerManager.getMarker("TEST2").addParents(parent); + assertTrue(test1.isInstanceOf(parent), "TEST1 is not an instance of PARENT"); + assertTrue(test2.isInstanceOf(parent), "TEST2 is not an instance of PARENT"); + } + + @Test + public void testMultipleParents() { + final Marker parent1 = MarkerManager.getMarker("PARENT1"); + final Marker parent2 = MarkerManager.getMarker("PARENT2"); + final Marker test1 = MarkerManager.getMarker("TEST1").setParents(parent1, parent2); + final Marker test2 = MarkerManager.getMarker("TEST2").addParents(parent1, parent2); + assertTrue(test1.isInstanceOf(parent1), "TEST1 is not an instance of PARENT1"); + assertTrue(test1.isInstanceOf(parent2), "TEST1 is not an instance of PARENT2"); + assertTrue(test2.isInstanceOf(parent1), "TEST2 is not an instance of PARENT1"); + assertTrue(test2.isInstanceOf(parent2), "TEST2 is not an instance of PARENT2"); + } + + @Test + public void testAddToExistingParents() { + final Marker parent = MarkerManager.getMarker("PARENT"); + final Marker existing = MarkerManager.getMarker("EXISTING"); + final Marker test1 = MarkerManager.getMarker("TEST1").setParents(existing); + test1.addParents(parent); + assertTrue(test1.isInstanceOf(parent), "TEST1 is not an instance of PARENT"); + assertTrue(test1.isInstanceOf(existing), "TEST1 is not an instance of EXISTING"); + } + + + @Test + public void testDuplicateParents() { + final Marker parent = MarkerManager.getMarker("PARENT"); + final Marker existing = MarkerManager.getMarker("EXISTING"); + final Marker test1 = MarkerManager.getMarker("TEST1").setParents(existing); + test1.addParents(parent); + final Marker[] parents = test1.getParents(); + test1.addParents(existing); + assertEquals(parents.length, test1.getParents().length, "duplicate add allowed"); + test1.addParents(existing, MarkerManager.getMarker("EXTRA")); + assertEquals(parents.length + 1, test1.getParents().length, "incorrect add"); + assertTrue(test1.isInstanceOf(parent), "TEST1 is not an instance of PARENT"); + assertTrue(test1.isInstanceOf(existing), "TEST1 is not an instance of EXISTING"); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java new file mode 100644 index 00000000000..a93a7cbc30a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.test.junit.InitializesThreadContext; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests {@link ThreadContext}. + */ +public class NoopThreadContextTest { + + @Test + @SetSystemProperty(key = LoggingSystemProperties.THREAD_CONTEXT_DISABLED, value = "true") + @SetSystemProperty(key = LoggingSystemProperties.THREAD_CONTEXT_MAP_DISABLED, value = "true") + @InitializesThreadContext + @UsingThreadContextMap + public void testNoop() { + ThreadContext.put("Test", "Test"); + final String value = ThreadContext.get("Test"); + assertNull(value, "value was saved"); + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TestProvider.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java similarity index 89% rename from log4j-api/src/test/java/org/apache/logging/log4j/TestProvider.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java index f96181a52c8..24d17e04f7f 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TestProvider.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java @@ -17,12 +17,13 @@ package org.apache.logging.log4j; import org.apache.logging.log4j.spi.Provider; +import org.apache.logging.log4j.test.TestLoggerContextFactory; /** * Binding for the Log4j API. */ public class TestProvider extends Provider { public TestProvider() { - super(0, "2.6.0", TestLoggerContextFactory.class); + super(0, "3.0.0", TestLoggerContextFactory.class); } } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java new file mode 100644 index 00000000000..0d31ce33d6b --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import org.apache.logging.log4j.test.ThreadContextUtilityClass; +import org.apache.logging.log4j.test.junit.InitializesThreadContext; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.apache.logging.log4j.test.junit.UsingThreadContextStack; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.apache.logging.log4j.spi.LoggingSystemProperties.THREAD_CONTEXT_MAP_INHERITABLE; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests {@link ThreadContext}. + */ +@SetSystemProperty(key = THREAD_CONTEXT_MAP_INHERITABLE, value = "true") +@InitializesThreadContext +public class ThreadContextInheritanceTest { + + @Test + @UsingThreadContextStack + public void testPush() { + ThreadContext.push("Hello"); + ThreadContext.push("{} is {}", ThreadContextInheritanceTest.class.getSimpleName(), "running"); + assertEquals( + ThreadContext.pop(), "ThreadContextInheritanceTest is running", "Incorrect parameterized stack value"); + assertEquals(ThreadContext.pop(), "Hello", "Incorrect simple stack value"); + } + + @Test + @SetSystemProperty(key = THREAD_CONTEXT_MAP_INHERITABLE, value = "true") + @InitializesThreadContext + public void testInheritanceSwitchedOn() throws Exception { + ThreadContext.put("Greeting", "Hello"); + StringBuilder sb = new StringBuilder(); + TestThread thread = new TestThread(sb); + thread.start(); + thread.join(); + String str = sb.toString(); + assertEquals("Hello", str, "Unexpected ThreadContext value. Expected Hello. Actual " + str); + sb = new StringBuilder(); + thread = new TestThread(sb); + thread.start(); + thread.join(); + str = sb.toString(); + assertEquals("Hello", str, "Unexpected ThreadContext value. Expected Hello. Actual " + str); + } + + @Test + @Tag("performance") + @UsingThreadContextMap + public void perfTest() { + ThreadContextUtilityClass.perfTest(); + } + + @Test + @UsingThreadContextMap + public void testGetContextReturnsEmptyMapIfEmpty() { + ThreadContextUtilityClass.testGetContextReturnsEmptyMapIfEmpty(); + } + + @Test + @UsingThreadContextMap + public void testGetContextReturnsMutableCopy() { + ThreadContextUtilityClass.testGetContextReturnsMutableCopy(); + } + + @Test + @UsingThreadContextMap + public void testGetImmutableContextReturnsEmptyMapIfEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsEmptyMapIfEmpty(); + } + + @Test + @UsingThreadContextMap + public void testGetImmutableContextReturnsImmutableMapIfNonEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfNonEmpty(); + } + + @Test + @UsingThreadContextMap + public void testGetImmutableContextReturnsImmutableMapIfEmpty() { + ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfEmpty(); + } + + @Test + @UsingThreadContextStack + public void testGetImmutableStackReturnsEmptyStackIfEmpty() { + ThreadContextUtilityClass.testGetImmutableStackReturnsEmptyStackIfEmpty(); + } + + @Test + @UsingThreadContextMap + public void testPut() { + ThreadContextUtilityClass.testPut(); + } + + @Test + @UsingThreadContextMap + public void testRemove() { + assertNull(ThreadContext.get("testKey")); + ThreadContext.put("testKey", "testValue"); + assertEquals("testValue", ThreadContext.get("testKey")); + + ThreadContext.remove("testKey"); + assertNull(ThreadContext.get("testKey")); + assertTrue(ThreadContext.isEmpty()); + } + + @Test + @UsingThreadContextMap + public void testContainsKey() { + assertFalse(ThreadContext.containsKey("testKey")); + ThreadContext.put("testKey", "testValue"); + assertTrue(ThreadContext.containsKey("testKey")); + + ThreadContext.remove("testKey"); + assertFalse(ThreadContext.containsKey("testKey")); + } + + private static class TestThread extends Thread { + + private final StringBuilder sb; + + public TestThread(final StringBuilder sb) { + this.sb = sb; + } + + @Override + public void run() { + final String greeting = ThreadContext.get("Greeting"); + if (greeting == null) { + sb.append("null"); + } else { + sb.append(greeting); + } + ThreadContext.clearMap(); + } + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java similarity index 92% rename from log4j-api/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java index 81a2ca1ea8c..82cc261f818 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/TraceLoggingTest.java @@ -26,13 +26,10 @@ import org.apache.logging.log4j.message.ReusableParameterizedMessageTest; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.AbstractLogger; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class TraceLoggingTest extends AbstractLogger { static final StringBuilder CHAR_SEQ = new StringBuilder("CharSeq"); private int charSeqCount; @@ -221,7 +218,7 @@ public boolean isEnabled(final Level level, final Marker marker, final String da @Override public void logMessage(final String fqcn, final Level level, final Marker marker, final Message data, final Throwable t) { - assertTrue("Incorrect Level. Expected " + currentLevel + ", actual " + level, level.equals(currentLevel)); + assertEquals(level, currentLevel, "Incorrect Level. Expected " + currentLevel + ", actual " + level); if (marker == null) { if (currentEvent.markerName != null) { fail("Incorrect marker. Expected " + currentEvent.markerName + ", actual is null"); @@ -230,8 +227,9 @@ public void logMessage(final String fqcn, final Level level, final Marker marker if (currentEvent.markerName == null) { fail("Incorrect marker. Expected null. Actual is " + marker.getName()); } else { - assertTrue("Incorrect marker. Expected " + currentEvent.markerName + ", actual " + - marker.getName(), currentEvent.markerName.equals(marker.getName())); + assertEquals(currentEvent.markerName, marker.getName(), + "Incorrect marker. Expected " + currentEvent.markerName + ", actual " + + marker.getName()); } } if (data == null) { @@ -242,11 +240,12 @@ public void logMessage(final String fqcn, final Level level, final Marker marker if (currentEvent.data == null) { fail("Incorrect message. Expected null. Actual is " + data.getFormattedMessage()); } else { - assertTrue("Incorrect message type. Expected " + currentEvent.data + ", actual " + data, - data.getClass().isAssignableFrom(currentEvent.data.getClass())); - assertTrue("Incorrect message. Expected " + currentEvent.data.getFormattedMessage() + ", actual " + - data.getFormattedMessage(), - currentEvent.data.getFormattedMessage().equals(data.getFormattedMessage())); + assertTrue( + data.getClass().isAssignableFrom(currentEvent.data.getClass()), + "Incorrect message type. Expected " + currentEvent.data + ", actual " + data); + assertEquals(currentEvent.data.getFormattedMessage(), data.getFormattedMessage(), + "Incorrect message. Expected " + currentEvent.data.getFormattedMessage() + ", actual " + + data.getFormattedMessage()); } } if (t == null) { @@ -257,8 +256,7 @@ public void logMessage(final String fqcn, final Level level, final Marker marker if (currentEvent.t == null) { fail("Incorrect Throwable. Expected null. Actual is " + t); } else { - assertTrue("Incorrect Throwable. Expected " + currentEvent.t + ", actual " + t, - currentEvent.t.equals(t)); + assertEquals(currentEvent.t, t, "Incorrect Throwable. Expected " + currentEvent.t + ", actual " + t); } } } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java similarity index 87% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java index 937d6593e51..70f3c73caec 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/FormattedMessageTest.java @@ -23,16 +23,17 @@ import java.io.ObjectOutputStream; import java.util.Locale; -import org.apache.logging.log4j.junit.Mutable; +import org.apache.logging.log4j.test.junit.Mutable; import org.apache.logging.log4j.util.Constants; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * - */ +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public class FormattedMessageTest { private static final String SPACE = Constants.JAVA_MAJOR_VERSION < 9 ? " " : "\u00a0"; @@ -141,7 +142,7 @@ public void testUnsafeWithMutableParams() { // LOG4J2-763 // modify parameter before calling msg.getFormattedMessage param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent param value", "Test message XYZ", actual); + assertEquals("Test message XYZ", actual, "Expected most recent param value"); } @Test @@ -154,9 +155,10 @@ public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 msg.getFormattedMessage(); // freeze the formatted message param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Should use initial param value", "Test message abc", actual); + assertEquals("Test message abc", actual, "Should use initial param value"); } + @SuppressWarnings("BanSerializableRead") @Test public void testSerialization() throws IOException, ClassNotFoundException { final FormattedMessage expected = new FormattedMessage("Msg", "a", "b", "c"); @@ -167,9 +169,9 @@ public void testSerialization() throws IOException, ClassNotFoundException { final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); final ObjectInputStream in = new ObjectInputStream(bais); final FormattedMessage actual = (FormattedMessage) in.readObject(); - Assert.assertEquals(expected, actual); - Assert.assertEquals(expected.getFormat(), actual.getFormat()); - Assert.assertEquals(expected.getFormattedMessage(), actual.getFormattedMessage()); - Assert.assertArrayEquals(expected.getParameters(), actual.getParameters()); + assertEquals(expected, actual); + assertEquals(expected.getFormat(), actual.getFormat()); + assertEquals(expected.getFormattedMessage(), actual.getFormattedMessage()); + assertArrayEquals(expected.getParameters(), actual.getParameters()); } } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/JsonMessage.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/JsonMessage.java similarity index 100% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/JsonMessage.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/JsonMessage.java diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageFactoryTest.java new file mode 100644 index 00000000000..6881cd9232e --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageFactoryTest.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +import java.util.Locale; +import java.util.ResourceBundle; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests {@link LocalizedMessageFactory}. + */ +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +public class LocalizedMessageFactoryTest { + + @Test + public void testMessageMarkersDataNo() { + final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory(ResourceBundle.getBundle("MF", Locale.US)); + final Message message = localizedMessageFactory.newMessage("msg1"); + assertEquals("This is test number {0} with string argument {1}.", message.getFormattedMessage()); + } + + @Test + public void testMessageMarkersNoDataYes() { + final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory(ResourceBundle.getBundle("MF", Locale.US)); + final Message message = localizedMessageFactory.newMessage("msg1", 1, "two"); + assertEquals("This is test number 1 with string argument two.", message.getFormattedMessage()); + } + + @Test + public void testNewMessage() { + final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory(ResourceBundle.getBundle("MF", Locale.US)); + final Message message = localizedMessageFactory.newMessage("hello_world"); + assertEquals("Hello world.", message.getFormattedMessage()); + } + + @Test + @ResourceLock(Resources.LOCALE) + public void testNewMessageUsingBaseName() { + final Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + try { + final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory("MF"); + final String testMsg = "hello_world"; + final Message message = localizedMessageFactory.newMessage(testMsg); + assertEquals("Hello world.", message.getFormattedMessage()); + } finally { + Locale.setDefault(defaultLocale); + } + } + + @Test + public void testNoMatch() { + final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory(ResourceBundle.getBundle("MF", Locale.US)); + final Message message = localizedMessageFactory.newMessage("no match"); + assertEquals("no match", message.getFormattedMessage()); + } + + @Test + public void testNoMatchPercentInMessageArgsYes() { + final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory(ResourceBundle.getBundle("MF", Locale.US)); + final Message message = localizedMessageFactory.newMessage("C:/Program%20Files/Some%20Company/Some%20Product%20Name/{0}", "One"); + assertEquals("C:/Program%20Files/Some%20Company/Some%20Product%20Name/One", message.getFormattedMessage()); + } + + @Test + public void testNoMatchPercentInMessageNoArgsNo() { + // LOG4J2-3458 LocalizedMessage causes a lot of noise on the console + // + // ERROR StatusLogger Unable to format msg: C:/Program%20Files/Some%20Company/Some%20Product%20Name/ + // java.util.UnknownFormatConversionException: Conversion = 'F' + // at java.util.Formatter$FormatSpecifier.conversion(Formatter.java:2691) + // at java.util.Formatter$FormatSpecifier.(Formatter.java:2720) + // at java.util.Formatter.parse(Formatter.java:2560) + // at java.util.Formatter.format(Formatter.java:2501) + // at java.util.Formatter.format(Formatter.java:2455) + // at java.lang.String.format(String.java:2981) + // at org.apache.logging.log4j.message.StringFormattedMessage.formatMessage(StringFormattedMessage.java:116) + // at org.apache.logging.log4j.message.StringFormattedMessage.getFormattedMessage(StringFormattedMessage.java:88) + // at org.apache.logging.log4j.message.FormattedMessage.getFormattedMessage(FormattedMessage.java:178) + // at org.apache.logging.log4j.message.LocalizedMessage.getFormattedMessage(LocalizedMessage.java:196) + // at + // org.apache.logging.log4j.message.LocalizedMessageFactoryTest.testNoMatchPercentInMessage(LocalizedMessageFactoryTest.java:60) + // + final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory(ResourceBundle.getBundle("MF", Locale.US)); + final Message message = localizedMessageFactory.newMessage("C:/Program%20Files/Some%20Company/Some%20Product%20Name/"); + assertEquals("C:/Program%20Files/Some%20Company/Some%20Product%20Name/", message.getFormattedMessage()); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java similarity index 76% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java index aebe6ed1006..b8a1801d588 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/LocalizedMessageTest.java @@ -16,18 +16,22 @@ */ package org.apache.logging.log4j.message; +import org.apache.commons.lang3.SerializationUtils; +import org.apache.logging.log4j.test.junit.Mutable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + import java.io.Serializable; import java.util.Locale; -import org.apache.commons.lang3.SerializationUtils; -import org.apache.logging.log4j.junit.Mutable; -import org.junit.Test; - -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests LocalizedMessage. */ +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public class LocalizedMessageTest { private T roundtrip(final T msg) { @@ -71,7 +75,7 @@ public void testUnsafeWithMutableParams() { // LOG4J2-763 // modify parameter before calling msg.getFormattedMessage param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent param value", "Test message XYZ", actual); + assertEquals("Test message XYZ", actual, "Expected most recent param value"); } @Test @@ -84,6 +88,20 @@ public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 msg.getFormattedMessage(); param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Should use initial param value", "Test message abc", actual); + assertEquals("Test message abc", actual, "Should use initial param value"); + } + + @Test + @ResourceLock(Resources.LOCALE) + public void testMessageUsingBaseName() { // LOG4J2-2850 + final Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + try { + final String testMsg = "hello_world"; + final LocalizedMessage msg = new LocalizedMessage("MF", testMsg, null); + assertEquals("Hello world.", msg.getFormattedMessage()); + } finally { + Locale.setDefault(defaultLocale); + } } } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java new file mode 100644 index 00000000000..106cce2c975 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +import java.math.BigDecimal; +import java.sql.Time; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; + +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class MapMessageTest { + + @Test + public void testMap() { + final String testMsg = "Test message {}"; + final StringMapMessage msg = new StringMapMessage(); + msg.put("message", testMsg); + msg.put("project", "Log4j"); + final String result = msg.getFormattedMessage(); + final String expected = "message=\"Test message {}\" project=\"Log4j\""; + assertEquals(expected, result); + } + + @Test + public void testBuilder() { + final String testMsg = "Test message {}"; + final StringMapMessage msg = new StringMapMessage() + .with("message", testMsg) + .with("project", "Log4j"); + final String result = msg.getFormattedMessage(); + final String expected = "message=\"Test message {}\" project=\"Log4j\""; + assertEquals(expected, result); + } + + @Test + public void testXML() { + final String testMsg = "Test message {}"; + final StringMapMessage msg = new StringMapMessage(); + msg.put("message", testMsg); + msg.put("project", "Log4j"); + final String result = msg.getFormattedMessage(new String[]{"XML"}); + final String expected = "\n Test message {}\n" + + " Log4j\n" + + ""; + assertEquals(expected, result); + } + + @Test + public void testXMLEscape() { + final String testMsg = "Test message "; + final StringMapMessage msg = new StringMapMessage(); + msg.put("message", testMsg); + final String result = msg.getFormattedMessage(new String[]{"XML"}); + final String expected = "\n Test message <foo>\n" + + ""; + assertEquals(expected, result); + } + + @Test + public void testJSON() { + final String testMsg = "Test message {}"; + final StringMapMessage msg = new StringMapMessage(); + msg.put("message", testMsg); + msg.put("project", "Log4j"); + final String result = msg.getFormattedMessage(new String[]{"JSON"}); + final String expected = "{'message':'Test message {}','project':'Log4j'}".replace('\'', '"'); + assertEquals(expected, result); + } + + @Test + public void testJSONEscape() { + final String testMsg = "Test message \"Hello, World!\""; + final StringMapMessage msg = new StringMapMessage(); + msg.put("message", testMsg); + final String result = msg.getFormattedMessage(new String[]{"JSON"}); + final String expected = "{\"message\":\"Test message \\\"Hello, World!\\\"\"}"; + assertEquals(expected, result); + } + + @Test + public void testJSONEscapeNewlineAndOtherControlCharacters() { + final String testMsg = "hello\tworld\r\nhh\bere is it\f"; + final StringMapMessage msg = new StringMapMessage(); + msg.put("one\ntwo", testMsg); + final String result = msg.getFormattedMessage(new String[]{"JSON"}); + final String expected = + "{\"one\\ntwo\":\"hello\\tworld\\r\\nhh\\bere is it\\f\"}"; + assertEquals(expected, result); + } + + @Test + public void testJsonFormatterNestedObjectSupport() { + final String actualJson = new ObjectMapMessage() + .with("key1", "val1") + .with("key2", Collections.singletonMap("key2.1", "val2.1")) + .with("key3", Arrays.asList( + 3, + (byte) 127, + 4.5D, + 4.6F, + Arrays.asList(true, false), + new BigDecimal(30), + Collections.singletonMap("key3.3", "val3.3"))) + .with("key4", new LinkedHashMap() {{ + put("chars", new char[]{'a', 'b', 'c'}); + put("booleans", new boolean[]{true, false}); + put("bytes", new byte[]{1, 2}); + put("shorts", new short[]{3, 4}); + put("ints", new int[]{256, 257}); + put("longs", new long[]{2147483648L, 2147483649L}); + put("floats", new float[]{1.0F, 1.1F}); + put("doubles", new double[]{2.0D, 2.1D}); + put("objects", new Object[]{"foo", "bar"}); + }}) + .getFormattedMessage(new String[]{"JSON"}); + final String expectedJson = ("{" + + "'key1':'val1'," + + "'key2':{'key2.1':'val2.1'}," + + "'key3':[3,127,4.5,4.6,[true,false],30,{'key3.3':'val3.3'}]," + + "'key4':{" + + "'chars':['a','b','c']," + + "'booleans':[true,false]," + + "'bytes':[1,2]," + + "'shorts':[3,4]," + + "'ints':[256,257]," + + "'longs':[2147483648,2147483649]," + + "'floats':[1.0,1.1]," + + "'doubles':[2.0,2.1]," + + "'objects':['foo','bar']" + + "}}").replace('\'', '"'); + assertEquals(expectedJson, actualJson); + } + + @Test + public void testJsonFormatterInfiniteRecursionPrevention() { + final List recursiveValue = Arrays.asList(1, null); + // noinspection CollectionAddedToSelf + recursiveValue.set(1, recursiveValue); + assertThrows(IllegalArgumentException.class, () -> new ObjectMapMessage() + .with("key", recursiveValue) + .getFormattedMessage(new String[]{"JSON"})); + } + + @Test + public void testJsonFormatterMaxDepthViolation() { + assertThrows(IllegalArgumentException.class, () -> testJsonFormatterMaxDepth(MapMessageJsonFormatter.MAX_DEPTH - 1)); + } + + @Test + public void testJsonFormatterMaxDepthConformance() { + int depth = MapMessageJsonFormatter.MAX_DEPTH - 2; + String expectedJson = String + .format("{'key':%s1%s}", + Strings.repeat("[", depth), + Strings.repeat("]", depth)) + .replace('\'', '"'); + String actualJson = testJsonFormatterMaxDepth(depth); + assertEquals(expectedJson, actualJson); + } + + public static String testJsonFormatterMaxDepth(int depth) { + List list = new LinkedList<>(); + list.add(1); + while (--depth > 0) { + list = new LinkedList<>(Collections.singletonList(list)); + } + return new ObjectMapMessage() + .with("key", list) + .getFormattedMessage(new String[]{"JSON"}); + } + + @Test + public void testJava() { + final String testMsg = "Test message {}"; + final StringMapMessage msg = new StringMapMessage(); + msg.put("message", testMsg); + msg.put("project", "Log4j"); + final String result = msg.getFormattedMessage(new String[]{"Java"}); + final String expected = "{message=\"Test message {}\", project=\"Log4j\"}"; + assertEquals(expected, result); + } + + @Test + public void testMutableByDesign() { // LOG4J2-763 + final StringMapMessage msg = new StringMapMessage(); + + // modify parameter before calling msg.getFormattedMessage + msg.put("key1", "value1"); + msg.put("key2", "value2"); + final String result = msg.getFormattedMessage(new String[]{"Java"}); + final String expected = "{key1=\"value1\", key2=\"value2\"}"; + assertEquals(expected, result); + + // modify parameter after calling msg.getFormattedMessage + msg.put("key3", "value3"); + final String result2 = msg.getFormattedMessage(new String[]{"Java"}); + final String expected2 = "{key1=\"value1\", key2=\"value2\", key3=\"value3\"}"; + assertEquals(expected2, result2); + } + + @Test + public void testGetNonStringValue() { + final String key = "Key"; + final ObjectMapMessage msg = new ObjectMapMessage() + .with(key, 1L); + assertEquals("1", msg.get(key)); + } + + @Test + public void testRemoveNonStringValue() { + final String key = "Key"; + final ObjectMapMessage msg = new ObjectMapMessage() + .with(key, 1L); + assertEquals("1", msg.remove(key)); + } + + @Test + public void testJSONFormatNonStringValue() { + final ObjectMapMessage msg = new ObjectMapMessage().with("key", 1L); + final String result = msg.getFormattedMessage(new String[]{"JSON"}); + final String expected = "{'key':1}".replace('\'', '"'); + assertEquals(expected, result); + } + + @Test + public void testXMLFormatNonStringValue() { + final ObjectMapMessage msg = new ObjectMapMessage() + .with("key", 1L); + final String result = msg.getFormattedMessage(new String[]{"XML"}); + final String expected = "\n 1\n"; + assertEquals(expected, result); + } + + @Test + public void testFormatToUsedInOutputXml() { + final ObjectMapMessage msg = new ObjectMapMessage() + .with("key", new FormattableTestType()); + final String result = msg.getFormattedMessage(new String[]{"XML"}); + final String expected = "\n formatTo\n"; + assertEquals(expected, result); + } + + @Test + public void testFormatToUsedInOutputJson() { + final ObjectMapMessage msg = new ObjectMapMessage() + .with("key", new FormattableTestType()); + final String result = msg.getFormattedMessage(new String[]{"JSON"}); + final String expected = "{\"key\":\"formatTo\"}"; + assertEquals(expected, result); + } + + @Test + public void testFormatToUsedInOutputJava() { + final ObjectMapMessage msg = new ObjectMapMessage() + .with("key", new FormattableTestType()); + final String result = msg.getFormattedMessage(new String[]{"JAVA"}); + final String expected = "{key=\"formatTo\"}"; + assertEquals(expected, result); + } + + @Test + public void testFormatToUsedInOutputDefault() { + final ObjectMapMessage msg = new ObjectMapMessage() + .with("key", new FormattableTestType()); + final String result = msg.getFormattedMessage(null); + final String expected = "key=\"formatTo\""; + assertEquals(expected, result); + } + + @Test + public void testGetUsesDeepToString() { + final String key = "key"; + final ObjectMapMessage msg = new ObjectMapMessage() + .with(key, new FormattableTestType()); + final String result = msg.get(key); + final String expected = "formatTo"; + assertEquals(expected, result); + } + + @Test + public void testRemoveUsesDeepToString() { + final String key = "key"; + final ObjectMapMessage msg = new ObjectMapMessage() + .with(key, new FormattableTestType()); + final String result = msg.remove(key); + final String expected = "formatTo"; + assertEquals(expected, result); + } + + @Test + public void testTime() throws Exception { + final Time time = new Time(12, 5, 5); + final ObjectMapMessage message = new ObjectMapMessage().with("time", time); + assertEquals("time=\"" + time.toString() + "\"", + message.getFormattedMessage(), "Incorrect time format"); + } + + private static final class FormattableTestType implements StringBuilderFormattable { + + @Override + public String toString() { + return "toString"; + } + + @Override + public void formatTo(StringBuilder buffer) { + buffer.append("formatTo"); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageSerializationTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageSerializationTest.java new file mode 100644 index 00000000000..29062932284 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageSerializationTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.message; + +import java.util.stream.Stream; + +import org.apache.logging.log4j.test.AbstractSerializationTest; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +public class MessageFormatMessageSerializationTest extends AbstractSerializationTest { + + protected Stream data() { + return Stream.of( + new MessageFormatMessage("Test"), + new MessageFormatMessage("Test {0} {1}", "message", "test"), + new MessageFormatMessage("{0}{1}{2}", 3, '.', 14L)); + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java similarity index 82% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java index d123b74e1ad..3d4a9b567ae 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageTest.java @@ -14,23 +14,25 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.message; -import org.apache.logging.log4j.junit.Mutable; +import java.util.Locale; + +import org.apache.logging.log4j.test.junit.Mutable; import org.apache.logging.log4j.util.Constants; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; -import java.util.Locale; - -/** - * - */ +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public class MessageFormatMessageTest { private static final String SPACE = Constants.JAVA_MAJOR_VERSION < 9 ? " " : "\u00a0"; + private static final String N_SPACE = Constants.JAVA_MAJOR_VERSION < 15 ? "\u00a0" : "\u202f"; private static final int LOOP_CNT = 500; String[] array = new String[LOOP_CNT]; @@ -72,7 +74,7 @@ public void testOneIntArgLocaleFrance() { final String testMsg = "Test message {0,number,currency}"; final MessageFormatMessage msg = new MessageFormatMessage(Locale.FRANCE, testMsg, 1234567890); final String result = msg.getFormattedMessage(); - final String expected = "Test message 1 234 567 890,00" + SPACE + "€"; + final String expected = "Test message 1" + N_SPACE + "234" + N_SPACE + "567" + N_SPACE + "890,00" + SPACE + "€"; assertEquals(expected, result); } @@ -84,7 +86,7 @@ public void testException() { final String expected = "Test message Apache"; assertEquals(expected, result); final Throwable t = msg.getThrowable(); - assertNotNull("No Throwable", t); + assertNotNull(t, "No Throwable"); } @Test @@ -96,7 +98,7 @@ public void testUnsafeWithMutableParams() { // LOG4J2-763 // modify parameter before calling msg.getFormattedMessage param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent param value", "Test message XYZ", actual); + assertEquals("Test message XYZ", actual, "Expected most recent param value"); } @Test @@ -109,6 +111,6 @@ public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 msg.getFormattedMessage(); param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Should use initial param value", "Test message abc", actual); + assertEquals("Test message abc", actual, "Should use initial param value"); } } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java similarity index 92% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java index a151eac08d1..b5ff0456bb4 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/MessageFormatsPerfTest.java @@ -16,13 +16,19 @@ */ package org.apache.logging.log4j.message; -import org.apache.logging.log4j.Timer; -import org.junit.AfterClass; -import org.junit.Test; +import org.apache.logging.log4j.util.Timer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; /** * */ +@Tag("performance") +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public class MessageFormatsPerfTest { private static final int LOOP_CNT = 500; @@ -32,7 +38,7 @@ public class MessageFormatsPerfTest { private static long msgFormatTime = 0; private static long formattedTime = 0; - @AfterClass + @AfterAll public static void after() { if (stringTime > paramTime) { System.out.println(String.format("Parameterized is %1$.2f times faster than StringFormat.", diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java similarity index 80% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java index 5f11cf8204f..6196200df2e 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectArrayMessageTest.java @@ -1,40 +1,42 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.message; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @since 2.4 - */ -public class ObjectArrayMessageTest { - - private static final Object[] ARRAY = { "A", "B", "C" }; - private static final ObjectArrayMessage OBJECT_ARRAY_MESSAGE = new ObjectArrayMessage(ARRAY); - - @Test - public void testGetParameters() { - Assert.assertArrayEquals(ARRAY, OBJECT_ARRAY_MESSAGE.getParameters()); - } - - @Test - public void testGetThrowable() { - Assert.assertEquals(null, OBJECT_ARRAY_MESSAGE.getThrowable()); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * @since 2.4 + */ +public class ObjectArrayMessageTest { + + private static final Object[] ARRAY = { "A", "B", "C" }; + private static final ObjectArrayMessage OBJECT_ARRAY_MESSAGE = new ObjectArrayMessage(ARRAY); + + @Test + public void testGetParameters() { + assertArrayEquals(ARRAY, OBJECT_ARRAY_MESSAGE.getParameters()); + } + + @Test + public void testGetThrowable() { + assertNull(OBJECT_ARRAY_MESSAGE.getThrowable()); + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java similarity index 100% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMapMessage.java diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java similarity index 88% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java index 8f547ae5081..4f3218eeafe 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ObjectMessageTest.java @@ -19,11 +19,11 @@ import java.io.Serializable; import java.math.BigDecimal; -import org.apache.logging.log4j.junit.Mutable; -import org.apache.logging.log4j.junit.SerialUtil; -import org.junit.Test; +import org.apache.logging.log4j.test.junit.Mutable; +import org.apache.logging.log4j.test.junit.SerialUtil; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests {@link ObjectMessage}. @@ -53,7 +53,7 @@ public void testUnsafeWithMutableParams() { // LOG4J2-763 // modify parameter before calling msg.getFormattedMessage param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Expected most recent param value", "XYZ", actual); + assertEquals("XYZ", actual, "Expected most recent param value"); } @Test @@ -65,7 +65,7 @@ public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 msg.getFormattedMessage(); param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Should use initial param value", "abc", actual); + assertEquals("abc", actual, "Should use initial param value"); } @Test @@ -83,6 +83,11 @@ class NonSerializable { public boolean equals(final Object other) { return other instanceof NonSerializable; // a very lenient equals() } + + @Override + public int hashCode() { + return NonSerializable.class.hashCode(); + } } final NonSerializable nonSerializable = new NonSerializable(); assertFalse(nonSerializable instanceof Serializable); diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java similarity index 89% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java index 6e728f6928e..ba793e363d1 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterFormatterTest.java @@ -16,19 +16,21 @@ */ package org.apache.logging.log4j.message; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; /** - * Tests ParameterFormatter. + * Tests {@link ParameterFormatter}. */ public class ParameterFormatterTest { @Test - public void testCountArgumentPlaceholders() throws Exception { + public void testCountArgumentPlaceholders() { assertEquals(0, ParameterFormatter.countArgumentPlaceholders("")); assertEquals(0, ParameterFormatter.countArgumentPlaceholders("aaa")); assertEquals(0, ParameterFormatter.countArgumentPlaceholders("\\{}")); @@ -168,9 +170,10 @@ public void testFormatMessageStringArgsWithEscapedEscape() { } @Test - public void testDeepToString() throws Exception { + public void testDeepToString() { final List list = new ArrayList<>(); list.add(1); + // noinspection CollectionAddedToSelf list.add(list); list.add(2); final String actual = ParameterFormatter.deepToString(list); @@ -179,13 +182,29 @@ public void testDeepToString() throws Exception { } @Test - public void testIdentityToString() throws Exception { + public void testDeepToStringUsingNonRecursiveButConsequentObjects() { + final List list = new ArrayList<>(); + final Object item = Collections.singletonList(0); + list.add(1); + list.add(item); + list.add(2); + list.add(item); + list.add(3); + final String actual = ParameterFormatter.deepToString(list); + final String expected = "[1, [0], 2, [0], 3]"; + assertEquals(expected, actual); + } + + @Test + public void testIdentityToString() { final List list = new ArrayList<>(); list.add(1); + // noinspection CollectionAddedToSelf list.add(list); list.add(2); final String actual = ParameterFormatter.identityToString(list); final String expected = list.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(list)); assertEquals(expected, actual); } -} \ No newline at end of file + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java similarity index 94% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java index 2a5bac6ceb3..1c15c5e73a6 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ParameterizedMessageTest.java @@ -16,10 +16,10 @@ */ package org.apache.logging.log4j.message; -import org.apache.logging.log4j.junit.Mutable; -import org.junit.Test; +import org.apache.logging.log4j.test.junit.Mutable; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * @@ -135,11 +135,11 @@ public void testSafeWithMutableParams() { // LOG4J2-763 // modify parameter before calling msg.getFormattedMessage param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Should use current param value", "Test message XYZ", actual); + assertEquals("Test message XYZ", actual, "Should use current param value"); // modify parameter after calling msg.getFormattedMessage param.set("000"); final String after = msg.getFormattedMessage(); - assertEquals("Should not change after rendered once", "Test message XYZ", after); + assertEquals("Test message XYZ", after, "Should not change after rendered once"); } } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java similarity index 96% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java index 7942e5faf55..f5e293134e7 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java @@ -16,9 +16,9 @@ */ package org.apache.logging.log4j.message; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the ReusableMessageFactory class. @@ -53,10 +53,10 @@ public void testCreateEventReturnsSameInstance() throws Exception { private void assertReusableParameterizeMessage(final Message message, final String txt, final Object[] params) { assertTrue(message instanceof ReusableParameterizedMessage); final ReusableParameterizedMessage msg = (ReusableParameterizedMessage) message; - assertTrue("reserved", msg.reserved); + assertTrue(msg.reserved, "reserved"); assertEquals(txt, msg.getFormat()); - assertEquals("count", msg.getParameterCount(), params.length); + assertEquals(msg.getParameterCount(), params.length, "count"); final Object[] messageParams = msg.getParameters(); for (int i = 0; i < params.length; i++) { assertEquals(messageParams[i], params[i]); @@ -127,4 +127,4 @@ public void run() { ReusableMessageFactory.release(message2[0]); } -} \ No newline at end of file +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java similarity index 95% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java index d4f87a7dd8e..ea386fb3c38 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableObjectMessageTest.java @@ -16,9 +16,9 @@ */ package org.apache.logging.log4j.message; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests ReusableObjectMessage. @@ -49,8 +49,8 @@ public void testGetFormattedMessage_ReturnsLatestSetString() throws Exception { } @Test - public void testGetFormat_InitiallyNullString() throws Exception { - assertEquals("null", new ReusableObjectMessage().getFormat()); + public void testGetFormat_InitiallyNull() throws Exception { + assertNull(new ReusableObjectMessage().getFormat()); } @Test @@ -119,4 +119,4 @@ public void testFormatTo_WritesLatestSetString() throws Exception { msg.formatTo(sb); assertEquals("xyz", sb.toString()); } -} \ No newline at end of file +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java similarity index 83% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java index b25735c17af..262cf8ed558 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableParameterizedMessageTest.java @@ -16,10 +16,13 @@ */ package org.apache.logging.log4j.message; -import org.apache.logging.log4j.junit.Mutable; -import org.junit.Test; +import org.apache.logging.log4j.test.junit.Mutable; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests ReusableParameterizedMessage. @@ -124,12 +127,12 @@ public void testNotSafeWithMutableParams() { // modify parameter before calling msg.getFormattedMessage param.set("XYZ"); final String actual = msg.getFormattedMessage(); - assertEquals("Should use current param value", "Test message XYZ", actual); + assertEquals("Test message XYZ", actual, "Should use current param value"); // modify parameter after calling msg.getFormattedMessage param.set("000"); final String after = msg.getFormattedMessage(); - assertEquals("Renders again", "Test message 000", after); + assertEquals("Test message 000", after, "Renders again"); } @Test @@ -144,4 +147,23 @@ public void testThrowable() { msg.set(testMsg, "msgs", EXCEPTION2); assertSame(EXCEPTION2, msg.getThrowable()); } + + @Test + public void testParameterConsumer() { + final String testMsg = "Test message {}"; + final ReusableParameterizedMessage msg = new ReusableParameterizedMessage(); + final Throwable EXCEPTION1 = new IllegalAccessError("#1"); + msg.set(testMsg, "msg", EXCEPTION1); + List expected = new LinkedList<>(); + expected.add("msg"); + expected.add(EXCEPTION1); + final List actual = new LinkedList<>(); + msg.forEachParameter(new ParameterConsumer() { + @Override + public void accept(Object parameter, int parameterIndex, Void state) { + actual.add(parameter); + } + }, null); + assertEquals(expected, actual); + } } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java similarity index 96% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java index 3a56b643175..3a7f441214c 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java @@ -16,9 +16,9 @@ */ package org.apache.logging.log4j.message; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests ReusableSimpleMessage. @@ -50,7 +50,7 @@ public void testGetFormattedMessage_ReturnsLatestSetString() throws Exception { @Test public void testGetFormat_InitiallyStringNull() throws Exception { - assertEquals("null", new ReusableSimpleMessage().getFormat()); + assertNull(new ReusableSimpleMessage().getFormat()); } @Test @@ -119,4 +119,4 @@ public void testFormatTo_WritesLatestSetString() throws Exception { msg.formatTo(sb); assertEquals("xyz", sb.toString()); } -} \ No newline at end of file +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java similarity index 94% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java index 383ecb49263..a43d986a916 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/SimpleMessageTest.java @@ -16,9 +16,9 @@ */ package org.apache.logging.log4j.message; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the SimpleMessage class. diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java new file mode 100644 index 00000000000..08035a3ead3 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Locale; + +import org.apache.logging.log4j.test.junit.Mutable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +import static org.junit.jupiter.api.Assertions.*; + +@ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) +public class StringFormattedMessageTest { + + private static final int LOOP_CNT = 500; + String[] array = new String[LOOP_CNT]; + + @Test + public void testNoArgs() { + final String testMsg = "Test message %1s"; + StringFormattedMessage msg = new StringFormattedMessage(testMsg, (Object[]) null); + String result = msg.getFormattedMessage(); + final String expected = "Test message null"; + assertEquals(expected, result); + final Object[] array = null; + msg = new StringFormattedMessage(testMsg, array, null); + result = msg.getFormattedMessage(); + assertEquals(expected, result); + } + + @Test + public void testOneStringArg() { + final String testMsg = "Test message %1s"; + final StringFormattedMessage msg = new StringFormattedMessage(testMsg, "Apache"); + final String result = msg.getFormattedMessage(); + final String expected = "Test message Apache"; + assertEquals(expected, result); + } + + @Test + public void testOneIntArgLocaleUs() { + final String testMsg = "Test e = %+10.4f"; + final StringFormattedMessage msg = new StringFormattedMessage(Locale.US, testMsg, Math.E); + final String result = msg.getFormattedMessage(); + final String expected = "Test e = +2.7183"; + assertEquals(expected, result); + } + + @Test + public void testOneArgLocaleFrance() { + final String testMsg = "Test e = %+10.4f"; + final StringFormattedMessage msg = new StringFormattedMessage(Locale.FRANCE, testMsg, Math.E); + final String result = msg.getFormattedMessage(); + final String expected = "Test e = +2,7183"; + assertEquals(expected, result); + } + + @Test + public void testException() { + final String testMsg = "Test message {0}"; + final MessageFormatMessage msg = new MessageFormatMessage(testMsg, "Apache", new NullPointerException("Null")); + final String result = msg.getFormattedMessage(); + final String expected = "Test message Apache"; + assertEquals(expected, result); + final Throwable t = msg.getThrowable(); + assertNotNull(t, "No Throwable"); + } + + @Test + public void testUnsafeWithMutableParams() { // LOG4J2-763 + final String testMsg = "Test message %s"; + final Mutable param = new Mutable().set("abc"); + final StringFormattedMessage msg = new StringFormattedMessage(testMsg, param); + + // modify parameter before calling msg.getFormattedMessage + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("Test message XYZ", actual, "Should use initial param value"); + } + + @Test + public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 + final String testMsg = "Test message %s"; + final Mutable param = new Mutable().set("abc"); + final StringFormattedMessage msg = new StringFormattedMessage(testMsg, param); + + // modify parameter after calling msg.getFormattedMessage + msg.getFormattedMessage(); + param.set("XYZ"); + final String actual = msg.getFormattedMessage(); + assertEquals("Test message abc", actual, "Should use initial param value"); + } + + @SuppressWarnings("BanSerializableRead") + @Test + public void testSerialization() throws IOException, ClassNotFoundException { + final StringFormattedMessage expected = new StringFormattedMessage("Msg", "a", "b", "c"); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (final ObjectOutputStream out = new ObjectOutputStream(baos)) { + out.writeObject(expected); + } + final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + final ObjectInputStream in = new ObjectInputStream(bais); + final StringFormattedMessage actual = (StringFormattedMessage) in.readObject(); + assertEquals(expected, actual); + assertEquals(expected.getFormat(), actual.getFormat()); + assertEquals(expected.getFormattedMessage(), actual.getFormattedMessage()); + assertArrayEquals(expected.getParameters(), actual.getParameters()); + } + + @Test + public void testPercentInMessageNoArgs() { + // LOG4J2-3458 LocalizedMessage causes a lot of noise on the console + // + // ERROR StatusLogger Unable to format msg: C:/Program%20Files/Some%20Company/Some%20Product%20Name/ + // java.util.UnknownFormatConversionException: Conversion = 'F' + // at java.util.Formatter$FormatSpecifier.conversion(Formatter.java:2691) + // at java.util.Formatter$FormatSpecifier.(Formatter.java:2720) + // at java.util.Formatter.parse(Formatter.java:2560) + // at java.util.Formatter.format(Formatter.java:2501) + // at java.util.Formatter.format(Formatter.java:2455) + // at java.lang.String.format(String.java:2981) + // at org.apache.logging.log4j.message.StringFormattedMessage.formatMessage(StringFormattedMessage.java:120) + // at org.apache.logging.log4j.message.StringFormattedMessage.getFormattedMessage(StringFormattedMessage.java:88) + // at + // org.apache.logging.log4j.message.StringFormattedMessageTest.testPercentInMessageNoArgs(StringFormattedMessageTest.java:153) + final StringFormattedMessage msg = new StringFormattedMessage("C:/Program%20Files/Some%20Company/Some%20Product%20Name/", new Object[] {}); + assertEquals("C:/Program%20Files/Some%20Company/Some%20Product%20Name/", msg.getFormattedMessage()); + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java similarity index 89% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java index 51a4ceec460..b592fcea8b2 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/StructuredDataMessageTest.java @@ -16,9 +16,9 @@ */ package org.apache.logging.log4j.message; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * @@ -82,11 +82,12 @@ public void testBuilder() { assertEquals(expected, result); } - @Test(expected = IllegalArgumentException.class) + @Test public void testMsgWithKeyTooLong() { final String testMsg = "Test message {}"; final StructuredDataMessage msg = new StructuredDataMessage("MsgId@12345", testMsg, "Alert"); - msg.put("This is a very long key that will violate the key length validation", "Testing"); + assertThrows(IllegalArgumentException.class, () -> + msg.put("This is a very long key that will violate the key length validation", "Testing")); } @Test @@ -107,4 +108,12 @@ public void testMutableByDesign() { // LOG4J2-763 final String expected2 = "Alert [MsgId@1 memo=\"Added later\" message=\"Test message {}\" project=\"Log4j\"] Test message {}"; assertEquals(expected2, result2); } + + @Test + public void testEnterpriseNoAsOidFragment() { + final String testMsg = "Test message {}"; + final StructuredDataMessage structuredDataMessage = new StructuredDataMessage("XX_DATA@1234.55.6.7", testMsg, "Nothing"); + assertNotNull(structuredDataMessage); + } + } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java similarity index 82% rename from log4j-api/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java index 8668d0ab84f..40cb3e78bfd 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/message/ThreadDumpMessageTest.java @@ -16,16 +16,14 @@ */ package org.apache.logging.log4j.message; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.ReentrantLock; -import org.junit.Test; - -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class ThreadDumpMessageTest { @Test @@ -34,13 +32,14 @@ public void testMessage() { final String message = msg.getFormattedMessage(); //System.out.print(message); - assertTrue("No header", message.contains("Testing")); - assertTrue("No RUNNABLE", message.contains("RUNNABLE")); - assertTrue("No ThreadDumpMessage", message.contains("ThreadDumpMessage")); + assertTrue(message.contains("Testing"), "No header"); + assertTrue(message.contains("RUNNABLE"), "No RUNNABLE"); + assertTrue(message.contains("ThreadDumpMessage"), "No ThreadDumpMessage"); } @Test + @Tag("sleepy") public void testMessageWithLocks() throws Exception { final ReentrantLock lock = new ReentrantLock(); lock.lock(); @@ -60,9 +59,9 @@ public void testMessageWithLocks() throws Exception { final String message = msg.getFormattedMessage(); //System.out.print(message); - assertTrue("No header", message.contains("Testing")); - assertTrue("No RUNNABLE", message.contains("RUNNABLE")); - assertTrue("No ThreadDumpMessage", message.contains("ThreadDumpMessage")); + assertTrue(message.contains("Testing"), "No header"); + assertTrue(message.contains("RUNNABLE"), "No RUNNABLE"); + assertTrue(message.contains("ThreadDumpMessage"), "No ThreadDumpMessage"); //assertTrue("No Locks", message.contains("waiting on")); //assertTrue("No syncronizers", message.contains("locked syncrhonizers")); } @@ -90,7 +89,7 @@ public void run() { other.start(); other.join(); - assertTrue("No mention of other thread in msg", !actual[0].contains("OtherThread")); + assertFalse(actual[0].contains("OtherThread"), "No mention of other thread in msg"); } @Test @@ -98,7 +97,7 @@ public void formatTo_usesCachedMessageString() throws Exception { final ThreadDumpMessage message = new ThreadDumpMessage(""); final String initial = message.getFormattedMessage(); - assertFalse("no ThreadWithCountDownLatch thread yet", initial.contains("ThreadWithCountDownLatch")); + assertFalse(initial.contains("ThreadWithCountDownLatch"), "no ThreadWithCountDownLatch thread yet"); final CountDownLatch started = new CountDownLatch(1); final CountDownLatch keepAlive = new CountDownLatch(1); @@ -108,13 +107,13 @@ public void formatTo_usesCachedMessageString() throws Exception { final StringBuilder result = new StringBuilder(); message.formatTo(result); - assertFalse("no ThreadWithCountDownLatch captured", - result.toString().contains("ThreadWithCountDownLatch")); + assertFalse( + result.toString().contains("ThreadWithCountDownLatch"), "no ThreadWithCountDownLatch captured"); assertEquals(initial, result.toString()); keepAlive.countDown(); // allow thread to die } - private class Thread1 extends Thread { + private static class Thread1 extends Thread { private final ReentrantLock lock; public Thread1(final ReentrantLock lock) { @@ -128,7 +127,7 @@ public void run() { } } - private class Thread2 extends Thread { + private static class Thread2 extends Thread { private final Object obj; public Thread2(final Object obj) { @@ -142,7 +141,7 @@ public void run() { } } - private class ThreadWithCountDownLatch extends Thread { + private static class ThreadWithCountDownLatch extends Thread { private final CountDownLatch started; private final CountDownLatch keepAlive; volatile boolean finished; diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java similarity index 75% rename from log4j-api/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java index 0d57e5ee14a..d56c1aedf90 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/simple/SimpleLoggerTest.java @@ -18,15 +18,20 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LogManagerLoggerContextFactoryRule; -import org.junit.ClassRule; -import org.junit.Test; +import org.apache.logging.log4j.test.junit.LoggerContextFactoryExtension; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; +@Tag("smoke") +// TODO: use a 'LoggerContextFactory` lock +@ResourceLock(value = Resources.GLOBAL) public class SimpleLoggerTest { - @ClassRule - public static final LogManagerLoggerContextFactoryRule rule = new LogManagerLoggerContextFactoryRule( - new SimpleLoggerContextFactory()); + @RegisterExtension + public static final LoggerContextFactoryExtension EXTENSION = new LoggerContextFactoryExtension(SimpleLoggerContextFactory.INSTANCE); private final Logger logger = LogManager.getLogger("TestError"); diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java similarity index 87% rename from log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java index e3ad5d6bc47..e702e7ceec8 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java @@ -16,20 +16,18 @@ */ package org.apache.logging.log4j.spi; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - import java.util.HashMap; import java.util.Map; -import org.apache.logging.log4j.ThreadContext; -import org.junit.Test; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests the {@code DefaultThreadContextMap} class. */ +@UsingThreadContextMap public class DefaultThreadContextMapTest { @Test @@ -129,7 +127,7 @@ private DefaultThreadContextMap createMap() { assertEquals("value2", map.get("key2")); return map; } - + @Test public void testGetCopyReturnsMutableMap() { final DefaultThreadContextMap map = new DefaultThreadContextMap(true); @@ -172,7 +170,7 @@ public void testGetImmutableMapReturnsNullIfEmpty() { assertNull(map.getImmutableMapOrNull()); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testGetImmutableMapReturnsImmutableMapIfNonEmpty() { final DefaultThreadContextMap map = new DefaultThreadContextMap(true); map.put("key1", "value1"); @@ -182,11 +180,11 @@ public void testGetImmutableMapReturnsImmutableMapIfNonEmpty() { assertEquals("value1", immutable.get("key1")); // copy has values too // immutable - immutable.put("key", "value"); // error + assertThrows(UnsupportedOperationException.class, () -> immutable.put("key", "value")); } @Test - public void testGetImmutableMapCopyNotAffectdByContextMapChanges() { + public void testGetImmutableMapCopyNotAffectedByContextMapChanges() { final DefaultThreadContextMap map = new DefaultThreadContextMap(true); map.put("key1", "value1"); assertFalse(map.isEmpty()); @@ -216,20 +214,13 @@ public void testToStringShowsMapContext() { @Test public void testThreadLocalNotInheritableByDefault() { - System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP); - final ThreadLocal> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true); + final ThreadLocal> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true, false); assertFalse(threadLocal instanceof InheritableThreadLocal); } - + @Test public void testThreadLocalInheritableIfConfigured() { - System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true"); - ThreadContextMapFactory.init(); - try { - final ThreadLocal> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true); - assertTrue(threadLocal instanceof InheritableThreadLocal); - } finally { - System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP); - } + final ThreadLocal> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true, true); + assertTrue(threadLocal instanceof InheritableThreadLocal); } } diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java similarity index 90% rename from log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java index 2c893aa51f7..6ec90475b93 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextStackTest.java @@ -16,23 +16,26 @@ */ package org.apache.logging.log4j.spi; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import org.apache.logging.log4j.ThreadContext.ContextStack; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.Test; +@UsingAnyThreadContext public class DefaultThreadContextStackTest { - @Before - public void before() { - // clear the thread-local map - new DefaultThreadContextMap(true).clear(); - } - @Test public void testEqualsVsSameKind() { final DefaultThreadContextStack stack1 = createStack(); @@ -45,8 +48,8 @@ public void testEqualsVsSameKind() { @Test public void testEqualsVsMutable() { - final DefaultThreadContextStack stack1 = createStack(); - final MutableThreadContextStack stack2 = MutableThreadContextStackTest.createStack(); + final ThreadContextStack stack1 = createStack(); + final ThreadContextStack stack2 = MutableThreadContextStackTest.createStack(); assertEquals(stack1, stack1); assertEquals(stack2, stack2); assertEquals(stack1, stack2); @@ -64,7 +67,7 @@ public void testHashCodeVsSameKind() { public void testImmutableOrNullReturnsNullIfUseStackIsFalse() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(false); stack.clear(); - assertEquals(null, stack.getImmutableStackOrNull()); + assertNull(stack.getImmutableStackOrNull()); } @Test @@ -72,19 +75,19 @@ public void testImmutableOrNullReturnsNullIfStackIsEmpty() { final DefaultThreadContextStack stack = new DefaultThreadContextStack(true); stack.clear(); assertTrue(stack.isEmpty()); - assertEquals(null, stack.getImmutableStackOrNull()); + assertNull(stack.getImmutableStackOrNull()); } @Test public void testImmutableOrNullReturnsCopyOfContents() { final DefaultThreadContextStack stack = createStack(); - assertTrue(!stack.isEmpty()); + assertFalse(stack.isEmpty()); final ContextStack actual = stack.getImmutableStackOrNull(); assertNotNull(actual); assertEquals(stack, actual); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testModifyingImmutableOrNullThrowsException() { final DefaultThreadContextStack stack = createStack(); final int originalSize = stack.size(); @@ -92,7 +95,7 @@ public void testModifyingImmutableOrNullThrowsException() { final ContextStack actual = stack.getImmutableStackOrNull(); assertEquals(originalSize, actual.size()); - actual.pop(); + assertThrows(UnsupportedOperationException.class, () -> actual.pop()); } @Test @@ -277,7 +280,7 @@ public void testRemove() { stack.remove("msg3"); assertEquals(1, stack.size()); - assertTrue(stack.containsAll(Arrays.asList("msg2"))); + assertTrue(stack.containsAll(Collections.singletonList("msg2"))); assertEquals("msg2", stack.peek()); } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java new file mode 100644 index 00000000000..6a8ed7b6c61 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.spi; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.test.TestLoggerContext; +import org.apache.logging.log4j.test.TestLoggerContextFactory; +import org.apache.logging.log4j.simple.SimpleLoggerContext; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Created by Pavel.Sivolobtchik@uxpsystems.com on 2016-10-19. + */ +public class LoggerAdapterTest { + + private static class RunnableThreadTest implements Runnable { + private final AbstractLoggerAdapter adapter; + private final LoggerContext context; + private final CountDownLatch doneSignal; + private final int index; + private Map resultMap; + + private final CountDownLatch startSignal; + + public RunnableThreadTest(final int index, final TestLoggerAdapter adapter, final LoggerContext context, + final CountDownLatch startSignal, final CountDownLatch doneSignal) { + this.adapter = adapter; + this.context = context; + this.startSignal = startSignal; + this.doneSignal = doneSignal; + this.index = index; + } + + public Map getResultMap() { + return resultMap; + } + + @Override + public void run() { + try { + startSignal.await(); + resultMap = adapter.getLoggersInContext(context); + resultMap.put(String.valueOf(index), new TestLogger()); + doneSignal.countDown(); + } + catch (final Exception e) { + e.printStackTrace(); + } + } + + } + + private static class TestLoggerAdapter extends AbstractLoggerAdapter { + + @Override + protected LoggerContext getContext() { + return null; + } + + @Override + protected Logger newLogger(final String name, final LoggerContext context) { + return null; + } + } + + private static class TestLoggerAdapter2 extends AbstractLoggerAdapter { + + @Override + protected Logger newLogger(String name, LoggerContext context) { + return context.getLogger(name); + } + + @Override + protected LoggerContext getContext() { + return null; + } + + public LoggerContext getContext(String fqcn) { + for (LoggerContext lc : registry.keySet()) { + TestLoggerContext2 context = (TestLoggerContext2) lc; + if (fqcn.equals(context.getName())) { + return context; + } + } + LoggerContext lc = new TestLoggerContext2(fqcn, this); + registry.put(lc, new ConcurrentHashMap<>()); + return lc; + } + } + + private static class TestLoggerContext2 extends TestLoggerContext { + private final String name; + private final LoggerContextShutdownAware listener; + + public TestLoggerContext2(String name, LoggerContextShutdownAware listener) { + this.name = name; + this.listener = listener; + } + + public String getName() { + return name; + } + + public void shutdown() { + listener.contextShutdown(this); + } + } + + @Test + public void testCleanup() throws Exception { + final LoggerContextFactory factory = new TestLoggerContextFactory(); + final TestLoggerAdapter2 adapter = new TestLoggerAdapter2(); + for (int i = 0; i < 5; ++i) { + LoggerContext lc = adapter.getContext(Integer.toString(i)); + lc.getLogger(Integer.toString(i)); + } + assertEquals(5, adapter.registry.size(), "Expected 5 LoggerContexts"); + Set contexts = new HashSet<>(adapter.registry.keySet()); + for (LoggerContext context : contexts) { + ((TestLoggerContext2) context).shutdown(); + } + assertEquals(0, adapter.registry.size(), "Expected 0 LoggerContexts"); + } + + + + /** + * Testing synchronization in the getLoggersInContext() method + */ + @Test + public synchronized void testGetLoggersInContextSynch() throws Exception { + final TestLoggerAdapter adapter = new TestLoggerAdapter(); + + final int num = 500; + + final CountDownLatch startSignal = new CountDownLatch(1); + final CountDownLatch doneSignal = new CountDownLatch(num); + + final RunnableThreadTest[] instances = new RunnableThreadTest[num]; + LoggerContext lastUsedContext = null; + for (int i = 0; i < num; i++) { + if (i % 2 == 0) { + //every other time create a new context + lastUsedContext = new SimpleLoggerContext(); + } + final RunnableThreadTest runnable = new RunnableThreadTest(i, adapter, lastUsedContext, startSignal, doneSignal); + final Thread thread = new Thread(runnable); + thread.start(); + instances[i] = runnable; + } + + startSignal.countDown(); + doneSignal.await(); + + for (int i = 0; i < num; i = i + 2) { + //maps for the same context should be the same instance + final Map resultMap1 = instances[i].getResultMap(); + final Map resultMap2 = instances[i + 1].getResultMap(); + assertSame(resultMap1, resultMap2, "not the same map for instances" + i + " and " + (i + 1) + ":"); + assertEquals(2, resultMap1.size()); + } + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java similarity index 89% rename from log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java index 7592067dbeb..4a4a428ae9e 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/MutableThreadContextStackTest.java @@ -16,20 +16,20 @@ */ package org.apache.logging.log4j.spi; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; -import org.junit.Test; - -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class MutableThreadContextStackTest { @Test public void testEmptyIfConstructedWithEmptyList() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); assertTrue(stack.isEmpty()); } @@ -43,7 +43,7 @@ public void testConstructorCopiesListContents() { @Test public void testPushAndAddIncreaseStack() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); stack.clear(); assertTrue(stack.isEmpty()); stack.push("msg1"); @@ -54,7 +54,7 @@ public void testPushAndAddIncreaseStack() { @Test public void testPeekReturnsLastAddedItem() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); stack.clear(); assertTrue(stack.isEmpty()); stack.push("msg1"); @@ -87,7 +87,7 @@ public void testPopRemovesLastAddedItem() { @Test public void testAsList() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); stack.clear(); assertTrue(stack.isEmpty()); stack.push("msg1"); @@ -165,7 +165,7 @@ public void testHashCodeVsSameKind() { * @return */ static MutableThreadContextStack createStack() { - final MutableThreadContextStack stack1 = new MutableThreadContextStack(new ArrayList()); + final MutableThreadContextStack stack1 = new MutableThreadContextStack(new ArrayList<>()); stack1.clear(); assertTrue(stack1.isEmpty()); stack1.push("msg1"); @@ -228,7 +228,7 @@ public void testRemove() { stack.remove("msg3"); assertEquals(1, stack.size()); - assertTrue(stack.containsAll(Arrays.asList("msg2"))); + assertTrue(stack.contains("msg2")); assertEquals("msg2", stack.peek()); } @@ -276,7 +276,7 @@ public void testRetainAll() { @Test public void testToStringShowsListContents() { - final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList()); + final MutableThreadContextStack stack = new MutableThreadContextStack(new ArrayList<>()); assertEquals("[]", stack.toString()); stack.push("msg1"); @@ -302,66 +302,66 @@ public void testIsFrozenIsTrueAfterCallToFreeze() { assertTrue(stack.isFrozen()); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testAddAllOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.addAll(Arrays.asList("a", "b", "c")); + assertThrows(UnsupportedOperationException.class, () -> stack.addAll(Arrays.asList("a", "b", "c"))); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testAddOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.add("a"); + assertThrows(UnsupportedOperationException.class, () -> stack.add("a")); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testClearOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.clear(); + assertThrows(UnsupportedOperationException.class, stack::clear); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testPopOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.pop(); + assertThrows(UnsupportedOperationException.class, stack::pop); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testPushOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.push("a"); + assertThrows(UnsupportedOperationException.class, () -> stack.push("a")); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testRemoveOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.remove("a"); + assertThrows(UnsupportedOperationException.class, () -> stack.remove("a")); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testRemoveAllOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.removeAll(Arrays.asList("a", "b")); + assertThrows(UnsupportedOperationException.class, () -> stack.removeAll(Arrays.asList("a", "b"))); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testRetainAllOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.retainAll(Arrays.asList("a", "b")); + assertThrows(UnsupportedOperationException.class, () -> stack.retainAll(Arrays.asList("a", "b"))); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testTrimOnFrozenStackThrowsException() { final MutableThreadContextStack stack = new MutableThreadContextStack(); stack.freeze(); - stack.trim(3); + assertThrows(UnsupportedOperationException.class, () -> stack.trim(3)); } } diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java new file mode 100644 index 00000000000..9d761a1ce73 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusConsoleListenerTest.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.status; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +import org.apache.logging.log4j.simple.SimpleLogger; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class StatusConsoleListenerTest { + + public static final MessageFactory MESSAGE_FACTORY = ParameterizedNoReferenceMessageFactory.INSTANCE; + + @Test + void SimpleLogger_should_be_used() { + + // Create a mock `SimpleLoggerFactory`. + final SimpleLogger logger = Mockito.mock(SimpleLogger.class); + final LogBuilder logBuilder = Mockito.mock(LogBuilder.class); + Mockito.when(logger.atLevel(Mockito.any())).thenReturn(logBuilder); + Mockito.when(logBuilder.withThrowable(Mockito.any())).thenReturn(logBuilder); + Mockito.when(logBuilder.withLocation(Mockito.any())).thenReturn(logBuilder); + final StatusLoggerFactory loggerFactory = Mockito.mock(StatusLoggerFactory.class); + Mockito + .when(loggerFactory.createSimpleLogger( + Mockito.any(), + Mockito.any(), + Mockito.any())) + .thenReturn(logger); + + // Create the listener. + final PrintStream stream = Mockito.mock(PrintStream.class); + final Level level = Mockito.mock(Level.class); + final StatusConsoleListener listener = new StatusConsoleListener(level, stream, loggerFactory); + + // Log a message. + final StackTraceElement caller = Mockito.mock(StackTraceElement.class); + final Message message = Mockito.mock(Message.class); + final Throwable throwable = Mockito.mock(Throwable.class); + final StatusData statusData = new StatusData( + caller, + level, + message, + throwable, + null); + listener.log(statusData); + + // Verify the call. + Mockito + .verify(loggerFactory) + .createSimpleLogger( + Mockito.eq("StatusConsoleListener"), + Mockito.same(level), + Mockito.same(stream)); + Mockito.verify(logger).atLevel(Mockito.same(level)); + Mockito.verify(logBuilder).withThrowable(Mockito.same(throwable)); + Mockito.verify(logBuilder).withLocation(Mockito.same(caller)); + Mockito.verify(logBuilder).log(Mockito.same(message)); + + } + + @Test + void level_and_stream_should_be_honored() throws Exception { + + // Create the listener. + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final String encoding = "UTF-8"; + final PrintStream printStream = new PrintStream(outputStream, false, encoding); + final StatusConsoleListener listener = new StatusConsoleListener(Level.WARN, printStream); + + // First, log a message that is expected to be logged. + final RuntimeException expectedThrowable = new RuntimeException("expectedThrowable"); + expectedThrowable.setStackTrace(new StackTraceElement[]{ + new StackTraceElement("expectedThrowableClass", "expectedThrowableMethod", "expectedThrowableFile", 1) + }); + final Message expectedMessage = MESSAGE_FACTORY.newMessage("expectedMessage"); + listener.log(new StatusData( + null, // since ignored by `SimpleLogger` + Level.WARN, + expectedMessage, + expectedThrowable, + null)); // as set by `StatusLogger` itself + + // Second, log a message that is expected to be discarded due to its insufficient level. + final RuntimeException discardedThrowable = new RuntimeException("discardedThrowable"); + discardedThrowable.setStackTrace(new StackTraceElement[]{ + new StackTraceElement("discardedThrowableClass", "discardedThrowableMethod", "discardedThrowableFile", 2) + }); + final Message discardedMessage = MESSAGE_FACTORY.newMessage("discardedMessage"); + listener.log(new StatusData( + null, // since ignored by `SimpleLogger` + Level.INFO, + discardedMessage, + discardedThrowable, + null)); // as set by `StatusLogger` itself + + // Collect the output. + printStream.flush(); + final String output = outputStream.toString(encoding); + + // Verify the output. + Assertions + .assertThat(output) + .isNotBlank() + .contains(expectedThrowable.getMessage()) + .contains(expectedMessage.getFormattedMessage()) + .doesNotContain(discardedThrowable.getMessage()) + .doesNotContain(discardedMessage.getFormattedMessage()); + + } + + @Test + void filters_should_be_honored() throws Exception { + + // Create the listener. + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final String encoding = "UTF-8"; + final PrintStream printStream = new PrintStream(outputStream, false, encoding); + final StatusConsoleListener listener = new StatusConsoleListener(Level.TRACE, printStream); + + // Set the filter. + final StackTraceElement caller = new StackTraceElement("callerClass", "callerMethod", "callerFile", 1); + listener.setFilters(caller.getClassName()); + + // Log the message to be filtered. + final Message message = MESSAGE_FACTORY.newMessage("foo"); + listener.log(new StatusData( + caller, + Level.TRACE, + message, + null, + null)); // as set by `StatusLogger` itself + + // Verify the filtering. + printStream.flush(); + final String output = outputStream.toString(encoding); + Assertions.assertThat(output).isEmpty(); + + } + + @Test + void non_system_streams_should_be_closed() throws Exception { + final PrintStream stream = Mockito.mock(PrintStream.class); + final StatusConsoleListener listener = new StatusConsoleListener(Level.WARN, stream); + listener.close(); + Mockito.verify(stream).close(); + } + +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerSerializationTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerSerializationTest.java new file mode 100644 index 00000000000..fedc5bd9562 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/status/StatusLoggerSerializationTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.status; + +import java.util.stream.Stream; + +import org.apache.logging.log4j.test.AbstractSerializationTest; +import org.junit.jupiter.api.Disabled; + +@Disabled +public class StatusLoggerSerializationTest extends AbstractSerializationTest { + + protected Stream data() { + return Stream.of(StatusLogger.getLogger()); + } + +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/BetterService.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/BetterService.java new file mode 100644 index 00000000000..ca8b893e0ec --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/BetterService.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test; + +public interface BetterService extends Service { +} \ No newline at end of file diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service.java new file mode 100644 index 00000000000..25b1b33681c --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test; + +public interface Service { +} \ No newline at end of file diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service1.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service1.java new file mode 100644 index 00000000000..82c233b852c --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service1.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test; + +public class Service1 implements Service { +} \ No newline at end of file diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service2.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service2.java new file mode 100644 index 00000000000..c4a8c39b979 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/Service2.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.test; + +public class Service2 implements BetterService { +} \ No newline at end of file diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/ThreadContextTest.java similarity index 76% rename from log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/test/ThreadContextTest.java index a1d11016fd6..42c7067cf50 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/ThreadContextTest.java @@ -14,57 +14,53 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.test; import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import org.junit.Test; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * - */ +@UsingAnyThreadContext public class ThreadContextTest { - public static void reinitThreadContext() { - ThreadContext.init(); - } @Test public void testPush() { ThreadContext.push("Hello"); - ThreadContext.push("{} is {}", ThreadContextTest.class.getSimpleName(), - "running"); - assertEquals("Incorrect parameterized stack value", - ThreadContext.pop(), "ThreadContextTest is running"); - assertEquals("Incorrect simple stack value", ThreadContext.pop(), - "Hello"); + ThreadContext.push("{} is {}", ThreadContextTest.class.getSimpleName(), "running"); + assertEquals(ThreadContext.pop(), "ThreadContextTest is running", "Incorrect parameterized stack value"); + assertEquals(ThreadContext.pop(), "Hello", "Incorrect simple stack value"); } @Test public void testInheritanceSwitchedOffByDefault() throws Exception { - ThreadContext.clearMap(); ThreadContext.put("Greeting", "Hello"); StringBuilder sb = new StringBuilder(); TestThread thread = new TestThread(sb); thread.start(); thread.join(); String str = sb.toString(); - assertTrue("Unexpected ThreadContext value. Expected null. Actual " - + str, "null".equals(str)); + assertEquals("null", str, "Unexpected ThreadContext value. Expected null. Actual " + str); sb = new StringBuilder(); thread = new TestThread(sb); thread.start(); thread.join(); str = sb.toString(); - assertTrue("Unexpected ThreadContext value. Expected null. Actual " - + str, "null".equals(str)); + assertEquals("null", str, "Unexpected ThreadContext value. Expected null. Actual " + str); } @Test - public void perfTest() throws Exception { + @Tag("performance") + public void perfTest() { ThreadContextUtilityClass.perfTest(); } @@ -83,12 +79,12 @@ public void testGetImmutableContextReturnsEmptyMapIfEmpty() { ThreadContextUtilityClass.testGetImmutableContextReturnsEmptyMapIfEmpty(); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testGetImmutableContextReturnsImmutableMapIfNonEmpty() { ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfNonEmpty(); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testGetImmutableContextReturnsImmutableMapIfEmpty() { ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfEmpty(); } @@ -104,9 +100,19 @@ public void testPut() { } @Test - public void testPutAll() { + public void testPutIfNotNull() { + ThreadContext.clearMap(); + assertNull(ThreadContext.get("testKey")); + ThreadContext.put("testKey", "testValue"); + assertEquals("testValue", ThreadContext.get("testKey")); + assertEquals("testValue", ThreadContext.get("testKey"), "Incorrect value in test key"); + ThreadContext.putIfNull("testKey", "new Value"); + assertEquals("testValue", ThreadContext.get("testKey"), "Incorrect value in test key"); ThreadContext.clearMap(); - // + } + + @Test + public void testPutAll() { assertTrue(ThreadContext.isEmpty()); assertFalse(ThreadContext.containsKey("key")); final int mapSize = 10; @@ -124,7 +130,6 @@ public void testPutAll() { @Test public void testRemove() { - ThreadContext.clearMap(); assertNull(ThreadContext.get("testKey")); ThreadContext.put("testKey", "testValue"); assertEquals("testValue", ThreadContext.get("testKey")); @@ -136,7 +141,6 @@ public void testRemove() { @Test public void testRemoveAll() { - ThreadContext.clearMap(); ThreadContext.put("testKey1", "testValue1"); ThreadContext.put("testKey2", "testValue2"); assertEquals("testValue1", ThreadContext.get("testKey1")); @@ -151,7 +155,6 @@ public void testRemoveAll() { @Test public void testContainsKey() { - ThreadContext.clearMap(); assertFalse(ThreadContext.containsKey("testKey")); ThreadContext.put("testKey", "testValue"); assertTrue(ThreadContext.containsKey("testKey")); @@ -160,7 +163,7 @@ public void testContainsKey() { assertFalse(ThreadContext.containsKey("testKey")); } - private class TestThread extends Thread { + private static class TestThread extends Thread { private final StringBuilder sb; diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/test/junit/TestPropertySourceTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/junit/TestPropertySourceTest.java new file mode 100644 index 00000000000..2962659b55b --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/test/junit/TestPropertySourceTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.test.junit; + +import org.apache.logging.log4j.test.TestProperties; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@UsingTestProperties +public class TestPropertySourceTest { + + private static TestProperties staticProperties; + private TestProperties instanceProperties; + + @Test + public void testInjectedFields() { + assertThat(staticProperties).isNotNull(); + assertThat(instanceProperties).isNotNull(); + + // Test that per-class properties are overridden by per-test properties + final PropertyEnvironment env = PropertiesUtil.getProperties(); + staticProperties.setProperty("log4j2.staticProperty", "static"); + staticProperties.setProperty("log4j2.instanceProperty", "static"); + instanceProperties.setProperty("log4j2.instanceProperty", "instance"); + assertThat(env.getStringProperty("log4j2.staticProperty")).isEqualTo("static"); + assertThat(env.getStringProperty("log4j.instanceProperty")).isEqualTo("instance"); + } + + @Test + public void testInjectedParameter(final TestProperties paramProperties) { + assertThat(paramProperties).isEqualTo(instanceProperties); + } + + @Test + @SetTestProperty(key = "log4j2.testSetTestProperty", value = "true") + public void testSetTestProperty() { + final PropertyEnvironment env = PropertiesUtil.getProperties(); + assertThat(env.getBooleanProperty("log4j2.testSetTestProperty")).isTrue(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/AssertTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/AssertTest.java similarity index 75% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/AssertTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/AssertTest.java index 242c41e892c..9592ee59348 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/AssertTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/AssertTest.java @@ -14,28 +14,19 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * - */ -@RunWith(Parameterized.class) public class AssertTest { - private final Object value; - private final boolean isEmpty; - - @Parameterized.Parameters public static Object[][] data() { return new Object[][]{ // value, isEmpty @@ -55,14 +46,10 @@ public static Object[][] data() { }; } - public AssertTest(final Object value, final boolean isEmpty) { - this.value = value; - this.isEmpty = isEmpty; - } - - @Test - public void isEmpty() throws Exception { + @ParameterizedTest + @MethodSource("data") + public void isEmpty(Object value, boolean isEmpty) throws Exception { assertEquals(isEmpty, Assert.isEmpty(value)); } -} \ No newline at end of file +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsTest.java new file mode 100644 index 00000000000..fb5e4664a40 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.util.stream.IntStream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CharsTest { + @ParameterizedTest + @ValueSource(ints = {-1, 16, 400, -1, 16, 400}) + public void invalidDigitReturnsNullCharacter(int invalidDigit) { + assertAll( + () -> assertEquals('\0', Chars.getUpperCaseHex(invalidDigit)), + () -> assertEquals('\0', Chars.getLowerCaseHex(invalidDigit)) + ); + } + + @Test + public void validDigitReturnsProperCharacter() { + final char[] expectedLower = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + final char[] expectedUpper = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + assertAll(IntStream.range(0, 16).mapToObj(i -> () -> assertAll( + () -> assertEquals(expectedLower[i], Chars.getLowerCaseHex(i), String.format("Expected %x", i)), + () -> assertEquals(expectedUpper[i], Chars.getUpperCaseHex(i), String.format("Expected %X", i)) + ))); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java similarity index 100% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/CharsetForNameMain.java diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassLocator.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassLocator.java similarity index 100% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/ClassLocator.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassLocator.java diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java similarity index 100% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/ClassNameLocator.java diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ConstantsTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ConstantsTest.java new file mode 100644 index 00000000000..0c028b898fe --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ConstantsTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ConstantsTest { + + @Test + public void testJdkVersionDetection() { + assertEquals(1, Constants.getMajorVersion("1.1.2")); + assertEquals(8, Constants.getMajorVersion("1.8.2")); + assertEquals(9, Constants.getMajorVersion("9.1.1")); + assertEquals(11, Constants.getMajorVersion("11.1.1")); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java similarity index 97% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java index 930471d9cd2..31030d3d759 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java @@ -26,6 +26,7 @@ * @see SortedArrayStringMapTest#testDeserializationOfUnknownClass() */ public class DeserializerHelper { + @SuppressWarnings("BanSerializableRead") public static void main(final String... args) throws Exception { final File file = new File(args[0]); ObjectInputStream in = null; diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceSecurityManagerIT.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceSecurityManagerIT.java new file mode 100644 index 00000000000..24b0da537ba --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceSecurityManagerIT.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.security.Permission; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.parallel.ResourceLock; + +/** + * Tests LOG4J2-2274. + *

+ * Using a security manager can mess up other tests so this is best used from + * integration tests (classes that end in "IT" instead of "Test" and + * "TestCase".) + *

+ * + * @see EnvironmentPropertySource + * @see SecurityManager + * @see System#setSecurityManager(SecurityManager) + */ +@ResourceLock("java.lang.SecurityManager") +@DisabledForJreRange(min = JRE.JAVA_18) // custom SecurityManager instances throw UnsupportedOperationException +public class EnvironmentPropertySourceSecurityManagerIT { + + /** + * Always throws a SecurityException for any environment variables permission + * check. + */ + private static class TestSecurityManager extends SecurityManager { + @Override + public void checkPermission(final Permission permission) { + if ("getenv.*".equals(permission.getName())) { + throw new SecurityException(); + } + } + } + + /** + * Makes sure we do not blow up with exception below due to a security manager + * rejecting environment variable access in {@link EnvironmentPropertySource}. + * + *
+     * java.lang.NoClassDefFoundError: Could not initialize class org.apache.logging.log4j.util.PropertiesUtil
+     *     at org.apache.logging.log4j.status.StatusLogger.(StatusLogger.java:78)
+     *     at org.apache.logging.log4j.core.AbstractLifeCycle.(AbstractLifeCycle.java:38)
+     *     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
+     *     at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
+     *     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
+     *     at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
+     *     at org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder.build(DefaultConfigurationBuilder.java:172)
+     *     at org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder.build(DefaultConfigurationBuilder.java:161)
+     *     at org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder.build(DefaultConfigurationBuilder.java:1)
+     *     at org.apache.logging.log4j.util.EnvironmentPropertySourceSecurityManagerTest.test(EnvironmentPropertySourceSecurityManagerTest.java:55)
+     * 
+ */ + @Test + public void test() { + var existing = System.getSecurityManager(); + try { + System.setSecurityManager(new TestSecurityManager()); + PropertiesUtil.getProperties(); + } finally { + System.setSecurityManager(existing); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java new file mode 100644 index 00000000000..407915f5175 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EnvironmentPropertySourceTest { + + private final PropertySource source = new EnvironmentPropertySource(); + + public static Object[][] data() { + return new Object[][]{ + {"LOG4J_CONFIGURATION_FILE", Arrays.asList("configuration", "file")}, + {"LOG4J_FOO_BAR_PROPERTY", Arrays.asList("foo", "bar", "property")}, + {"LOG4J_EXACT", Collections.singletonList("EXACT")}, + {"LOG4J_TEST_PROPERTY_NAME", PropertySource.Util.tokenize("Log4jTestPropertyName")}, + {null, Collections.emptyList()} + }; + } + + @ParameterizedTest + @MethodSource("data") + public void testNormalFormFollowsEnvironmentVariableConventions(CharSequence expected, List tokens) { + assertEquals(expected, source.getNormalForm(tokens)); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LambdaUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LambdaUtilTest.java new file mode 100644 index 00000000000..a4671cab3fd --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LambdaUtilTest.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.util; + +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the LambdaUtil class. + */ +public class LambdaUtilTest { + + @Test + public void testGetSupplierResultOfSupplier() { + final String expected = "result"; + final Object actual = LambdaUtil.get((Supplier) () -> expected); + assertSame(expected, actual); + } + + @Test + public void testGetMessageSupplierResultOfSupplier() { + final Message expected = new SimpleMessage("hi"); + final Message actual = LambdaUtil.get(() -> expected); + assertSame(expected, actual); + } + + @Test + public void testGetSupplierReturnsNullIfSupplierNull() { + final Object actual = LambdaUtil.get((Supplier) null); + assertNull(actual); + } + + @Test + public void testGetMessageSupplierReturnsNullIfSupplierNull() { + final Object actual = LambdaUtil.get((MessageSupplier) null); + assertNull(actual); + } + + @Test + public void testGetSupplierExceptionIfSupplierThrowsException() { + assertThrows(RuntimeException.class, () -> LambdaUtil.get((Supplier) () -> { + throw new RuntimeException(); + })); + } + + @Test + public void testGetMessageSupplierExceptionIfSupplierThrowsException() { + assertThrows(RuntimeException.class, () -> LambdaUtil.get(() -> { + throw new RuntimeException(); + })); + } + + @Test + public void testGetAllReturnsResultOfSuppliers() { + final String expected1 = "result1"; + final Supplier function1 = () -> expected1; + final String expected2 = "result2"; + final Supplier function2 = () -> expected2; + + final Supplier[] functions = { function1, function2 }; + final Object[] actual = LambdaUtil.getAll(functions); + assertEquals(actual.length, functions.length); + assertSame(expected1, actual[0]); + assertSame(expected2, actual[1]); + } + + @Test + public void testGetAllReturnsNullArrayIfSupplierArrayNull() { + final Object[] actual = LambdaUtil.getAll((Supplier[]) null); + assertNull(actual); + } + + @Test + public void testGetAllReturnsNullElementsIfSupplierArrayContainsNulls() { + final Supplier[] functions = new Supplier[3]; + final Object[] actual = LambdaUtil.getAll(functions); + assertEquals(actual.length, functions.length); + for (final Object object : actual) { + assertNull(object); + } + } + + @Test + public void testGetAllThrowsExceptionIfAnyOfTheSuppliersThrowsException() { + final Supplier function1 = () -> "abc"; + final Supplier function2 = () -> { + throw new RuntimeException(); + }; + + final Supplier[] functions = { function1, function2 }; + assertThrows(RuntimeException.class, () -> LambdaUtil.getAll(functions)); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java similarity index 89% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java index b979d9ad974..d43150b7cdb 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java @@ -16,30 +16,19 @@ */ package org.apache.logging.log4j.util; -import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; -@RunWith(Parameterized.class) public class LegacyPropertiesCompatibilityTest { - private final CharSequence newName; - private final CharSequence oldName; - - public LegacyPropertiesCompatibilityTest(final CharSequence newName, final CharSequence oldName) { - this.newName = newName; - this.oldName = oldName; - } - - @Parameterized.Parameters(name = "New: {0}; Old: {1}") public static Object[][] data() { return new Object[][]{ {"log4j2.configurationFile", "log4j.configurationFile"}, - {"log4j2.mergeFactory", "log4j.mergeFactory"}, + {"log4j2.mergeStrategy", "log4j.mergeStrategy"}, {"log4j2.contextSelector", "Log4jContextSelector"}, {"log4j2.logEventFactory", "Log4jLogEventFactory"}, {"log4j2.configurationFactory", "log4j.configurationFactory"}, @@ -94,10 +83,11 @@ public static Object[][] data() { }; } - @Test - public void compareNewWithOldName() throws Exception { + @ParameterizedTest + @MethodSource("data") + public void compareNewWithOldName(final String newName, final String oldName) { final List newTokens = PropertySource.Util.tokenize(newName); final List oldTokens = PropertySource.Util.tokenize(oldName); assertEquals(oldTokens, newTokens); } -} \ No newline at end of file +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilTest.java new file mode 100644 index 00000000000..13700572d3a --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LoaderUtilTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ReadsSystemProperty +public class LoaderUtilTest { + @BeforeEach + @AfterEach + public void reset() { + LoaderUtil.forceTcclOnly = null; + } + + @Test + public void systemClassLoader() { + final Thread thread = Thread.currentThread(); + final ClassLoader tccl = thread.getContextClassLoader(); + + LoaderUtil.forceTcclOnly = true; + final ClassLoader loader = new ClassLoader(tccl) { + @Override + public Enumeration getResources(final String name) { + return Collections.emptyEnumeration(); + } + }; + thread.setContextClassLoader(loader); + try { + assertEquals(0, LoaderUtil.findUrlResources("Log4j-charsets.properties", false).size()); + + LoaderUtil.forceTcclOnly = false; + assertEquals(1, LoaderUtil.findUrlResources("Log4j-charsets.properties", false).size()); + } finally { + thread.setContextClassLoader(tccl); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Log4jCharsetsPropertiesTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Log4jCharsetsPropertiesTest.java new file mode 100644 index 00000000000..7e2b48ccfe6 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Log4jCharsetsPropertiesTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.nio.charset.Charset; +import java.util.Enumeration; +import java.util.ResourceBundle; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Log4jCharsetsPropertiesTest { + + /** + * Tests that we can load all mappings. + */ + @Test + public void testLoadAll() { + ResourceBundle resourceBundle = PropertiesUtil.getCharsetsResourceBundle(); + Enumeration keys = resourceBundle.getKeys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + assertFalse( + Charset.isSupported(key), String.format("The Charset %s is available and should not be mapped", key)); + String value = resourceBundle.getString(key); + assertTrue( + Charset.isSupported(value), + String.format("The Charset %s is not available and is mapped from %s", value, key)); + } + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java similarity index 75% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java index c1c97efaa8b..5e741d20d5c 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesPropertySourceTest.java @@ -16,41 +16,33 @@ */ package org.apache.logging.log4j.util; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - import static org.junit.Assert.assertEquals; -@RunWith(Parameterized.class) public class PropertiesPropertySourceTest { private final PropertySource source = new PropertiesPropertySource(new Properties()); - private final CharSequence expected; - private final List tokens; - - public PropertiesPropertySourceTest(final String expected, final List tokens) { - this.expected = expected; - this.tokens = tokens; - } - @Parameterized.Parameters(name = "{0}") public static Object[][] data() { return new Object[][]{ {"log4j2.configurationFile", Arrays.asList("configuration", "file")}, {"log4j2.fooBarProperty", Arrays.asList("foo", "bar", "property")}, {"log4j2.EXACT", Collections.singletonList("EXACT")}, {"log4j2.testPropertyName", PropertySource.Util.tokenize("Log4jTestPropertyName")}, + {null, Collections.emptyList()} }; } - @Test - public void testNormalFormFollowsCamelCaseConventions() throws Exception { + @ParameterizedTest + @MethodSource("data") + public void testNormalFormFollowsCamelCaseConventions(final String expected, final List tokens) { assertEquals(expected, source.getNormalForm(tokens)); } -} \ No newline at end of file +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilOrderTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilOrderTest.java new file mode 100644 index 00000000000..1dc07c7c849 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilOrderTest.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.io.InputStream; +import java.util.Properties; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import uk.org.webcompere.systemstubs.properties.SystemProperties; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SystemStubsExtension.class) +@ResourceLock(value = Resources.SYSTEM_PROPERTIES) +public class PropertiesUtilOrderTest { + + public static class NonEnumerablePropertySource implements PropertySource { + + private final Properties props; + + public NonEnumerablePropertySource(final Properties props) { + this.props = props; + } + + @Override + public int getPriority() { + return Integer.MIN_VALUE; + } + + @Override + public CharSequence getNormalForm(Iterable tokens) { + final CharSequence camelCase = PropertySource.Util.joinAsCamelCase(tokens); + return camelCase.length() > 0 ? "log4j2." + camelCase : null; + } + + @Override + public String getProperty(String key) { + return props.getProperty(key); + } + + @Override + public boolean containsProperty(String key) { + return getProperty(key) != null; + } + + } + + public static class NullPropertySource implements PropertySource { + + @Override + public int getPriority() { + return Integer.MIN_VALUE; + } + + } + + private final Properties properties = new Properties(); + + @BeforeEach + public void setUp() throws Exception { + try (final InputStream is = ClassLoader.getSystemResourceAsStream("PropertiesUtilOrderTest.properties")) { + properties.load(is); + } + } + + @Test + public void testNormalizedOverrideLegacy() { + final PropertiesUtil util = new PropertiesUtil(properties); + final String legacy = "props.legacy"; + final String normalized = "props.normalized"; + assertEquals(legacy, properties.getProperty("log4j.legacyProperty")); + assertTrue(util.hasProperty("log4j.legacyProperty")); + assertEquals(normalized, util.getStringProperty("log4j.legacyProperty")); + assertEquals(legacy, properties.getProperty("org.apache.logging.log4j.legacyProperty2")); + assertTrue(util.hasProperty("log4j.legacyProperty2")); + assertEquals(normalized, util.getStringProperty("org.apache.logging.log4j.legacyProperty2")); + assertEquals(legacy, properties.getProperty("Log4jLegacyProperty3")); + assertTrue(util.hasProperty("log4j.legacyProperty3")); + assertEquals(normalized, util.getStringProperty("Log4jLegacyProperty3")); + // non-overridden legacy property + assertTrue(util.hasProperty("log4j.nonOverriddenLegacy")); + assertEquals(legacy, util.getStringProperty("log4j.nonOverriddenLegacy")); + } + + @Test + public void testFallsBackToTokenMatching() { + final PropertiesUtil util = new PropertiesUtil(properties); + for (int i = 1; i <= 4; i++) { + final String key = "log4j2.tokenBasedProperty" + i; + assertTrue(util.hasProperty(key)); + assertEquals("props.token", util.getStringProperty(key)); + } + // No fall back (a normalized property is present) + assertTrue(util.hasProperty("log4j2.normalizedProperty")); + assertEquals("props.normalized", util.getStringProperty("log4j2.normalizedProperty")); + } + + @Test + public void testOrderOfNormalizedProperties(EnvironmentVariables env, SystemProperties sysProps) { + properties.remove("log4j2.normalizedProperty"); + properties.remove("LOG4J_normalized.property"); + final PropertiesUtil util = new PropertiesUtil(properties); + // Same result for both a legacy property and a normalized property + assertFalse(util.hasProperty("Log4jNormalizedProperty")); + assertEquals(null, util.getStringProperty("Log4jNormalizedProperty")); + assertFalse(util.hasProperty("log4j2.normalizedProperty")); + assertEquals(null, util.getStringProperty("log4j2.normalizedProperty")); + + properties.setProperty("log4j2.normalizedProperty", "props.normalized"); + util.reload(); + assertTrue(util.hasProperty("Log4jNormalizedProperty")); + assertEquals("props.normalized", util.getStringProperty("Log4jNormalizedProperty")); + assertTrue(util.hasProperty("log4j2.normalizedProperty")); + assertEquals("props.normalized", util.getStringProperty("log4j2.normalizedProperty")); + + env.set("LOG4J_NORMALIZED_PROPERTY", "env"); + util.reload(); + assertTrue(util.hasProperty("Log4jNormalizedProperty")); + assertEquals("env", util.getStringProperty("Log4jNormalizedProperty")); + assertTrue(util.hasProperty("log4j2.normalizedProperty")); + assertEquals("env", util.getStringProperty("log4j2.normalizedProperty")); + + sysProps.set("log4j2.normalizedProperty", "sysProps"); + util.reload(); + assertTrue(util.hasProperty("Log4jNormalizedProperty")); + assertEquals("sysProps", util.getStringProperty("Log4jNormalizedProperty")); + assertTrue(util.hasProperty("log4j2.normalizedProperty")); + assertEquals("sysProps", util.getStringProperty("log4j2.normalizedProperty")); + } + + @Test + public void testHighPriorityNonEnumerableSource(SystemProperties sysProps) { + // In both datasources + assertNotNull(properties.getProperty("log4j2.normalizedProperty")); + assertNotNull(properties.getProperty("log4j.onlyLegacy")); + sysProps.set("log4j2.normalizedProperty", "sysProps.normalized"); + sysProps.set("log4j.onlyLegacy", "sysProps.legazy"); + // Only system properties + assertNull(properties.getProperty("log4j2.normalizedPropertySysProps")); + assertNull(properties.getProperty("log4j.onlyLegacySysProps")); + sysProps.set("log4j2.normalizedPropertySysProps", "sysProps.normalized"); + sysProps.set("log4j.onlyLegacySysProps", "sysProps.legacy"); + // Only the non enumerable source + assertNotNull(properties.getProperty("log4j2.normalizedPropertyProps")); + assertNotNull(properties.getProperty("log4j.onlyLegacyProps")); + + final PropertiesUtil util = new PropertiesUtil(new NonEnumerablePropertySource(properties)); + assertTrue(util.hasProperty("log4j2.normalizedProperty")); + assertEquals("props.normalized", util.getStringProperty("log4j2.normalizedProperty")); + assertTrue(util.hasProperty("log4j.onlyLegacy")); + assertEquals("props.legacy", util.getStringProperty("log4j.onlyLegacy")); + assertTrue(util.hasProperty("log4j2.normalizedPropertySysProps")); + assertEquals("sysProps.normalized", util.getStringProperty("log4j2.normalizedPropertySysProps")); + assertTrue(util.hasProperty("log4j.onlyLegacySysProps")); + assertEquals("sysProps.legacy", util.getStringProperty("log4j.onlyLegacySysProps")); + assertTrue(util.hasProperty("log4j2.normalizedPropertyProps")); + assertEquals("props.normalized", util.getStringProperty("log4j2.normalizedPropertyProps")); + assertTrue(util.hasProperty("log4j.onlyLegacyProps")); + assertEquals("props.legacy", util.getStringProperty("log4j.onlyLegacyProps")); + } + + /** + * Checks the for missing null checks. The {@link NullPropertySource} returns + * {@code null} in almost every call. + * + * @param sysProps + */ + @Test + public void testNullChecks(SystemProperties sysProps) { + sysProps.set("log4j2.someProperty", "sysProps"); + sysProps.set("Log4jLegacyProperty", "sysProps"); + final PropertiesUtil util = new PropertiesUtil(new NullPropertySource()); + assertTrue(util.hasProperty("log4j2.someProperty")); + assertEquals("sysProps", util.getStringProperty("log4j2.someProperty")); + assertTrue(util.hasProperty("Log4jLegacyProperty")); + assertEquals("sysProps", util.getStringProperty("Log4jLegacyProperty")); + assertTrue(util.hasProperty("log4j.legacyProperty")); + assertEquals("sysProps", util.getStringProperty("log4j.legacyProperty")); + assertFalse(util.hasProperty("log4j2.nonExistentProperty")); + assertNull(util.getStringProperty("log4j2.nonExistentProperty")); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilTest.java new file mode 100644 index 00000000000..94ac38d882c --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertiesUtilTest.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Properties; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +public class PropertiesUtilTest { + + private final Properties properties = new Properties(); + + @BeforeEach + public void setUp() throws Exception { + properties.load(ClassLoader.getSystemResourceAsStream("PropertiesUtilTest.properties")); + } + + @Test + public void testExtractSubset() { + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "a")); + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "b.")); + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "c.1")); + assertHasAllProperties(PropertiesUtil.extractSubset(properties, "dd")); + assertThat(properties).containsOnly(Map.entry("a", "invalid")); + } + + @Test + public void testPartitionOnCommonPrefix() { + final Map parts = PropertiesUtil.partitionOnCommonPrefixes(properties); + assertEquals(4, parts.size()); + assertHasAllProperties(parts.get("a")); + assertHasAllProperties(parts.get("b")); + assertHasAllProperties(PropertiesUtil.partitionOnCommonPrefixes(parts.get("c")).get("1")); + assertHasAllProperties(parts.get("dd")); + } + + private static void assertHasAllProperties(final Properties properties) { + assertNotNull(properties); + assertEquals("1", properties.getProperty("1")); + assertEquals("2", properties.getProperty("2")); + assertEquals("3", properties.getProperty("3")); + } + + + @Test + public void testGetCharsetProperty() { + final Properties p = new Properties(); + p.setProperty("e.1", StandardCharsets.US_ASCII.name()); + p.setProperty("e.2", "wrong-charset-name"); + final PropertiesUtil pu = new PropertiesUtil(p); + + assertEquals(Charset.defaultCharset(), pu.getCharsetProperty("e.0")); + assertEquals(StandardCharsets.US_ASCII, pu.getCharsetProperty("e.1")); + assertEquals(Charset.defaultCharset(), pu.getCharsetProperty("e.2")); + } + + @Test + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + public void testGetMappedProperty_sun_stdout_encoding() { + final PropertiesUtil pu = new PropertiesUtil(System.getProperties()); + Charset expected = System.console() == null ? Charset.defaultCharset() : StandardCharsets.UTF_8; + assertEquals(expected, pu.getCharsetProperty("sun.stdout.encoding")); + } + + @Test + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + public void testGetMappedProperty_sun_stderr_encoding() { + final PropertiesUtil pu = new PropertiesUtil(System.getProperties()); + Charset expected = System.console() == null ? Charset.defaultCharset() : StandardCharsets.UTF_8; + assertEquals(expected, pu.getCharsetProperty("sun.err.encoding")); + } + + @Test + @ResourceLock(Resources.SYSTEM_PROPERTIES) + public void testNonStringSystemProperties() { + Object key1 = "1"; + Object key2 = new Object(); + System.getProperties().put(key1, new Object()); + System.getProperties().put(key2, "value-2"); + try { + final PropertiesUtil util = new PropertiesUtil(new Properties()); + assertNull(util.getStringProperty("1")); + } finally { + System.getProperties().remove(key1); + System.getProperties().remove(key2); + } + } + + @Test + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + public void testPublish() { + final Properties props = new Properties(); + final PropertiesUtil util = new PropertiesUtil(props); + String value = System.getProperty("Application"); + assertNotNull(value, "System property was not published"); + assertEquals("Log4j", value); + } + + private static final String[][] data = { + { null, "org.apache.logging.log4j.level" }, + { null, "Log4jAnotherProperty" }, + { null, "log4j2.catalinaBase" }, + { "ok", "log4j2.configurationFile" }, + { "ok", "log4j2.defaultStatusLevel" }, + { "ok", "log4j2.newLevel" }, + { "ok", "log4j2.asyncLoggerTimeout" }, + { "ok", "log4j2.asyncLoggerConfigRingBufferSize" }, + { "ok", "log4j2.disableThreadContext" }, + { "ok", "log4j2.disableThreadContextStack" }, + { "ok", "log4j2.disableThreadContextMap" }, + { "ok", "log4j2.isThreadContextMapInheritable" } + }; + + /** + * LOG4J2-3413: Log4j should only resolve properties that start with a 'log4j' + * prefix or similar. + */ + @Test + @ResourceLock(value = Resources.SYSTEM_PROPERTIES, mode = ResourceAccessMode.READ) + public void testResolvesOnlyLog4jProperties() { + final PropertiesUtil util = new PropertiesUtil("Jira3413Test.properties"); + for (final String[] pair : data) { + assertEquals(pair[0], util.getStringProperty(pair[1])); + } + } + + /** + * LOG4J2-3559: the fix for LOG4J2-3413 returns the value of 'log4j2.' for each + * property not starting with 'log4j'. + */ + @Test + @ReadsSystemProperty + public void testLog4jProperty() { + final Properties props = new Properties(); + final String incorrect = "log4j2."; + final String correct = "not.starting.with.log4j"; + props.setProperty(incorrect, incorrect); + props.setProperty(correct, correct); + final PropertiesUtil util = new PropertiesUtil(props); + assertEquals(correct, util.getStringProperty(correct)); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertyFilePropertySourceSecurityManagerIT.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertyFilePropertySourceSecurityManagerIT.java new file mode 100644 index 00000000000..8d137efa848 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertyFilePropertySourceSecurityManagerIT.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.io.FilePermission; +import java.nio.file.Paths; +import java.security.Permission; +import java.util.PropertyPermission; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.parallel.ResourceLock; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test related to LOG4J2-2274. + *

+ * Using a security manager can mess up other tests so this is best used from + * integration tests (classes that end in "IT" instead of "Test" and + * "TestCase".) + *

+ * + * @see PropertyFilePropertySource + * @see SecurityManager + * @see System#setSecurityManager(SecurityManager) + * @see PropertyPermission + */ +@ResourceLock("java.lang.SecurityManager") +@DisabledForJreRange(min = JRE.JAVA_18) // custom SecurityManager instances throw UnsupportedOperationException +public class PropertyFilePropertySourceSecurityManagerIT { + + @BeforeAll + public static void beforeClass() { + assertThat(Paths.get(TEST_FIXTURE_PATH)).exists(); + } + + private static final String TEST_FIXTURE_PATH = "src/test/resources/PropertiesUtilTest.properties"; + + /** + * Always throws a SecurityException for any environment variables permission + * check. + */ + private static class TestSecurityManager extends SecurityManager { + + @Override + public void checkPermission(final Permission permission) { + if (permission instanceof FilePermission && permission.getName().endsWith(TEST_FIXTURE_PATH)) { + throw new SecurityException(); + } + } + } + + /** + * Makes sure we do not blow up with exception below due to a security manager + * rejecting environment variable access in + * {@link SystemPropertiesPropertySource}. + * + *
+     * 
+ */ + @Test + public void test() { + var existing = System.getSecurityManager(); + try { + System.setSecurityManager(new TestSecurityManager()); + final PropertiesUtil propertiesUtil = new PropertiesUtil(TEST_FIXTURE_PATH); + assertThat(propertiesUtil.getStringProperty("a.1")).isNull(); + } finally { + System.setSecurityManager(existing); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceCamelCaseTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceCamelCaseTest.java new file mode 100644 index 00000000000..aea6942c325 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceCamelCaseTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class PropertySourceCamelCaseTest { + + public static Object[][] data() { + return new Object[][]{ + {"", Collections.singletonList("")}, + {"foo", Collections.singletonList("foo")}, + {"fooBar", Arrays.asList("foo", "bar")}, + {"oneTwoThree", Arrays.asList("one", "two", "three")}, + }; + } + + @ParameterizedTest + @MethodSource("data") + public void testJoinAsCamelCase(final CharSequence expected, final List tokens) { + assertEquals(expected, PropertySource.Util.joinAsCamelCase(tokens)); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java similarity index 80% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java index dd1d49cbbe5..6dbf4d0f409 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/PropertySourceTokenizerTest.java @@ -16,28 +16,17 @@ */ package org.apache.logging.log4j.util; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -@RunWith(Parameterized.class) public class PropertySourceTokenizerTest { - private final CharSequence value; - private final List expectedTokens; - - public PropertySourceTokenizerTest(final CharSequence value, final List expectedTokens) { - this.value = value; - this.expectedTokens = expectedTokens; - } - - @Parameterized.Parameters(name = "{0}") public static Object[][] data() { return new Object[][]{ {"log4j.simple", Collections.singletonList("simple")}, @@ -55,12 +44,17 @@ public static Object[][] data() { {"log4j2-dashed-propertyName", Arrays.asList("dashed", "property", "name")}, {"Log4jProperty_with.all-the/separators", Arrays.asList("property", "with", "all", "the", "separators")}, {"org.apache.logging.log4j.config.property", Arrays.asList("config", "property")}, + // LOG4J2-3413 + {"level", Collections.emptyList()}, + {"user.home", Collections.emptyList()}, + {"CATALINA_BASE", Collections.emptyList()} }; } - @Test - public void testTokenize() throws Exception { + @ParameterizedTest + @MethodSource("data") + public void testTokenize(final CharSequence value, final List expectedTokens) { List tokens = PropertySource.Util.tokenize(value); assertEquals(expectedTokens, tokens); } -} \ No newline at end of file +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java similarity index 84% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java index ce4236afb10..a9b4cb950d1 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java @@ -21,11 +21,11 @@ import java.net.URLClassLoader; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.TestLoggerContext; import org.apache.logging.log4j.spi.LoggerContext; -import org.junit.Test; +import org.apache.logging.log4j.test.TestLoggerContext; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ProviderUtilTest { @@ -37,10 +37,10 @@ public void complexTest() throws Exception { worker.setContextClassLoader(classLoader); worker.start(); worker.join(); - assertTrue("Incorrect LoggerContext", worker.context instanceof TestLoggerContext); + assertTrue(worker.context instanceof TestLoggerContext, "Incorrect LoggerContext"); } - private class Worker extends Thread { + private static class Worker extends Thread { LoggerContext context = null; @Override diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.java new file mode 100644 index 00000000000..ab8e9051d0e --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ServiceLoaderUtilTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.lang.invoke.MethodHandles; +import java.util.Collections; +import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.test.BetterService; +import org.apache.logging.log4j.test.Service; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class ServiceLoaderUtilTest { + + @Test + public void testServiceResolution() { + // Run only if we are a module + if (ServiceLoaderUtil.class.getModule().isNamed()) { + List services = Collections.emptyList(); + // Service from test module + try { + services = ServiceLoaderUtil.loadServices(Service.class, MethodHandles.lookup()) + .collect(Collectors.toList()); + } catch (ServiceConfigurationError e) { + fail(e); + } + assertEquals(2, services.size(), "Service services"); + // BetterService from test module + services.clear(); + try { + services = ServiceLoaderUtil.loadServices(BetterService.class, MethodHandles.lookup()) + .collect(Collectors.toList()); + } catch (ServiceConfigurationError e) { + fail(e); + } + assertEquals(1, services.size(), "BetterService services"); + // PropertySource from org.apache.logging.log4j module from this module + services.clear(); + try { + services = ServiceLoaderUtil.loadServices(PropertySource.class, MethodHandles.lookup()) + .collect(Collectors.toList()); + } catch (ServiceConfigurationError e) { + fail(e); + } + assertEquals(0, services.size(), "PropertySource services"); + } + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java similarity index 79% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java index 3d9ce3e8ae4..af08dbc0a5b 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java @@ -29,34 +29,46 @@ import java.net.URL; import java.net.URLDecoder; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Map; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the SortedArrayStringMap class. */ public class SortedArrayStringMapTest { - @Test(expected = IllegalArgumentException.class) - public void testConstructorDisallowsNegativeCapacity() throws Exception { - new SortedArrayStringMap(-1); + @Test + public void testConstructorDisallowsNegativeCapacity() { + assertThrows(IllegalArgumentException.class, () -> new SortedArrayStringMap(-1)); } - public void testConstructorAllowsZeroCapacity() throws Exception { + @Test + public void testConstructorAllowsZeroCapacity() { SortedArrayStringMap sortedArrayStringMap = new SortedArrayStringMap(0); assertEquals(0, sortedArrayStringMap.size()); } @Test - public void testConstructorIgnoresNull() throws Exception { + public void testConstructorIgnoresNull() { assertEquals(0, new SortedArrayStringMap((SortedArrayStringMap) null).size()); } + @Test + public void testConstructorNonStringKeys() { + Map map = new HashMap<>(1); + map.put(Long.MAX_VALUE, 1); + map.put(null, null); + SortedArrayStringMap sMap = new SortedArrayStringMap((Map) map); + assertEquals(1, (int) sMap.getValue(Long.toString(Long.MAX_VALUE))); + assertEquals((Integer) null, sMap.getValue(null)); + } + @Test public void testToString() { final SortedArrayStringMap original = new SortedArrayStringMap(); @@ -70,7 +82,7 @@ public void testToString() { public void testSerialization() throws Exception { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); - original.putValue("B", "Bvalue"); + original.putValue("B", null); // null may be treated differently original.putValue("3", "3value"); final byte[] binary = serialize(original); @@ -78,6 +90,14 @@ public void testSerialization() throws Exception { assertEquals(original, copy); } + @Test + public void testSerializationOfEmptyMap() throws Exception { + final SortedArrayStringMap original = new SortedArrayStringMap(); + final byte[] binary = serialize(original); + final SortedArrayStringMap copy = deserialize(binary); + assertEquals(original, copy); + } + @Test public void testSerializationOfNonSerializableValue() throws Exception { final SortedArrayStringMap original = new SortedArrayStringMap(); @@ -165,16 +185,16 @@ private byte[] serialize(final SortedArrayStringMap data) throws IOException { return arr.toByteArray(); } + @SuppressWarnings("BanSerializableRead") private SortedArrayStringMap deserialize(final byte[] binary) throws IOException, ClassNotFoundException { final ByteArrayInputStream inArr = new ByteArrayInputStream(binary); try (final ObjectInputStream in = new FilteredObjectInputStream(inArr)) { - final SortedArrayStringMap result = (SortedArrayStringMap) in.readObject(); - return result; + return (SortedArrayStringMap) in.readObject(); } } @Test - public void testPutAll() throws Exception { + public void testPutAll() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -195,7 +215,7 @@ public void testPutAll() throws Exception { } @Test - public void testPutAll_overwritesSameKeys2() throws Exception { + public void testPutAll_overwritesSameKeys2() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aORIG"); original.putValue("b", "bORIG"); @@ -210,7 +230,7 @@ public void testPutAll_overwritesSameKeys2() throws Exception { other.putValue("c", "cc"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cc", original.getValue("c")); @@ -221,7 +241,7 @@ public void testPutAll_overwritesSameKeys2() throws Exception { } @Test - public void testPutAll_nullKeyInLargeOriginal() throws Exception { + public void testPutAll_nullKeyInLargeOriginal() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue(null, "nullORIG"); original.putValue("a", "aORIG"); @@ -235,7 +255,7 @@ public void testPutAll_nullKeyInLargeOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cORIG", original.getValue("c")); @@ -246,7 +266,7 @@ public void testPutAll_nullKeyInLargeOriginal() throws Exception { } @Test - public void testPutAll_nullKeyInSmallOriginal() throws Exception { + public void testPutAll_nullKeyInSmallOriginal() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue(null, "nullORIG"); original.putValue("a", "aORIG"); @@ -259,7 +279,7 @@ public void testPutAll_nullKeyInSmallOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("11", original.getValue("1")); @@ -269,7 +289,7 @@ public void testPutAll_nullKeyInSmallOriginal() throws Exception { } @Test - public void testPutAll_nullKeyInSmallAdditional() throws Exception { + public void testPutAll_nullKeyInSmallAdditional() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aORIG"); original.putValue("b", "bORIG"); @@ -283,7 +303,7 @@ public void testPutAll_nullKeyInSmallAdditional() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cORIG", original.getValue("c")); @@ -294,7 +314,7 @@ public void testPutAll_nullKeyInSmallAdditional() throws Exception { } @Test - public void testPutAll_nullKeyInLargeAdditional() throws Exception { + public void testPutAll_nullKeyInLargeAdditional() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aORIG"); original.putValue("b", "bORIG"); @@ -307,7 +327,7 @@ public void testPutAll_nullKeyInLargeAdditional() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("11", original.getValue("1")); @@ -317,7 +337,7 @@ public void testPutAll_nullKeyInLargeAdditional() throws Exception { } @Test - public void testPutAll_nullKeyInBoth_LargeOriginal() throws Exception { + public void testPutAll_nullKeyInBoth_LargeOriginal() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue(null, "nullORIG"); original.putValue("a", "aORIG"); @@ -332,7 +352,7 @@ public void testPutAll_nullKeyInBoth_LargeOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cORIG", original.getValue("c")); @@ -343,7 +363,7 @@ public void testPutAll_nullKeyInBoth_LargeOriginal() throws Exception { } @Test - public void testPutAll_nullKeyInBoth_SmallOriginal() throws Exception { + public void testPutAll_nullKeyInBoth_SmallOriginal() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue(null, "nullORIG"); original.putValue("a", "aORIG"); @@ -357,7 +377,7 @@ public void testPutAll_nullKeyInBoth_SmallOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("11", original.getValue("1")); @@ -367,7 +387,7 @@ public void testPutAll_nullKeyInBoth_SmallOriginal() throws Exception { } @Test - public void testPutAll_overwritesSameKeys1() throws Exception { + public void testPutAll_overwritesSameKeys1() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aORIG"); original.putValue("b", "bORIG"); @@ -380,7 +400,7 @@ public void testPutAll_overwritesSameKeys1() throws Exception { other.putValue("c", "cc"); original.putAll(other); - assertEquals("size after put other", 5, original.size()); + assertEquals(5, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cc", original.getValue("c")); @@ -391,12 +411,17 @@ public void testPutAll_overwritesSameKeys1() throws Exception { @Test public void testEquals() { final SortedArrayStringMap original = new SortedArrayStringMap(); + final SortedArrayStringMap other = new SortedArrayStringMap(); + + assertEquals(other, original, "Empty maps are equal"); + assertEquals(other.hashCode(), original.hashCode(), "Empty maps have equal hashcode"); + assertNotEquals(original, "Object other than SortedArrayStringMap"); + original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); original.putValue("3", "3value"); assertEquals(original, original); // equal to itself - final SortedArrayStringMap other = new SortedArrayStringMap(); other.putValue("a", "avalue"); assertNotEquals(original, other); @@ -405,19 +430,27 @@ public void testEquals() { other.putValue("3", "3value"); assertEquals(original, other); + assertEquals(original.hashCode(), other.hashCode()); other.putValue("3", "otherValue"); assertNotEquals(original, other); other.putValue("3", null); assertNotEquals(original, other); + assertNotEquals(original.hashCode(), other.hashCode()); other.putValue("3", "3value"); assertEquals(original, other); + assertEquals(other, original); // symmetry + + original.putValue("key not in other", "4value"); + other.putValue("key not in original", "4value"); + assertNotEquals(original, other); + assertNotEquals(other, original); // symmetry } @Test - public void testToMap() throws Exception { + public void testToMap() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -430,11 +463,7 @@ public void testToMap() throws Exception { assertEquals(expected, original.toMap()); - try { - original.toMap().put("abc", "xyz"); - } catch (final UnsupportedOperationException ex) { - fail("Expected map to be mutable, but " + ex); - } + assertDoesNotThrow(() -> original.toMap().put("abc", "xyz"), "Expected map to be mutable"); } @Test @@ -443,11 +472,11 @@ public void testPutAll_KeepsExistingValues() { original.putValue("a", "aaa"); original.putValue("b", "bbb"); original.putValue("c", "ccc"); - assertEquals("size", 3, original.size()); + assertEquals(3, original.size(), "size"); // add empty context data original.putAll(new SortedArrayStringMap()); - assertEquals("size after put empty", 3, original.size()); + assertEquals(3, original.size(), "size after put empty"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -458,7 +487,7 @@ public void testPutAll_KeepsExistingValues() { other.putValue("3", "333"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -474,11 +503,11 @@ public void testPutAll_sizePowerOfTwo() { original.putValue("b", "bbb"); original.putValue("c", "ccc"); original.putValue("d", "ddd"); - assertEquals("size", 4, original.size()); + assertEquals(4, original.size(), "size"); // add empty context data original.putAll(new SortedArrayStringMap()); - assertEquals("size after put empty", 4, original.size()); + assertEquals(4, original.size(), "size after put empty"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -491,7 +520,7 @@ public void testPutAll_sizePowerOfTwo() { other.putValue("4", "444"); original.putAll(other); - assertEquals("size after put other", 8, original.size()); + assertEquals(8, original.size(), "size after put other"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -510,7 +539,7 @@ public void testPutAll_largeAddition() { original.putValue("b", "bbb"); original.putValue("c", "ccc"); original.putValue("d", "ddd"); - assertEquals("size", 5, original.size()); + assertEquals(5, original.size(), "size"); final SortedArrayStringMap other = new SortedArrayStringMap(); for (int i = 0 ; i < 500; i++) { @@ -519,7 +548,7 @@ public void testPutAll_largeAddition() { other.putValue(null, "otherVal"); original.putAll(other); - assertEquals("size after put other", 505, original.size()); + assertEquals(505, original.size(), "size after put other"); assertEquals("otherVal", original.getValue(null)); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); @@ -536,110 +565,70 @@ public void testPutAllSelfDoesNotModify() { original.putValue("a", "aaa"); original.putValue("b", "bbb"); original.putValue("c", "ccc"); - assertEquals("size", 3, original.size()); + assertEquals(3, original.size(), "size"); // putAll with self original.putAll(original); - assertEquals("size after put empty", 3, original.size()); + assertEquals(3, original.size(), "size after put empty"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationBiConsumerPut() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.putValue("c", "other"); - } - }); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o) -> original.putValue("c", "other"))); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationBiConsumerPutValue() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.putValue("c", "other"); - } - }); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o) -> original.putValue("c", "other"))); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationBiConsumerRemove() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.remove("a"); - } - }); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o) -> original.remove("a"))); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationBiConsumerClear() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.clear(); - } - }); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o) -> original.clear())); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationTriConsumerPut() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.putValue("c", "other"); - } - }, null); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o, o2) -> original.putValue("c", "other"), null)); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationTriConsumerPutValue() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.putValue("c", "other"); - } - }, null); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o, o2) -> original.putValue("c", "other"), null)); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationTriConsumerRemove() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.remove("a"); - } - }, null); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o, o2) -> original.remove("a"), null)); } - @Test(expected = ConcurrentModificationException.class) + @Test public void testConcurrentModificationTriConsumerClear() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.clear(); - } - }, null); + assertThrows(ConcurrentModificationException.class, () -> original.forEach((s, o, o2) -> original.clear(), null)); } @Test @@ -650,24 +639,24 @@ public void testInitiallyNotFrozen() { @Test public void testIsFrozenAfterCallingFreeze() { final SortedArrayStringMap original = new SortedArrayStringMap(); - assertFalse("before freeze", original.isFrozen()); + assertFalse(original.isFrozen(), "before freeze"); original.freeze(); - assertTrue("after freeze", original.isFrozen()); + assertTrue(original.isFrozen(), "after freeze"); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testFreezeProhibitsPutValue() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.freeze(); - original.putValue("a", "aaa"); + assertThrows(UnsupportedOperationException.class, () -> original.putValue("a", "aaa")); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testFreezeProhibitsRemove() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("b", "bbb"); original.freeze(); - original.remove("b"); // existing key: modifies the collection + assertThrows(UnsupportedOperationException.class, () -> original.remove("b")); // existing key: modifies the collection } @Test @@ -675,33 +664,33 @@ public void testFreezeAllowsRemoveOfNonExistingKey() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("b", "bbb"); original.freeze(); - original.remove("a"); // no actual modification + assertDoesNotThrow(() -> original.remove("a")); } @Test public void testFreezeAllowsRemoveIfEmpty() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.freeze(); - original.remove("a"); // no exception + assertDoesNotThrow(() -> original.remove("a")); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testFreezeProhibitsClear() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "aaa"); original.freeze(); - original.clear(); + assertThrows(UnsupportedOperationException.class, original::clear); } @Test public void testFreezeAllowsClearIfEmpty() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.freeze(); - original.clear(); + assertDoesNotThrow(original::clear); } @Test - public void testPutInsertsInAlphabeticOrder() throws Exception { + public void testPutInsertsInAlphabeticOrder() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -726,7 +715,7 @@ public void testPutInsertsInAlphabeticOrder() throws Exception { } @Test - public void testPutValueInsertsInAlphabeticOrder() throws Exception { + public void testPutValueInsertsInAlphabeticOrder() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -794,27 +783,27 @@ public void testNullKeysCopiedToAsMap() { expected.put("3", "3value"); expected.put("c", "cvalue"); expected.put("d", "dvalue"); - assertEquals("initial", expected, original.toMap()); + assertEquals(expected, original.toMap(), "initial"); original.putValue(null, "nullvalue"); expected.put(null, "nullvalue"); assertEquals(6, original.size()); - assertEquals("with null key", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key"); original.putValue(null, "otherNullvalue"); expected.put(null, "otherNullvalue"); assertEquals(6, original.size()); - assertEquals("with null key value2", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key value2"); original.putValue(null, "nullvalue"); expected.put(null, "nullvalue"); assertEquals(6, original.size()); - assertEquals("with null key value1 again", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key value1 again"); original.putValue(null, "abc"); expected.put(null, "abc"); assertEquals(6, original.size()); - assertEquals("with null key value3", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key value3"); } @Test @@ -826,11 +815,11 @@ public void testRemove() { original.remove("a"); assertEquals(0, original.size()); - assertNull("no a val", original.getValue("a")); + assertNull(original.getValue("a"), "no a val"); original.remove("B"); assertEquals(0, original.size()); - assertNull("no B val", original.getValue("B")); + assertNull(original.getValue("B"), "no B val"); } @Test @@ -850,13 +839,11 @@ public void testRemoveNullsOutRemovedSlot() throws Exception { final Field f = SortedArrayStringMap.class.getDeclaredField("values"); f.setAccessible(true); final Object[] values = (Object[]) f.get(original); - for (int i = 0; i < values.length; i++) { - assertNull(values[i]); - } + assertAll(Arrays.stream(values).map(value -> () -> assertNull(value))); } @Test - public void testRemoveWhenFull() throws Exception { + public void testRemoveWhenFull() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("b", "bvalue"); @@ -874,15 +861,15 @@ public void testNullValuesArePreserved() { original.putValue("a", null); assertEquals(1, original.size()); - assertNull("no a val", original.getValue("a")); + assertNull(original.getValue("a"), "no a val"); original.putValue("B", null); assertEquals(2, original.size()); - assertNull("no B val", original.getValue("B")); + assertNull(original.getValue("B"), "no B val"); } @Test - public void testGet() throws Exception { + public void testGet() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -900,7 +887,7 @@ public void testGet() throws Exception { } @Test - public void testGetValue_GetValueAt() throws Exception { + public void testGetValue_GetValueAt() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -941,13 +928,11 @@ public void testClear() throws Exception { final Field f = SortedArrayStringMap.class.getDeclaredField("values"); f.setAccessible(true); final Object[] values = (Object[]) f.get(original); - for (int i = 0; i < values.length; i++) { - assertNull(values[i]); - } + assertAll(Arrays.stream(values).map(value -> () -> assertNull(value))); } @Test - public void testIndexOfKey() throws Exception { + public void testIndexOfKey() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); assertEquals(0, original.indexOfKey("a")); @@ -984,40 +969,40 @@ public void testIndexOfKey() throws Exception { } @Test - public void testContainsKey() throws Exception { + public void testContainsKey() { final SortedArrayStringMap original = new SortedArrayStringMap(); - assertFalse("a", original.containsKey("a")); - assertFalse("B", original.containsKey("B")); - assertFalse("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertFalse(original.containsKey("a"), "a"); + assertFalse(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("a", "avalue"); - assertTrue("a", original.containsKey("a")); - assertFalse("B", original.containsKey("B")); - assertFalse("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertFalse(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("B", "Bvalue"); - assertTrue("a", original.containsKey("a")); - assertTrue("B", original.containsKey("B")); - assertFalse("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("3", "3value"); - assertTrue("a", original.containsKey("a")); - assertTrue("B", original.containsKey("B")); - assertTrue("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("A", "AAA"); - assertTrue("a", original.containsKey("a")); - assertTrue("B", original.containsKey("B")); - assertTrue("3", original.containsKey("3")); - assertTrue("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertTrue(original.containsKey("A"), "A"); } @Test - public void testGetValueAt() throws Exception { + public void testGetValueAt() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); assertEquals("a", original.getKeyAt(0)); @@ -1039,38 +1024,38 @@ public void testGetValueAt() throws Exception { } @Test - public void testSizeAndIsEmpty() throws Exception { + public void testSizeAndIsEmpty() { final SortedArrayStringMap original = new SortedArrayStringMap(); assertEquals(0, original.size()); - assertTrue("initial", original.isEmpty()); + assertTrue(original.isEmpty(), "initial"); original.putValue("a", "avalue"); assertEquals(1, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.putValue("B", "Bvalue"); assertEquals(2, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.putValue("3", "3value"); assertEquals(3, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.remove("B"); assertEquals(2, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.remove("3"); assertEquals(1, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.remove("a"); assertEquals(0, original.size()); - assertTrue("size=" + original.size(), original.isEmpty()); + assertTrue(original.isEmpty(), "size=" + original.size()); } @Test - public void testForEachBiConsumer() throws Exception { + public void testForEachBiConsumer() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -1080,10 +1065,10 @@ public void testForEachBiConsumer() throws Exception { int count = 0; @Override public void accept(final String key, final String value) { - assertEquals("key", key, original.getKeyAt(count)); - assertEquals("val", value, original.getValueAt(count)); + assertEquals(key, original.getKeyAt(count), "key"); + assertEquals(value, original.getValueAt(count), "val"); count++; - assertTrue("count should not exceed size but was " + count, count <= original.size()); + assertTrue(count <= original.size(), "count should not exceed size but was " + count); } }); } @@ -1092,19 +1077,16 @@ static class State { SortedArrayStringMap data; int count; } - static TriConsumer COUNTER = new TriConsumer() { - @Override - public void accept(final String key, final String value, final State state) { - assertEquals("key", key, state.data.getKeyAt(state.count)); - assertEquals("val", value, state.data.getValueAt(state.count)); - state.count++; - assertTrue("count should not exceed size but was " + state.count, - state.count <= state.data.size()); - } + static TriConsumer COUNTER = (key, value, state) -> { + assertEquals(key, state.data.getKeyAt(state.count), "key"); + assertEquals(value, state.data.getValueAt(state.count), "val"); + state.count++; + assertTrue( + state.count <= state.data.size(), "count should not exceed size but was " + state.count); }; @Test - public void testForEachTriConsumer() throws Exception { + public void testForEachTriConsumer() { final SortedArrayStringMap original = new SortedArrayStringMap(); original.putValue("a", "avalue"); original.putValue("B", "Bvalue"); @@ -1115,4 +1097,4 @@ public void testForEachTriConsumer() throws Exception { original.forEach(COUNTER, state); assertEquals(state.count, original.size()); } -} \ No newline at end of file +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StackLocatorUtilTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StackLocatorUtilTest.java new file mode 100644 index 00000000000..8fe11c3f8e9 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StackLocatorUtilTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; +import org.junit.jupiter.engine.execution.InvocationInterceptorChain; + +import static org.junit.jupiter.api.Assertions.*; + +public class StackLocatorUtilTest { + + @Test + public void testStackTraceEquivalence() throws Throwable { + StackTraceElement[] stackTraceElements = expectedStack(new Throwable().getStackTrace()); + for (int i = 1; i < 10; i++) { + final String expected = stackTraceElements[i-1].getClassName(); + final String actual = StackLocatorUtil.getCallerClass(i).getName(); + final String fallbackActual = Class.forName( + StackLocatorUtil.getStackTraceElement(i).getClassName()).getName(); + assertSame(expected, actual); + assertSame(expected, fallbackActual); + } + } + + private StackTraceElement[] expectedStack(StackTraceElement[] elements) { + StackTraceElement[] elementArray = new StackTraceElement[10]; + int i = 0; + for (int index = 0; index < 10;) { + if (elements[i].getClassName().startsWith("org.")) { + elementArray[index] = elements[i]; + ++index; + } + ++i; + } + return elementArray; + } + + @Test + public void testGetCallerClass() throws Exception { + final Class expected = StackLocatorUtilTest.class; + final Class actual = StackLocatorUtil.getCallerClass(1); + assertSame(expected, actual); + } + + @Test + public void testGetCallerClassLoader() throws Exception { + assertSame(StackLocatorUtilTest.class.getClassLoader(), StackLocatorUtil.getCallerClassLoader(1)); + } + + @Test + public void testGetCallerClassNameViaStackTrace() throws Exception { + final Class expected = StackLocatorUtilTest.class; + final Class actual = Class.forName(new Throwable().getStackTrace()[0].getClassName()); + assertSame(expected, actual); + } + + @Test + public void testGetCurrentStackTrace() throws Exception { + final Deque> classes = StackLocatorUtil.getCurrentStackTrace(); + final Deque> reversed = new ArrayDeque<>(classes.size()); + while (!classes.isEmpty()) { + reversed.push(classes.pop()); + } + while (reversed.peek() != StackLocatorUtil.class) { + reversed.pop(); + } + reversed.pop(); // ReflectionUtil + assertSame(StackLocatorUtilTest.class, reversed.pop()); + } + + @Test + public void testGetCallerClassViaName() throws Exception { + final Class expected = InterceptingExecutableInvoker.class; + final Class actual = StackLocatorUtil.getCallerClass("org.junit.jupiter.engine.execution.InvocationInterceptorChain"); + // if this test fails in the future, it's probably because of a JUnit upgrade; check the new stack trace and + // update this test accordingly + assertSame(expected, actual); + } + + @Test + public void testGetCallerClassViaAnchorClass() throws Exception { + final Class expected = InterceptingExecutableInvoker.class; + final Class actual = StackLocatorUtil.getCallerClass(InvocationInterceptorChain.class); + // if this test fails in the future, it's probably because of a JUnit upgrade; check the new stack trace and + // update this test accordingly + assertSame(expected, actual); + } + + @Test + public void testLocateClass() { + final ClassLocator locator = new ClassLocator(); + final Class clazz = locator.locateClass(); + assertNotNull(clazz, "Could not locate class"); + assertEquals(this.getClass(), clazz, "Incorrect class"); + } + +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringBuildersTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringBuildersTest.java new file mode 100644 index 00000000000..eb759f25eb1 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringBuildersTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests the StringBuilders class. + */ +public class StringBuildersTest { + @Test + public void trimToMaxSize() { + final StringBuilder sb = new StringBuilder(); + final char[] value = new char[4 * 1024]; + sb.append(value); + + assertTrue(sb.length() > Constants.MAX_REUSABLE_MESSAGE_SIZE, "needs trimming"); + StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE); + assertTrue(sb.length() <= Constants.MAX_REUSABLE_MESSAGE_SIZE, "trimmed OK"); + } + + @Test + public void trimToMaxSizeWithLargeCapacity() { + final StringBuilder sb = new StringBuilder(); + final char[] value = new char[4 * 1024]; + sb.append(value); + sb.setLength(0); + + assertTrue(sb.capacity() > Constants.MAX_REUSABLE_MESSAGE_SIZE, "needs trimming"); + StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE); + assertTrue(sb.capacity() <= Constants.MAX_REUSABLE_MESSAGE_SIZE, "trimmed OK"); + } + + @Test + public void escapeJsonCharactersCorrectly() { + String jsonValueNotEscaped = "{\"field\n1\":\"value_1\"}"; + String jsonValueEscaped = "{\\\"field\\n1\\\":\\\"value_1\\\"}"; + + StringBuilder sb = new StringBuilder(); + sb.append(jsonValueNotEscaped); + assertEquals(jsonValueNotEscaped, sb.toString()); + StringBuilders.escapeJson(sb, 0); + assertEquals(jsonValueEscaped, sb.toString()); + + sb = new StringBuilder(); + String jsonValuePartiallyEscaped = "{\"field\n1\":\\\"value_1\\\"}"; + sb.append(jsonValueNotEscaped); + assertEquals(jsonValueNotEscaped, sb.toString()); + StringBuilders.escapeJson(sb, 10); + assertEquals(jsonValuePartiallyEscaped, sb.toString()); + } + + @Test + public void escapeJsonCharactersISOControl() { + String jsonValueNotEscaped = "{\"field\n1\":\"value" + (char) 0x8F + "_1\"}"; + String jsonValueEscaped = "{\\\"field\\n1\\\":\\\"value\\u008F_1\\\"}"; + + StringBuilder sb = new StringBuilder(); + sb.append(jsonValueNotEscaped); + assertEquals(jsonValueNotEscaped, sb.toString()); + StringBuilders.escapeJson(sb, 0); + assertEquals(jsonValueEscaped, sb.toString()); + } + + @Test + public void escapeXMLCharactersCorrectly() { + String xmlValueNotEscaped = "<\"Salt&Peppa'\">"; + String xmlValueEscaped = "<"Salt&Peppa'">"; + + StringBuilder sb = new StringBuilder(); + sb.append(xmlValueNotEscaped); + assertEquals(xmlValueNotEscaped, sb.toString()); + StringBuilders.escapeXml(sb, 0); + assertEquals(xmlValueEscaped, sb.toString()); + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java new file mode 100644 index 00000000000..7c9695d3a81 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/StringsTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.util; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; + +import static org.junit.jupiter.api.Assertions.*; + +public class StringsTest { + + @Test + public void testIsEmpty() { + assertTrue(Strings.isEmpty(null)); + assertTrue(Strings.isEmpty("")); + assertFalse(Strings.isEmpty(" ")); + assertFalse(Strings.isEmpty("a")); + } + + @Test + public void testIsBlank() { + assertTrue(Strings.isBlank(null)); + assertTrue(Strings.isBlank("")); + assertTrue(Strings.isBlank(" ")); + assertTrue(Strings.isBlank("\n")); + assertTrue(Strings.isBlank("\r")); + assertTrue(Strings.isBlank("\t")); + assertFalse(Strings.isEmpty("a")); + } + + /** + * A sanity test to make sure a typo does not mess up {@link Strings#EMPTY}. + */ + @Test + public void testEMPTY() { + assertEquals("", Strings.EMPTY); + assertEquals(0, Strings.EMPTY.length()); + } + + @Test + public void testConcat() { + assertEquals("ab", Strings.concat("a", "b")); + assertEquals("a", Strings.concat("a", "")); + assertEquals("a", Strings.concat("a", null)); + assertEquals("b", Strings.concat("", "b")); + assertEquals("b", Strings.concat(null, "b")); + } + + @Test + public void testJoin() { + assertNull(Strings.join((Iterable) null, '.')); + assertNull(Strings.join((Iterator) null, '.')); + assertEquals("", Strings.join((Collections.emptyList()), '.')); + + assertEquals("a", Strings.join(Collections.singletonList("a"), '.')); + assertEquals("a.b", Strings.join(Arrays.asList("a", "b"), '.')); + assertEquals("a.b.c", Strings.join(Arrays.asList("a", "b", "c"), '.')); + + assertEquals("", Strings.join(Collections.singletonList((String) null), ':')); + assertEquals(":", Strings.join(Arrays.asList(null, null), ':')); + assertEquals("a:", Strings.join(Arrays.asList("a", null), ':')); + assertEquals(":b", Strings.join(Arrays.asList(null, "b"), ':')); + } + + @Test + public void splitList() { + String[] list = Strings.splitList("1, 2, 3"); + assertEquals(3, list.length); + list = Strings.splitList(""); + assertEquals(1, list.length); + list = Strings.splitList(null); + assertEquals(0, list.length); + } + + @Test + public void testQuote() { + assertEquals("'Q'", Strings.quote("Q")); + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.java similarity index 100% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesMain.java diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesPropertySourceSecurityManagerIT.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesPropertySourceSecurityManagerIT.java new file mode 100644 index 00000000000..439a3788b40 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesPropertySourceSecurityManagerIT.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.security.Permission; +import java.util.PropertyPermission; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.parallel.ResourceLock; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test related to LOG4J2-2274. + *

+ * Using a security manager can mess up other tests so this is best used from + * integration tests (classes that end in "IT" instead of "Test" and + * "TestCase".) + *

+ * + * @see SystemPropertiesPropertySource + * @see SecurityManager + * @see System#setSecurityManager(SecurityManager) + * @see PropertyPermission + */ +@ResourceLock("java.lang.SecurityManager") +@DisabledForJreRange(min = JRE.JAVA_18) // custom SecurityManager instances throw UnsupportedOperationException +public class SystemPropertiesPropertySourceSecurityManagerIT { + + /** + * Always throws a SecurityException for any environment variables permission + * check. + */ + private static class TestSecurityManager extends SecurityManager { + @Override + public void checkPermission(final Permission permission) { + if (permission instanceof PropertyPermission) { + throw new SecurityException(); + } + } + } + + /** + * Makes sure we do not blow up with exception below due to a security manager + * rejecting environment variable access in + * {@link SystemPropertiesPropertySource}. + * + *
+     * java.lang.ExceptionInInitializerError
+     * 	at org.apache.logging.log4j.util.SystemPropertiesPropertySourceSecurityManagerTest.test(SystemPropertiesPropertySourceSecurityManagerTest.java:64)
+     * 	...
+     * Caused by: java.lang.SecurityException
+     * 	at org.apache.logging.log4j.util.SystemPropertiesPropertySourceSecurityManagerTest$TestSecurityManager.checkPermission(SystemPropertiesPropertySourceSecurityManagerTest.java:49)
+     * 	at java.lang.SecurityManager.checkPropertiesAccess(SecurityManager.java:1265)
+     * 	at java.lang.System.getProperties(System.java:624)
+     * 	at org.apache.logging.log4j.util.SystemPropertiesPropertySource.forEach(SystemPropertiesPropertySource.java:40)
+     * 	at org.apache.logging.log4j.util.PropertiesUtil$Environment.reload(PropertiesUtil.java:330)
+     * 	at org.apache.logging.log4j.util.PropertiesUtil$Environment.(PropertiesUtil.java:322)
+     * 	at org.apache.logging.log4j.util.PropertiesUtil$Environment.(PropertiesUtil.java:310)
+     * 	at org.apache.logging.log4j.util.PropertiesUtil.(PropertiesUtil.java:69)
+     * 	at org.apache.logging.log4j.util.PropertiesUtil.(PropertiesUtil.java:49)
+     * 	... 26 more
+     * 
+ */ + @Test + public void test() { + var existing = System.getSecurityManager(); + try { + System.setSecurityManager(new TestSecurityManager()); + final PropertiesUtil propertiesUtil = new PropertiesUtil("src/test/resources/PropertiesUtilTest.properties"); + assertThat(propertiesUtil.getStringProperty("a.1")).isNull(); + } finally { + System.setSecurityManager(existing); + } + } +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesPropertySourceTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesPropertySourceTest.java new file mode 100644 index 00000000000..3b568d8726f --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/SystemPropertiesPropertySourceTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.util.Properties; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.WritesSystemProperty; + +/** + * Tests LOG4J2-2276. + */ +@Tag("concurrency") +@WritesSystemProperty +public class SystemPropertiesPropertySourceTest { + + private static final int ITERATIONS = 10000; + + /** + * Tests avoiding a ConcurrentModificationException. For example: + * + *
+     * java.util.ConcurrentModificationException
+     *  at java.util.Hashtable$Enumerator.next(Hashtable.java:1167)
+     *  at org.apache.logging.log4j.util.SystemPropertiesPropertySource.forEach(SystemPropertiesPropertySource.java:38)
+     *  at org.apache.logging.log4j.util.SystemPropertiesPropertySourceTest.testMultiThreadedAccess(SystemPropertiesPropertySourceTest.java:47)
+     * 
+ * @throws InterruptedException + * @throws ExecutionException + */ + @Test + public void testMultiThreadedAccess() throws InterruptedException, ExecutionException { + ExecutorService threadPool = Executors.newSingleThreadExecutor(); + try { + Future future = threadPool.submit(() -> { + final Properties properties = System.getProperties(); + for (int i = 0; i < ITERATIONS; i++) { + properties.setProperty("FOO_" + i, "BAR"); + } + }); + for (int i = 0; i < ITERATIONS; i++) + new SystemPropertiesPropertySource().forEach((key, value) -> { + // nothing + }); + future.get(); + } finally { + threadPool.shutdown(); + } + } + +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java similarity index 79% rename from log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java rename to log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java index 2e8cb774054..fa336d19417 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox1Test.java @@ -14,30 +14,33 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.util; -import org.junit.BeforeClass; -import org.junit.Test; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the Unbox class. */ +@ResourceLock(Resources.SYSTEM_PROPERTIES) public class Unbox1Test { - @BeforeClass + @BeforeAll public static void beforeClass() { - System.clearProperty("log4j.unbox.ringbuffer.size"); + System.clearProperty(LoggingSystemProperties.UNBOX_RING_BUFFER_SIZE); } @Test - public void testBoxClaimsItHas32Slots() throws Exception { + public void testBoxClaimsItHas32Slots() { assertEquals(32, Unbox.getRingbufferSize()); } @Test - public void testBoxHas32Slots() throws Exception { + public void testBoxHas32Slots() { final int MAX = 32; final StringBuilder[] probe = new StringBuilder[MAX * 3]; for (int i = 0; i <= probe.length - 8; ) { @@ -51,21 +54,21 @@ public void testBoxHas32Slots() throws Exception { probe[i++] = Unbox.box(Short.MAX_VALUE); } for (int i = 0; i < probe.length - MAX; i++) { - assertSame("probe[" + i +"], probe[" + (i + MAX) +"]", probe[i], probe[i + MAX]); + assertSame(probe[i], probe[i + MAX], "probe[" + i +"], probe[" + (i + MAX) +"]"); for (int j = 1; j < MAX - 1; j++) { - assertNotSame("probe[" + i +"], probe[" + (i + j) +"]", probe[i], probe[i + j]); + assertNotSame(probe[i], probe[i + j], "probe[" + i +"], probe[" + (i + j) +"]"); } } } @Test - public void testBoxBoolean() throws Exception { + public void testBoxBoolean() { assertEquals("true", Unbox.box(true).toString()); assertEquals("false", Unbox.box(false).toString()); } @Test - public void testBoxByte() throws Exception { + public void testBoxByte() { assertEquals("0", Unbox.box((byte) 0).toString()); assertEquals("1", Unbox.box((byte) 1).toString()); assertEquals("127", Unbox.box((byte) 127).toString()); @@ -74,28 +77,28 @@ public void testBoxByte() throws Exception { } @Test - public void testBoxChar() throws Exception { + public void testBoxChar() { assertEquals("a", Unbox.box('a').toString()); assertEquals("b", Unbox.box('b').toString()); assertEquals("字", Unbox.box('字').toString()); } @Test - public void testBoxDouble() throws Exception { + public void testBoxDouble() { assertEquals("3.14", Unbox.box(3.14).toString()); assertEquals(new Double(Double.MAX_VALUE).toString(), Unbox.box(Double.MAX_VALUE).toString()); assertEquals(new Double(Double.MIN_VALUE).toString(), Unbox.box(Double.MIN_VALUE).toString()); } @Test - public void testBoxFloat() throws Exception { + public void testBoxFloat() { assertEquals("3.14", Unbox.box(3.14F).toString()); assertEquals(new Float(Float.MAX_VALUE).toString(), Unbox.box(Float.MAX_VALUE).toString()); assertEquals(new Float(Float.MIN_VALUE).toString(), Unbox.box(Float.MIN_VALUE).toString()); } @Test - public void testBoxInt() throws Exception { + public void testBoxInt() { assertEquals("0", Unbox.box(0).toString()); assertEquals("1", Unbox.box(1).toString()); assertEquals("127", Unbox.box(127).toString()); @@ -106,7 +109,7 @@ public void testBoxInt() throws Exception { } @Test - public void testBoxLong() throws Exception { + public void testBoxLong() { assertEquals("0", Unbox.box(0L).toString()); assertEquals("1", Unbox.box(1L).toString()); assertEquals("127", Unbox.box(127L).toString()); @@ -117,7 +120,7 @@ public void testBoxLong() throws Exception { } @Test - public void testBoxShort() throws Exception { + public void testBoxShort() { assertEquals("0", Unbox.box((short) 0).toString()); assertEquals("1", Unbox.box((short) 1).toString()); assertEquals("127", Unbox.box((short) 127).toString()); @@ -131,26 +134,16 @@ public void testBoxShort() throws Exception { public void testBoxIsThreadLocal() throws Exception { final StringBuilder[] probe = new StringBuilder[16 * 3]; populate(0, probe); - final Thread t1 = new Thread() { - @Override - public void run() { - populate(16, probe); - } - }; + final Thread t1 = new Thread(() -> populate(16, probe)); t1.start(); t1.join(); - final Thread t2 = new Thread() { - @Override - public void run() { - populate(16, probe); - } - }; + final Thread t2 = new Thread(() -> populate(16, probe)); t2.start(); t2.join(); for (int i = 0; i < probe.length - 16; i++) { for (int j = 1; j < 16; j++) { - assertNotSame("probe[" + i +"]=" + probe[i] + ", probe[" + (i + j) +"]=" + probe[i + j], - probe[i], probe[i + j]); + assertNotSame( + probe[i], probe[i + j], "probe[" + i +"]=" + probe[i] + ", probe[" + (i + j) +"]=" + probe[i + j]); } } } @@ -167,4 +160,4 @@ private void populate(final int start, final StringBuilder[] probe) { probe[i++] = Unbox.box(Short.MAX_VALUE); } } -} \ No newline at end of file +} diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java new file mode 100644 index 00000000000..a6f8a12c691 --- /dev/null +++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.parallel.Isolated; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests that the Unbox ring buffer size is configurable. + * Must be run in a separate process as the other UnboxTest or the last-run test will fail. + * Run this test on its own via {@code mvn --projects log4j-api test -Dtest=Unbox2ConfigurableTest} which will automatically + * enable the test, too. + */ +@EnabledIfSystemProperty(named = "test", matches = ".*Unbox2ConfigurableTest.*") +@Isolated +@SetSystemProperty(key = "log4j.unbox.ringbuffer.size", value = "65") +public class Unbox2ConfigurableTest { + @AfterAll + public static void afterClass() throws Exception { + // ensure subsequent tests (which assume 32 slots) pass + // make non-private + final Field field = FieldUtils.getDeclaredField(Unbox.class, "RINGBUFFER_SIZE", true); + // make non-final + FieldUtils.writeDeclaredField(field, "modifiers", field.getModifiers() &~ Modifier.FINAL); + + field.set(null, 32); // reset to default + + final Field threadLocalField = FieldUtils.getDeclaredField(Unbox.class, "threadLocalState", true); + final ThreadLocal threadLocal = (ThreadLocal) threadLocalField.get(null); + threadLocal.remove(); + threadLocalField.set(null, new ThreadLocal<>()); + } + + @Test + public void testBoxConfiguredTo128Slots() { + // next power of 2 that is 65 or more + assertEquals(128, Unbox.getRingbufferSize()); + } + + @Test + public void testBoxSuccessfullyConfiguredTo128Slots() { + final int MAX = 128; + final StringBuilder[] probe = new StringBuilder[MAX * 3]; + for (int i = 0; i <= probe.length - 8; ) { + probe[i++] = Unbox.box(true); + probe[i++] = Unbox.box('c'); + probe[i++] = Unbox.box(Byte.MAX_VALUE); + probe[i++] = Unbox.box(Double.MAX_VALUE); + probe[i++] = Unbox.box(Float.MAX_VALUE); + probe[i++] = Unbox.box(Integer.MAX_VALUE); + probe[i++] = Unbox.box(Long.MAX_VALUE); + probe[i++] = Unbox.box(Short.MAX_VALUE); + } + for (int i = 0; i < probe.length - MAX; i++) { + assertSame(probe[i], probe[i + MAX], "probe[" + i +"], probe[" + (i + MAX) +"]"); + for (int j = 1; j < MAX - 1; j++) { + assertNotSame(probe[i], probe[i + j], "probe[" + i +"], probe[" + (i + j) +"]"); + } + } + } +} diff --git a/log4j-api-test/src/test/resources/Jira3413Test.properties b/log4j-api-test/src/test/resources/Jira3413Test.properties new file mode 100644 index 00000000000..1d3bdff366c --- /dev/null +++ b/log4j-api-test/src/test/resources/Jira3413Test.properties @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +# + +# These should not be resolved +level=fail +another.property=fail +catalina.base=fail + +# Some legacy properties with a characteristic prefix +log4j.configurationFile=ok +Log4jDefaultStatusLevel=ok +org.apache.logging.log4j.newLevel=ok + +# No characteristic prefix +AsyncLogger.Timeout=ok +AsyncLoggerConfig.RingBufferSize=ok +disableThreadContext=ok +disableThreadContextStack=ok +disableThreadContextMap=ok +isThreadContextMapInheritable=ok \ No newline at end of file diff --git a/log4j-api/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider similarity index 100% rename from log4j-api/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider rename to log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.BetterService b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.BetterService new file mode 100644 index 00000000000..69b68983a6c --- /dev/null +++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.BetterService @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +org.apache.logging.log4j.util.test.Service2 diff --git a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.Service b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.Service new file mode 100644 index 00000000000..5fde1d53513 --- /dev/null +++ b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.util.test.Service @@ -0,0 +1,26 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +# A correct entry +org.apache.logging.log4j.util.test.Service1 + +# Simulates a service retrieved from the server's classloader in a servlet environment. +java.lang.String + +# Simulates a broken service. +invalid.Service + +# Another correct service +org.apache.logging.log4j.util.test.Service2 diff --git a/log4j-api/src/test/resources/MF_en_US.properties b/log4j-api-test/src/test/resources/MF_en_US.properties similarity index 100% rename from log4j-api/src/test/resources/MF_en_US.properties rename to log4j-api-test/src/test/resources/MF_en_US.properties diff --git a/log4j-api/src/test/resources/MF_fr.properties b/log4j-api-test/src/test/resources/MF_fr.properties similarity index 100% rename from log4j-api/src/test/resources/MF_fr.properties rename to log4j-api-test/src/test/resources/MF_fr.properties diff --git a/log4j-api/src/test/resources/MF_fr_CH.properties b/log4j-api-test/src/test/resources/MF_fr_CH.properties similarity index 100% rename from log4j-api/src/test/resources/MF_fr_CH.properties rename to log4j-api-test/src/test/resources/MF_fr_CH.properties diff --git a/log4j-api-test/src/test/resources/PropertiesUtilOrderTest.properties b/log4j-api-test/src/test/resources/PropertiesUtilOrderTest.properties new file mode 100644 index 00000000000..64de072612a --- /dev/null +++ b/log4j-api-test/src/test/resources/PropertiesUtilOrderTest.properties @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +# + +### +# Legacy properties +log4j.legacyProperty=props.legacy +org.apache.logging.log4j.legacyProperty2=props.legacy +Log4jLegacyProperty3=props.legacy +log4j.nonOverriddenLegacy=props.legacy +log4j.onlyLegacy=props.legacy +log4j.onlyLegacyProps=props.legacy + +### +# Their equivalent normalized versions +log4j2.legacyProperty=props.normalized +log4j2.legacyProperty2=props.normalized +log4j2.legacyProperty3=props.normalized + +### +# Token-based matching +LOG4J_token.based.property1=props.token +LOG4J_token-based-property2=props.token +LOG4J_token/based/property3=props.token +LOG4J_tokenBasedProperty4=props.token + +## +LOG4J_normalized.property=props.token +log4j2.normalizedProperty=props.normalized +log4j2.normalizedPropertyProps=props.normalized diff --git a/log4j-api/src/test/resources/PropertiesUtilTest.properties b/log4j-api-test/src/test/resources/PropertiesUtilTest.properties similarity index 91% rename from log4j-api/src/test/resources/PropertiesUtilTest.properties rename to log4j-api-test/src/test/resources/PropertiesUtilTest.properties index 46e67d58be5..0107458e218 100644 --- a/log4j-api/src/test/resources/PropertiesUtilTest.properties +++ b/log4j-api-test/src/test/resources/PropertiesUtilTest.properties @@ -27,3 +27,6 @@ c.1.3 = 3 dd.1 = 1 dd.2 = 2 dd.3 = 3 + +# dotless entry should be ignored by partitionOnCommonPrefixes() +a = invalid diff --git a/log4j-api/src/test/resources/SF_en_US.properties b/log4j-api-test/src/test/resources/SF_en_US.properties similarity index 100% rename from log4j-api/src/test/resources/SF_en_US.properties rename to log4j-api-test/src/test/resources/SF_en_US.properties diff --git a/log4j-api/src/test/resources/SF_fr.properties b/log4j-api-test/src/test/resources/SF_fr.properties similarity index 100% rename from log4j-api/src/test/resources/SF_fr.properties rename to log4j-api-test/src/test/resources/SF_fr.properties diff --git a/log4j-api/src/test/resources/SF_fr_CH.properties b/log4j-api-test/src/test/resources/SF_fr_CH.properties similarity index 100% rename from log4j-api/src/test/resources/SF_fr_CH.properties rename to log4j-api-test/src/test/resources/SF_fr_CH.properties diff --git a/log4j-api-test/src/test/resources/log4j2.system.properties b/log4j-api-test/src/test/resources/log4j2.system.properties new file mode 100644 index 00000000000..6e4f8aed1f7 --- /dev/null +++ b/log4j-api-test/src/test/resources/log4j2.system.properties @@ -0,0 +1 @@ +Application=Log4j \ No newline at end of file diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml index 2ddbde86382..714dea6c2b0 100644 --- a/log4j-api/pom.xml +++ b/log4j-api/pom.xml @@ -20,8 +20,7 @@ org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + 3.0.0-SNAPSHOT log4j-api jar @@ -31,108 +30,36 @@ ${basedir}/.. API Documentation /api + true - org.apache.logging.log4j - log4j-api-java9 + org.osgi + org.osgi.framework provided - zip - - - - org.apache.felix - org.apache.felix.framework - test org.osgi - org.osgi.core + org.osgi.resource provided - - junit - junit - test - - - org.eclipse.tycho - org.eclipse.osgi - test - - - org.apache.maven - maven-core - test - - - org.apache.commons - commons-lang3 - test - - - - com.fasterxml.jackson.core - jackson-core - test - - - - com.fasterxml.jackson.core - jackson-databind - test - - org.apache.maven.plugins - maven-dependency-plugin - 3.0.2 - - - unpack-classes - prepare-package - - unpack - - - - - org.apache.logging.log4j - log4j-api-java9 - ${project.version} - zip - false - - - **/*.class - **/*.java - ${project.build.directory} - false - true - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.7 - - - add-source - generate-sources - - add-source - - - - ${project.build.directory}/log4j-api-java9 - - - - + org.apache.felix + maven-bundle-plugin + + + org.apache.logging.log4j.* + + sun.reflect;resolution:=optional, + * + + org.apache.logging.log4j.util.Activator + <_fixupmessages>"Classes found in the wrong directory";is:=warning + + org.apache.maven.plugins @@ -140,21 +67,21 @@ default-compile - - 1.7 - 1.7 + + + com.google.errorprone + error_prone_core + ${errorprone.version} + + org.apache.maven.plugins - maven-surefire-plugin - - 2C - true - + maven-deploy-plugin org.apache.maven.plugins @@ -165,7 +92,7 @@ jar - + ${manifestfile} @@ -178,86 +105,19 @@ org.apache ${maven.compiler.source} ${maven.compiler.target} - true - - default - - test-jar - - - - ${manifestfile} - - ${project.name} - ${project.version} - ${project.organization.name} - ${project.name} - ${project.version} - ${project.organization.name} - org.apache - ${maven.compiler.source} - ${maven.compiler.target} - - - - - - - - - org.apache.maven.plugins - maven-remote-resources-plugin - - - - process - - - false - - - - org.apache.felix - maven-bundle-plugin - - - org.apache.logging.log4j.* - - sun.reflect;resolution:=optional, - org.apache.logging.log4j.core.osgi;resolution:=optional, - org.apache.logging.log4j.core.util;resolution:=optional, - org.apache.logging.log4j.core.async;resolution:=optional, - * - - org.apache.logging.log4j.util.Activator - <_fixupmessages>"Classes found in the wrong directory";is:=warning - - - - - org.apache.maven.plugins - maven-deploy-plugin - ${deploy.plugin.version} - - - org.codehaus.mojo - clirr-maven-plugin - ${clirr.plugin.version} - org.apache.maven.plugins maven-changes-plugin - ${changes.plugin.version} @@ -266,14 +126,14 @@ - %URL%/show_bug.cgi?id=%ISSUE% + %URL%/%ISSUE% true + API org.apache.maven.plugins maven-checkstyle-plugin - ${checkstyle.plugin.version} ${log4jParentDir}/checkstyle.xml @@ -286,18 +146,20 @@ org.apache.maven.plugins maven-javadoc-plugin - ${javadoc.plugin.version} - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
+ <p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, - and the Apache Log4j logo are trademarks of The Apache Software Foundation.

]]>
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>
+ false false + -Xdoclint:none true http://www.osgi.org/javadoc/r4v43/core/ + 8
@@ -308,22 +170,9 @@
- - org.codehaus.mojo - findbugs-maven-plugin - ${findbugs.plugin.version} - - true - -Duser.language=en - Normal - Default - ${log4jParentDir}/findbugs-exclude-filter.xml - - org.apache.maven.plugins maven-jxr-plugin - ${jxr.plugin.version} non-aggregate @@ -342,12 +191,14 @@ org.apache.maven.plugins maven-pmd-plugin - ${pmd.plugin.version} ${maven.compiler.target} + + com.github.spotbugs + spotbugs-maven-plugin +
- diff --git a/log4j-api/revapi.json b/log4j-api/revapi.json new file mode 100644 index 00000000000..06bc470f7b1 --- /dev/null +++ b/log4j-api/revapi.json @@ -0,0 +1,116 @@ +[ + { + "extension": "revapi.java", + "configuration": { + "filter": { + "classes": { + "exclude": [ + "org\\.apache\\.logging\\.log4j\\.util\\.Activator", + "org\\.apache\\.logging\\.log4j\\.util\\.LoaderUtil", + "org\\.apache\\.logging\\.log4j\\.util\\.PrivateSecurityManagerStackTraceUtil", + "org\\.apache\\.logging\\.log4j\\.util\\.PropertiesUtil", + "org\\.apache\\.logging\\.log4j\\.util\\.ProviderUtil", + "org\\.apache\\.logging\\.log4j\\.util\\.StackLocator", + "org\\.apache\\.logging\\.log4j\\.util\\.StackLocatorUtil" + ] + } + } + } + }, + { + "extension": "revapi.ignore", + "configuration": [ + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.Logger::entry()", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.Logger::entry(java.lang.Object[])", + "justification": "Removed deprectated method" + }, + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.Logger::exit()", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method R org.apache.logging.log4j.Logger::exit(R)", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method org.apache.logging.log4j.Marker org.apache.logging.log4j.MarkerManager::getMarker(java.lang.String, java.lang.String)", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method org.apache.logging.log4j.Marker org.apache.logging.log4j.MarkerManager::getMarker(java.lang.String, org.apache.logging.log4j.Marker)", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.message.ParameterizedMessage::(java.lang.String, java.lang.String[], java.lang.Throwable)", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method org.apache.logging.log4j.message.EntryMessage org.apache.logging.log4j.spi.AbstractLogger::enter(java.lang.String, java.lang.String, org.apache.logging.log4j.util.MessageSupplier[])", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method org.apache.logging.log4j.message.EntryMessage org.apache.logging.log4j.spi.AbstractLogger::enter(java.lang.String, org.apache.logging.log4j.util.MessageSupplier)", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.spi.AbstractLogger::entry()", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.spi.AbstractLogger::entry(java.lang.Object[])", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.spi.AbstractLogger::exit()", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method R org.apache.logging.log4j.spi.AbstractLogger::exit(R)", + "justification": "Removed deprecated method" + }, + { + "code": "java.class.removed", + "old": "class org.apache.logging.log4j.spi.LoggerContextKey", + "justification": "Removed deprecated method" + }, + { + "code": "java.class.removed", + "old": "class org.apache.logging.log4j.util.ProcessIdUtil", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.util.ProviderUtil::loadProviders(java.util.Enumeration, java.lang.ClassLoader)", + "justification": "Removed deprecated method" + }, + { + "code": "java.method.removed", + "old": "method org.apache.logging.log4j.util.StackLocator.PrivateSecurityManager org.apache.logging.log4j.util.StackLocator::getSecurityManager()", + "justification": "Internal inner class moved to its own class so it can be shared with Java 9" + }, + { + "code": "java.method.nowStatic", + "old": "method java.lang.String org.apache.logging.log4j.util.Strings::toRootUpperCase(java.lang.String)", + "new": "method java.lang.String org.apache.logging.log4j.util.Strings::toRootUpperCase(java.lang.String)", + "justification": "Method was inaccessible" + } + ] + } +] diff --git a/log4j-api/src/main/java/module-info.java b/log4j-api/src/main/java/module-info.java new file mode 100644 index 00000000000..57f500e63e1 --- /dev/null +++ b/log4j-api/src/main/java/module-info.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +import org.apache.logging.log4j.util.EnvironmentPropertySource; +import org.apache.logging.log4j.util.PropertySource; +import org.apache.logging.log4j.util.SystemPropertiesPropertySource; + +/** + *

Log4j public API for libraries and applications. This module is provided as a portable + * {@linkplain org.apache.logging.log4j.Logger logging API} which supports independent + * {@linkplain org.apache.logging.log4j.spi.Provider logging provider} backends for configuring + * the underlying logging system. The {@link org.apache.logging.log4j} package contains the main APIs for loggers, + * markers, logging levels, thread context maps (aka MDC), and thread context stacks (aka NDC).

+ * + *

Logging provider SPIs are located in {@link org.apache.logging.log4j.spi}. A reference implementation + * is given in {@link org.apache.logging.log4j.simple} which is used internally by the + * {@linkplain org.apache.logging.log4j.status.StatusLogger status logger API}.

+ */ +module org.apache.logging.log4j { + exports org.apache.logging.log4j; + exports org.apache.logging.log4j.message; + exports org.apache.logging.log4j.simple; + exports org.apache.logging.log4j.spi; + exports org.apache.logging.log4j.status; + exports org.apache.logging.log4j.util; + + requires static java.sql; + requires static org.osgi.framework; + uses org.apache.logging.log4j.spi.Provider; + uses PropertySource; + uses org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory; + + provides PropertySource with EnvironmentPropertySource, SystemPropertiesPropertySource; +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/BridgeAware.java b/log4j-api/src/main/java/org/apache/logging/log4j/BridgeAware.java new file mode 100644 index 00000000000..fc3b8fd929a --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/BridgeAware.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +/** + * Extended interface to allow bridges between logging systems to convey the + * correct location information. + * + */ +public interface BridgeAware { + + /** + * Fully qualified class name of the entry point to the logging system. This + * class will not appear in the location information. + * + * @param fqcn + * @return this + */ + public void setEntryPoint(final String fqcn); +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/Level.java b/log4j-api/src/main/java/org/apache/logging/log4j/Level.java index cbb4dc73c47..e3570586261 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/Level.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/Level.java @@ -17,7 +17,6 @@ package org.apache.logging.log4j; import java.io.Serializable; -import java.util.Collection; import java.util.Locale; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -28,83 +27,105 @@ /** * Levels used for identifying the severity of an event. Levels are organized from most specific to least: - *
    - *
  • {@link #OFF} (most specific, no logging)
  • - *
  • {@link #FATAL} (most specific, little data)
  • - *
  • {@link #ERROR}
  • - *
  • {@link #WARN}
  • - *
  • {@link #INFO}
  • - *
  • {@link #DEBUG}
  • - *
  • {@link #TRACE} (least specific, a lot of data)
  • - *
  • {@link #ALL} (least specific, all data)
  • - *
- * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameDescription
{@link #OFF}No events will be logged.
{@link #FATAL}A fatal event that will prevent the application from continuing.
{@link #ERROR}An error in the application, possibly recoverable.
{@link #WARN}An event that might possible lead to an error.
{@link #INFO}An event for informational purposes.
{@link #DEBUG}A general debugging event.
{@link #TRACE}A fine-grained debug message, typically capturing the flow through the application.
{@link #ALL}All events should be logged.
+ *

* Typically, configuring a level in a filter or on a logger will cause logging events of that level and those that are * more specific to pass through the filter. A special level, {@link #ALL}, is guaranteed to capture all levels when * used in logging configurations. + *

*/ public final class Level implements Comparable, Serializable { + private static final Level[] EMPTY_ARRAY = {}; + + private static final ConcurrentMap LEVELS = new ConcurrentHashMap<>(); // SUPPRESS CHECKSTYLE + /** * No events will be logged. */ - public static final Level OFF; + public static final Level OFF = new Level("OFF", StandardLevel.OFF.intLevel()); /** - * A severe error that will prevent the application from continuing. + * A fatal event that will prevent the application from continuing. */ - public static final Level FATAL; + public static final Level FATAL = new Level("FATAL", StandardLevel.FATAL.intLevel()); /** * An error in the application, possibly recoverable. */ - public static final Level ERROR; + public static final Level ERROR = new Level("ERROR", StandardLevel.ERROR.intLevel()); /** * An event that might possible lead to an error. */ - public static final Level WARN; + public static final Level WARN = new Level("WARN", StandardLevel.WARN.intLevel()); /** * An event for informational purposes. */ - public static final Level INFO; + public static final Level INFO = new Level("INFO", StandardLevel.INFO.intLevel()); /** * A general debugging event. */ - public static final Level DEBUG; + public static final Level DEBUG = new Level("DEBUG", StandardLevel.DEBUG.intLevel()); /** * A fine-grained debug message, typically capturing the flow through the application. */ - public static final Level TRACE; + public static final Level TRACE = new Level("TRACE", StandardLevel.TRACE.intLevel()); /** * All events should be logged. */ - public static final Level ALL; + public static final Level ALL = new Level("ALL", StandardLevel.ALL.intLevel()); + + public static final String NAMESPACE = "Level"; /** * @since 2.1 */ - public static final String CATEGORY = "Level"; - - private static final ConcurrentMap LEVELS = new ConcurrentHashMap<>(); // SUPPRESS CHECKSTYLE + public static final String CATEGORY = NAMESPACE; private static final long serialVersionUID = 1581082L; - static { - OFF = new Level("OFF", StandardLevel.OFF.intLevel()); - FATAL = new Level("FATAL", StandardLevel.FATAL.intLevel()); - ERROR = new Level("ERROR", StandardLevel.ERROR.intLevel()); - WARN = new Level("WARN", StandardLevel.WARN.intLevel()); - INFO = new Level("INFO", StandardLevel.INFO.intLevel()); - DEBUG = new Level("DEBUG", StandardLevel.DEBUG.intLevel()); - TRACE = new Level("TRACE", StandardLevel.TRACE.intLevel()); - ALL = new Level("ALL", StandardLevel.ALL.intLevel()); - } - private final String name; private final int intLevel; private final StandardLevel standardLevel; @@ -119,7 +140,7 @@ private Level(final String name, final int intLevel) { this.name = name; this.intLevel = intLevel; this.standardLevel = StandardLevel.getStandardLevel(intLevel); - if (LEVELS.putIfAbsent(name, this) != null) { + if (LEVELS.putIfAbsent(toUpperCase(name.trim()), this) != null) { throw new IllegalStateException("Level " + name + " has already been defined."); } } @@ -239,15 +260,20 @@ public String toString() { * @throws java.lang.IllegalArgumentException if the name is null or intValue is less than zero. */ public static Level forName(final String name, final int intValue) { - final Level level = LEVELS.get(name); + if (Strings.isEmpty(name)) { + throw new IllegalArgumentException("Illegal null or empty Level name."); + } + final String normalizedName = toUpperCase(name.trim()); + final Level level = LEVELS.get(normalizedName); if (level != null) { return level; } try { + // use original capitalization return new Level(name, intValue); } catch (final IllegalStateException ex) { // The level was added by something else so just return that one. - return LEVELS.get(name); + return LEVELS.get(normalizedName); } } @@ -256,20 +282,24 @@ public static Level forName(final String name, final int intValue) { * * @param name The name of the Level. * @return The Level or null. + * @throws java.lang.IllegalArgumentException if the name is null. */ public static Level getLevel(final String name) { - return LEVELS.get(name); + if (Strings.isEmpty(name)) { + throw new IllegalArgumentException("Illegal null or empty Level name."); + } + return LEVELS.get(toUpperCase(name.trim())); } /** * Converts the string passed as argument to a level. If the conversion fails, then this method returns * {@link #DEBUG}. * - * @param sArg The name of the desired Level. + * @param level The name of the desired Level. * @return The Level associated with the String. */ - public static Level toLevel(final String sArg) { - return toLevel(sArg, Level.DEBUG); + public static Level toLevel(final String level) { + return toLevel(level, Level.DEBUG); } /** @@ -284,7 +314,7 @@ public static Level toLevel(final String name, final Level defaultLevel) { if (name == null) { return defaultLevel; } - final Level level = LEVELS.get(toUpperCase(name)); + final Level level = LEVELS.get(toUpperCase(name.trim())); return level == null ? defaultLevel : level; } @@ -298,8 +328,7 @@ private static String toUpperCase(final String name) { * @return An array of Levels. */ public static Level[] values() { - final Collection values = Level.LEVELS.values(); - return values.toArray(new Level[values.size()]); + return Level.LEVELS.values().toArray(EMPTY_ARRAY); } /** @@ -312,7 +341,7 @@ public static Level[] values() { */ public static Level valueOf(final String name) { Objects.requireNonNull(name, "No level name given."); - final String levelName = toUpperCase(name); + final String levelName = toUpperCase(name.trim()); final Level level = LEVELS.get(levelName); if (level != null) { return level; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogBuilder.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogBuilder.java new file mode 100644 index 00000000000..aea90282918 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogBuilder.java @@ -0,0 +1,296 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.Supplier; + + +/** + * Interface for constructing log events before logging them. Instances of LogBuilders should only be created + * by calling one of the Logger methods that return a LogBuilder. + */ +public interface LogBuilder { + + LogBuilder NOOP = new LogBuilder() {}; + + /** + * Includes a Marker in the log event. Interface default method does nothing. + * @param marker The Marker to log. + * @return The LogBuilder. + */ + default LogBuilder withMarker(final Marker marker) { + return this; + } + + /** + * Includes a Throwable in the log event. Interface default method does nothing. + * @param throwable The Throwable to log. + * @return the LogBuilder. + */ + default LogBuilder withThrowable(final Throwable throwable) { + return this; + } + + /** + * An implementation will calculate the caller's stack frame and include it in the log event. + * Interface default method does nothing. + * @return The LogBuilder. + */ + default LogBuilder withLocation() { + return this; + } + + /** + * Adds the specified stack trace element to the log event. Interface default method does nothing. + * @param location The stack trace element to include in the log event. + * @return The LogBuilder. + */ + default LogBuilder withLocation(final StackTraceElement location) { + return this; + } + + /** + * Causes all the data collected to be logged along with the message. Interface default method does nothing. + * @param message The message to log. + */ + default void log(final CharSequence message) { + } + + /** + * Causes all the data collected to be logged along with the message. Interface default method does nothing. + * @param message The message to log. + */ + default void log(final String message) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param params parameters to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object... params) { + } + + /** + * Causes all the data collected to be logged along with the message and parameters. + * Interface default method does nothing. + * @param message The message. + * @param params Parameters to the message. + */ + default void log(final String message, final Supplier... params) { + } + + /** + * Causes all the data collected to be logged along with the message. Interface default method does nothing. + * @param message The message to log. + */ + default void log(final Message message) { + } + + /** + * Causes all the data collected to be logged along with the message. Interface default method does nothing. + * @param messageSupplier The supplier of the message to log. + */ + default void log(final Supplier messageSupplier) { + } + + /** + * Causes all the data collected to be logged along with the message. + * + * @param messageSupplier The supplier of the message to log. + * @return the message logger or {@literal null} if no logging occurred. + * @since 2.20 + */ + default Message logAndGet(final Supplier messageSupplier) { + return null; + } + + /** + * Causes all the data collected to be logged along with the message. Interface default method does nothing. + * @param message The message to log. + */ + default void log(final Object message) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { + } + + /** + * Logs a message with parameters. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + } + + /** + * Logs a message with parameters. Interface default method does nothing. + * + * @param message the message to log; the format depends on the message factory. + * @param p0 parameter to the message. + * @param p1 parameter to the message. + * @param p2 parameter to the message. + * @param p3 parameter to the message. + * @param p4 parameter to the message. + * @param p5 parameter to the message. + * @param p6 parameter to the message. + * @param p7 parameter to the message. + * @param p8 parameter to the message. + * @param p9 parameter to the message. + * + * @see org.apache.logging.log4j.util.Unbox + */ + default void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + } + + /** + * Causes all the data collected to be logged. Default implementation does nothing. + */ + default void log() { + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java index ecff44339f0..1f8dec7827b 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java @@ -17,21 +17,16 @@ package org.apache.logging.log4j; import java.net.URI; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.message.StringFormatterMessageFactory; import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; import org.apache.logging.log4j.spi.LoggerContext; import org.apache.logging.log4j.spi.LoggerContextFactory; -import org.apache.logging.log4j.spi.Provider; +import org.apache.logging.log4j.spi.LoggingSystem; +import org.apache.logging.log4j.spi.LoggingSystemProperties; import org.apache.logging.log4j.spi.Terminable; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.apache.logging.log4j.util.ProviderUtil; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; @@ -50,10 +45,10 @@ public class LogManager { * Log4j property to set to the fully qualified class name of a custom implementation of * {@link org.apache.logging.log4j.spi.LoggerContextFactory}. */ - public static final String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory"; + public static final String FACTORY_PROPERTY_NAME = LoggingSystemProperties.LOGGER_CONTEXT_FACTORY_CLASS; /** - * The name of the root Logger. + * The name of the root Logger is {@value #ROOT_LOGGER_NAME}. */ public static final String ROOT_LOGGER_NAME = Strings.EMPTY; @@ -62,68 +57,6 @@ public class LogManager { // for convenience private static final String FQCN = LogManager.class.getName(); - private static volatile LoggerContextFactory factory; - - /** - * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be - * extended to allow multiple implementations to be used. - */ - static { - // Shortcut binding to force a specific logging implementation. - final PropertiesUtil managerProps = PropertiesUtil.getProperties(); - final String factoryClassName = managerProps.getStringProperty(FACTORY_PROPERTY_NAME); - if (factoryClassName != null) { - try { - factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, LoggerContextFactory.class); - } catch (final ClassNotFoundException cnfe) { - LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName); - } catch (final Exception ex) { - LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex); - } - } - - if (factory == null) { - final SortedMap factories = new TreeMap<>(); - // note that the following initial call to ProviderUtil may block until a Provider has been installed when - // running in an OSGi environment - if (ProviderUtil.hasProviders()) { - for (final Provider provider : ProviderUtil.getProviders()) { - final Class factoryClass = provider.loadLoggerContextFactory(); - if (factoryClass != null) { - try { - factories.put(provider.getPriority(), factoryClass.newInstance()); - } catch (final Exception e) { - LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider - .getUrl(), e); - } - } - } - - if (factories.isEmpty()) { - LOGGER.error("Log4j2 could not find a logging implementation. " - + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console..."); - factory = new SimpleLoggerContextFactory(); - } else if (factories.size() == 1) { - factory = factories.get(factories.lastKey()); - } else { - final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n"); - for (final Map.Entry entry : factories.entrySet()) { - sb.append("Factory: ").append(entry.getValue().getClass().getName()); - sb.append(", Weighting: ").append(entry.getKey()).append('\n'); - } - factory = factories.get(factories.lastKey()); - sb.append("Using factory: ").append(factory.getClass().getName()); - LOGGER.warn(sb.toString()); - - } - } else { - LOGGER.error("Log4j2 could not find a logging implementation. " - + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console..."); - factory = new SimpleLoggerContextFactory(); - } - } - } - /** * Prevents instantiation */ @@ -152,10 +85,10 @@ public static boolean exists(final String name) { */ public static LoggerContext getContext() { try { - return factory.getContext(FQCN, null, null, true); + return getFactory().getContext(FQCN, null, null, true); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, null, null, true); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(FQCN, null, null, true); } } @@ -171,10 +104,10 @@ public static LoggerContext getContext() { public static LoggerContext getContext(final boolean currentContext) { // TODO: would it be a terrible idea to try and find the caller ClassLoader here? try { - return factory.getContext(FQCN, null, null, currentContext, null, null); + return getFactory().getContext(FQCN, null, null, currentContext, null, null); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, null, null, currentContext, null, null); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(FQCN, null, null, currentContext, null, null); } } @@ -191,10 +124,10 @@ public static LoggerContext getContext(final boolean currentContext) { */ public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) { try { - return factory.getContext(FQCN, loader, null, currentContext); + return getFactory().getContext(FQCN, loader, null, currentContext); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(FQCN, loader, null, currentContext); } } @@ -213,10 +146,10 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, final Object externalContext) { try { - return factory.getContext(FQCN, loader, externalContext, currentContext); + return getFactory().getContext(FQCN, loader, externalContext, currentContext); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, loader, externalContext, currentContext); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(FQCN, loader, externalContext, currentContext); } } @@ -235,10 +168,10 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, final URI configLocation) { try { - return factory.getContext(FQCN, loader, null, currentContext, configLocation, null); + return getFactory().getContext(FQCN, loader, null, currentContext, configLocation, null); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext, configLocation, + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(FQCN, loader, null, currentContext, configLocation, null); } } @@ -259,10 +192,10 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, final Object externalContext, final URI configLocation) { try { - return factory.getContext(FQCN, loader, externalContext, currentContext, configLocation, null); + return getFactory().getContext(FQCN, loader, externalContext, currentContext, configLocation, null); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, loader, externalContext, currentContext, + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(FQCN, loader, externalContext, currentContext, configLocation, null); } } @@ -284,10 +217,10 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext, final Object externalContext, final URI configLocation, final String name) { try { - return factory.getContext(FQCN, loader, externalContext, currentContext, configLocation, name); + return getFactory().getContext(FQCN, loader, externalContext, currentContext, configLocation, name); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(FQCN, loader, externalContext, currentContext, + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(FQCN, loader, externalContext, currentContext, configLocation, name); } } @@ -304,10 +237,10 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c */ protected static LoggerContext getContext(final String fqcn, final boolean currentContext) { try { - return factory.getContext(fqcn, null, null, currentContext); + return getFactory().getContext(fqcn, null, null, currentContext); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(fqcn, null, null, currentContext); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(fqcn, null, null, currentContext); } } @@ -326,10 +259,10 @@ protected static LoggerContext getContext(final String fqcn, final boolean curre protected static LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { try { - return factory.getContext(fqcn, loader, null, currentContext); + return getFactory().getContext(fqcn, loader, null, currentContext); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(fqcn, loader, null, currentContext); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(fqcn, loader, null, currentContext); } } @@ -349,12 +282,12 @@ protected static LoggerContext getContext(final String fqcn, final ClassLoader l * @return a LoggerContext. */ protected static LoggerContext getContext(final String fqcn, final ClassLoader loader, - final boolean currentContext, URI configLocation, String name) { + final boolean currentContext, final URI configLocation, final String name) { try { - return factory.getContext(fqcn, loader, null, currentContext, configLocation, name); + return getFactory().getContext(fqcn, loader, null, currentContext, configLocation, name); } catch (final IllegalStateException ex) { - LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); - return new SimpleLoggerContextFactory().getContext(fqcn, loader, null, currentContext); + LOGGER.warn("{} Using SimpleLogger", ex.getMessage()); + return SimpleLoggerContextFactory.INSTANCE.getContext(fqcn, loader, null, currentContext); } } @@ -387,7 +320,27 @@ public static void shutdown() { * @since 2.6 */ public static void shutdown(final boolean currentContext) { - shutdown(getContext(currentContext)); + getFactory().shutdown(FQCN, null, currentContext, false); + } + + /** + * Shutdown the logging system if the logging system supports it. + * This is equivalent to calling {@code LogManager.shutdown(LogManager.getContext(currentContext))}. + * + * This call is synchronous and will block until shut down is complete. + * This may include flushing pending log events over network connections. + * + * @param currentContext if true a default LoggerContext (may not be the LoggerContext used to create a Logger + * for the calling class) will be used. + * If false the LoggerContext appropriate for the caller of this method is used. For + * example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be + * used and if the caller is a class in the container's classpath then a different LoggerContext may + * be used. + * @param allContexts if true all LoggerContexts that can be located will be shutdown. + * @since 2.13.0 + */ + public static void shutdown(final boolean currentContext, final boolean allContexts) { + getFactory().shutdown(FQCN, null, currentContext, allContexts); } /** @@ -400,23 +353,18 @@ public static void shutdown(final boolean currentContext) { * @since 2.6 */ public static void shutdown(final LoggerContext context) { - if (context != null && context instanceof Terminable) { + if (context instanceof Terminable) { ((Terminable) context).terminate(); } } - private static String toLoggerName(final Class cls) { - String canonicalName = cls.getCanonicalName(); - return canonicalName != null ? canonicalName : cls.getName(); - } - /** * Returns the current LoggerContextFactory. * * @return The LoggerContextFactory. */ public static LoggerContextFactory getFactory() { - return factory; + return LoggingSystem.getLoggerContextFactory(); } /** @@ -431,10 +379,10 @@ public static LoggerContextFactory getFactory() { *

* * @param factory the LoggerContextFactory to use. + * @see LoggingSystem */ - // FIXME: should we allow only one update of the factory? public static void setFactory(final LoggerContextFactory factory) { - LogManager.factory = factory; + LoggingSystem.getInstance().setLoggerContextFactory(factory); } /** @@ -578,7 +526,7 @@ public static Logger getLogger() { */ public static Logger getLogger(final Class clazz) { final Class cls = callerClass(clazz); - return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls)); + return getContext(cls.getClassLoader(), false).getLogger(cls); } /** @@ -594,7 +542,7 @@ public static Logger getLogger(final Class clazz) { */ public static Logger getLogger(final Class clazz, final MessageFactory messageFactory) { final Class cls = callerClass(clazz); - return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls), messageFactory); + return getContext(cls.getClassLoader(), false).getLogger(cls, messageFactory); } /** @@ -670,7 +618,7 @@ public static Logger getLogger(final String name, final MessageFactory messageFa * @return The Logger. */ protected static Logger getLogger(final String fqcn, final String name) { - return factory.getContext(fqcn, null, null, false).getLogger(name); + return getFactory().getContext(fqcn, null, null, false).getLogger(name); } /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/Logger.java b/log4j-api/src/main/java/org/apache/logging/log4j/Logger.java index 995c6df02f2..2b2b30c05ab 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/Logger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/Logger.java @@ -17,9 +17,9 @@ package org.apache.logging.log4j; import org.apache.logging.log4j.message.EntryMessage; +import org.apache.logging.log4j.message.FlowMessageFactory; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.message.MessageFactory2; import org.apache.logging.log4j.util.MessageSupplier; import org.apache.logging.log4j.util.Supplier; @@ -33,12 +33,12 @@ * {@link LogManager#getLogger()} method). Thus, the simplest way to use this would be like so: *

* - *
+ * 

  * public class MyClass {
  *     private static final Logger LOGGER = LogManager.getLogger();
  *     // ...
  * }
- * 
+ *
*

* For ease of filtering, searching, sorting, etc., it is generally a good idea to create Loggers for each class rather * than sharing Loggers. Instead, {@link Marker Markers} should be used for shared, filterable identification. @@ -52,13 +52,13 @@ * allow client code to lazily log messages without explicitly checking if the requested log level is enabled. For * example, previously one would write: * - *

+ * 

  * // pre-Java 8 style optimization: explicitly check the log level
  * // to make sure the expensiveOperation() method is only called if necessary
  * if (logger.isTraceEnabled()) {
  *     logger.trace("Some long-running operation returned {}", expensiveOperation());
  * }
- * 
+ *
*

* With Java 8, the same effect can be achieved with a lambda expression: * @@ -69,7 +69,7 @@ * * *

- * Note that although {@link MessageSupplier} is provided, using {@link Supplier Supplier} works just the + * Note that although {@link MessageSupplier} is provided, using {@link Supplier Supplier<Message>} works just the * same. MessageSupplier was deprecated in 2.6 and un-deprecated in 2.8.1. Anonymous class usage of these APIs * should prefer using Supplier instead. *

@@ -620,34 +620,6 @@ void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4 void debug(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9); - /** - * Logs entry to a method. Used when the method in question has no parameters or when the parameters should not be - * logged. - * @deprecated Use {@link #traceEntry()} instead which performs the same function. - */ - @Deprecated - void entry(); - - /** - * Logs entry to a method along with its parameters (consider using one of the {@code traceEntry(...)} methods instead.) - *

- * For example: - *

- *
-     * public void doSomething(String foo, int bar) {
-     *     LOGGER.entry(foo, bar);
-     *     // do something
-     * }
-     * 
- *

- * The use of methods such as this are more effective when combined with aspect-oriented programming or other - * bytecode manipulation tools. It can be rather tedious (and messy) to use this type of method manually. - *

- * - * @param params The parameters to the method. - */ - void entry(Object... params); - /** * Logs a message with the specific Marker at the {@link Level#ERROR ERROR} level. * @@ -1174,28 +1146,6 @@ void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4 void error(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9); - /** - * Logs exit from a method. Used for methods that do not return anything. - * @deprecated Use {@link #traceExit()} instead which performs the same function. - */ - @Deprecated - void exit(); - - /** - * Logs exiting from a method with the result. This may be coded as: - * - *
-     * return LOGGER.exit(myResult);
-     * 
- * - * @param The type of the parameter and object being returned. - * @param result The result being returned from the method call. - * @return the result. - * @deprecated Use {@link #traceExit(Object)} instead which performs the same function. - */ - @Deprecated - R exit(R result); - /** * Logs a message with the specific Marker at the {@link Level#FATAL FATAL} level. * @@ -1732,16 +1682,19 @@ void fatal(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Gets the message factory used to convert message Objects and Strings/CharSequences into actual log Messages. * - * Since version 2.6, Log4j internally uses message factories that implement the {@link MessageFactory2} interface. - * From version 2.6.2, the return type of this method was changed from {@link MessageFactory} to - * {@code MF}. The returned factory will always implement {@link MessageFactory2}, - * but the return type of this method could not be changed to {@link MessageFactory2} without breaking binary - * compatibility. - * - * @return the message factory, as an instance of {@link MessageFactory2} + * @param The type of the MessageFactory. + * @return the message factory, as an instance of {@link MessageFactory} */ MF getMessageFactory(); + /** + * Gets the flow message factory used to convert messages into flow messages. + * + * @return the flow message factory, as an instance of {@link FlowMessageFactory}. + * @since 2.20 + */ + FlowMessageFactory getFlowMessageFactory(); + /** * Gets the logger name. * @@ -3552,20 +3505,20 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs entry to a method along with its parameters. For example, * - *
+     * 

      * public void doSomething(String foo, int bar) {
      *     LOGGER.traceEntry("Parameters: {} and {}", foo, bar);
      *     // do something
      * }
-     * 
+ *
* or: - *
+     * 

      * public int doSomething(String foo, int bar) {
      *     Message m = LOGGER.traceEntry("doSomething(foo={}, bar={})", foo, bar);
      *     // do something
      *     return traceExit(m, value);
      * }
-     * 
+ *
* * @param format The format String for the parameters. * @param params The parameters to the method. @@ -3579,10 +3532,12 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * Logs entry to a method along with its parameters. For example, * *
+     * 
      * public void doSomething(Request foo) {
-     *     LOGGER.traceEntry(()->gson.toJson(foo));
+     *     LOGGER.traceEntry(()->gson.toJson(foo));
      *     // do something
      * }
+     * 
      * 
* * @param paramSuppliers The Suppliers for the parameters to the method. @@ -3596,10 +3551,12 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 * Logs entry to a method along with its parameters. For example, * *
+     * 
      * public void doSomething(String foo, int bar) {
-     *     LOGGER.traceEntry("Parameters: {} and {}", ()->gson.toJson(foo), ()-> bar);
+     *     LOGGER.traceEntry("Parameters: {} and {}", ()->gson.toJson(foo), ()-> bar);
      *     // do something
      * }
+     * 
      * 
* * @param format The format String for the parameters. @@ -3612,12 +3569,12 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs entry to a method using a Message to describe the parameters. - *
+     * 

      * public void doSomething(Request foo) {
      *     LOGGER.traceEntry(new JsonMessage(foo));
      *     // do something
      * }
-     * 
+ *
*

* Avoid passing a {@code ReusableMessage} to this method (therefore, also avoid passing messages created by * calling {@code logger.getMessageFactory().newMessage("some message")}): Log4j will replace such messages with @@ -3643,9 +3600,9 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs exiting from a method with the result. This may be coded as: * - *

+     * 

      * return LOGGER.traceExit(myResult);
-     * 
+ *
* * @param The type of the parameter and object being returned. * @param result The result being returned from the method call. @@ -3658,9 +3615,9 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs exiting from a method with the result. This may be coded as: * - *
+     * 

      * return LOGGER.traceExit("Result: {}", myResult);
-     * 
+ *
* * @param The type of the parameter and object being returned. * @param format The format String for the result. @@ -3674,13 +3631,13 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs exiting from a method with no result. Allows custom formatting of the result. This may be coded as: * - *
+     * 

      * public long doSomething(int a, int b) {
      *    EntryMessage m = traceEntry("doSomething(a={}, b={})", a, b);
      *    // ...
      *    return LOGGER.traceExit(m);
      * }
-     * 
+ *
* @param message The Message containing the formatted result. * * @since 2.6 @@ -3690,13 +3647,13 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs exiting from a method with the result. Allows custom formatting of the result. This may be coded as: * - *
+     * 

      * public long doSomething(int a, int b) {
      *    EntryMessage m = traceEntry("doSomething(a={}, b={})", a, b);
      *    // ...
      *    return LOGGER.traceExit(m, myResult);
      * }
-     * 
+ *
* @param message The Message containing the formatted result. * @param result The result being returned from the method call. * @@ -3710,9 +3667,9 @@ void trace(String message, Object p0, Object p1, Object p2, Object p3, Object p4 /** * Logs exiting from a method with the result. Allows custom formatting of the result. This may be coded as: * - *
+     * 

      * return LOGGER.traceExit(new JsonMessage(myResult), myResult);
-     * 
+ *
* @param message The Message containing the formatted result. * @param result The result being returned from the method call. * @@ -4249,4 +4206,86 @@ void warn(String message, Object p0, Object p1, Object p2, Object p3, Object p4, void warn(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9); + /** + * Logs a Message. + * @param level The logging Level to check. + * @param marker A Marker or null. + * @param fqcn The fully qualified class name of the logger entry point, used to determine the caller class and + * method when location information needs to be logged. + * @param location The location of the caller. + * @param message The message format. + * @param throwable the exception to log, including its stack trace. + * @since 2.13.0 + */ + default void logMessage(final Level level, final Marker marker, final String fqcn, final StackTraceElement location, final Message message, + final Throwable throwable) { + + } + + /** + * Construct a trace log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder atTrace() { + return LogBuilder.NOOP; + } + /** + * Construct a trace log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder atDebug() { + return LogBuilder.NOOP; + } + /** + * Construct a trace log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder atInfo() { + return LogBuilder.NOOP; + } + /** + * Construct a trace log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder atWarn() { + return LogBuilder.NOOP; + } + /** + * Construct a trace log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder atError() { + return LogBuilder.NOOP; + } + /** + * Construct a trace log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder atFatal() { + return LogBuilder.NOOP; + } + /** + * Construct a log event that will always be logged. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder always() { + return LogBuilder.NOOP; + } + /** + * Construct a log event. + * @param level The Logging Level. + * @return a LogBuilder. + * @since 2.13.0 + */ + default LogBuilder atLevel(final Level level) { + return LogBuilder.NOOP; + } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java index 8843883b8ce..72164008ded 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java @@ -68,38 +68,6 @@ public static Marker getMarker(final String name) { return result; } - /** - * Retrieves or creates a Marker with the specified parent. The parent must have been previously created. - * - * @param name The name of the Marker. - * @param parent The name of the parent Marker. - * @return The Marker with the specified name. - * @throws IllegalArgumentException if the parent Marker does not exist. - * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. - */ - @Deprecated - public static Marker getMarker(final String name, final String parent) { - final Marker parentMarker = MARKERS.get(parent); - if (parentMarker == null) { - throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined"); - } - return getMarker(name, parentMarker); - } - - /** - * Retrieves or creates a Marker with the specified parent. - * - * @param name The name of the Marker. - * @param parent The parent Marker. - * @return The Marker with the specified name. - * @throws IllegalArgumentException if any argument is {@code null} - * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release. - */ - @Deprecated - public static Marker getMarker(final String name, final Marker parent) { - return getMarker(name).addParents(parent); - } - /** * Consider this class private, it is only public to satisfy Jackson for XML and JSON IO. *

@@ -231,10 +199,11 @@ public String getName() { @Override public Marker[] getParents() { - if (this.parents == null) { + Marker[] parentsSnapshot = parents; + if (parentsSnapshot == null) { return null; } - return Arrays.copyOf(this.parents, this.parents.length); + return Arrays.copyOf(parentsSnapshot, parentsSnapshot.length); } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java index c4ae445f4e5..5711276ef52 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j; import java.io.Serializable; @@ -28,21 +27,19 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.spi.DefaultThreadContextMap; -import org.apache.logging.log4j.spi.DefaultThreadContextStack; -import org.apache.logging.log4j.spi.NoOpThreadContextMap; +import org.apache.logging.log4j.spi.LoggingSystem; +import org.apache.logging.log4j.spi.LoggingSystemProperties; import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; import org.apache.logging.log4j.spi.ThreadContextMap; -import org.apache.logging.log4j.spi.ThreadContextMap2; -import org.apache.logging.log4j.spi.CleanableThreadContextMap; -import org.apache.logging.log4j.spi.ThreadContextMapFactory; import org.apache.logging.log4j.spi.ThreadContextStack; -import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.InternalApi; /** * The ThreadContext allows applications to store information either in a Map or a Stack. *

* The MDC is managed on a per thread basis. To enable automatic inheritance of copies of the MDC - * to newly created threads, enable the {@value DefaultThreadContextMap#INHERITABLE_MAP} Log4j system property. + * to newly created threads, enable the {@value LoggingSystemProperties#THREAD_CONTEXT_MAP_INHERITABLE} + * Log4j system property. *

* @see Thread Context Manual */ @@ -188,13 +185,6 @@ public void remove() { @SuppressWarnings("PublicStaticCollectionField") public static final ThreadContextStack EMPTY_STACK = new EmptyThreadContextStack(); - private static final String DISABLE_MAP = "disableThreadContextMap"; - private static final String DISABLE_STACK = "disableThreadContextStack"; - private static final String DISABLE_ALL = "disableThreadContext"; - - private static boolean disableAll; - private static boolean useMap; - private static boolean useStack; private static ThreadContextMap contextMap; private static ThreadContextStack contextStack; private static ReadOnlyThreadContextMap readOnlyContextMap; @@ -210,20 +200,10 @@ private ThreadContext() { /** * Consider private, used for testing. */ - static void init() { - ThreadContextMapFactory.init(); - contextMap = null; - final PropertiesUtil managerProps = PropertiesUtil.getProperties(); - disableAll = managerProps.getBooleanProperty(DISABLE_ALL); - useStack = !(managerProps.getBooleanProperty(DISABLE_STACK) || disableAll); - useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) || disableAll); - - contextStack = new DefaultThreadContextStack(useStack); - if (!useMap) { - contextMap = new NoOpThreadContextMap(); - } else { - contextMap = ThreadContextMapFactory.createThreadContextMap(); - } + @InternalApi + public static void init() { + contextMap = LoggingSystem.createContextMap(); + contextStack = LoggingSystem.createContextStack(); if (contextMap instanceof ReadOnlyThreadContextMap) { readOnlyContextMap = (ReadOnlyThreadContextMap) contextMap; } else { @@ -246,6 +226,24 @@ public static void put(final String key, final String value) { contextMap.put(key, value); } + /** + * Puts a context value (the value parameter) as identified with the key parameter into + * the current thread's context map if the key does not exist. + * + *

+ * If the current thread does not have a context map it is created as a side effect. + *

+ * + * @param key The key name. + * @param value The key value. + * @since 2.13.0 + */ + public static void putIfNull(final String key, final String value) { + if(!contextMap.containsKey(key)) { + contextMap.put(key, value); + } + } + /** * Puts all given context map entries into the current thread's * context map. @@ -256,15 +254,7 @@ public static void put(final String key, final String value) { * @since 2.7 */ public static void putAll(final Map m) { - if (contextMap instanceof ThreadContextMap2) { - ((ThreadContextMap2) contextMap).putAll(m); - } else if (contextMap instanceof DefaultThreadContextMap) { - ((DefaultThreadContextMap) contextMap).putAll(m); - } else { - for (final Map.Entry entry: m.entrySet()) { - contextMap.put(entry.getKey(), entry.getValue()); - } - } + contextMap.putAll(m); } /** @@ -298,15 +288,7 @@ public static void remove(final String key) { * @since 2.8 */ public static void removeAll(final Iterable keys) { - if (contextMap instanceof CleanableThreadContextMap) { - ((CleanableThreadContextMap) contextMap).removeAll(keys); - } else if (contextMap instanceof DefaultThreadContextMap) { - ((DefaultThreadContextMap) contextMap).removeAll(keys); - } else { - for (final String key : keys) { - contextMap.remove(key); - } - } + contextMap.removeAll(keys); } /** @@ -363,7 +345,6 @@ public static Map getImmutableContext() { *

* * @return the internal data structure used to store thread context key-value pairs or {@code null} - * @see ThreadContextMapFactory * @see DefaultThreadContextMap * @see org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap * @see org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap @@ -414,7 +395,7 @@ public static ContextStack getImmutableStack() { * @param stack The stack to use. */ public static void setStack(final Collection stack) { - if (stack.isEmpty() || !useStack) { + if (stack.isEmpty()) { return; } contextStack.clear(); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java new file mode 100644 index 00000000000..cd472c43e3e --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java @@ -0,0 +1,266 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.internal; + +import org.apache.logging.log4j.BridgeAware; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LambdaUtil; +import org.apache.logging.log4j.util.StackLocatorUtil; +import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.Supplier; + + +/** + * Collects data for a log event and then logs it. This class should be considered private. + */ +public class DefaultLogBuilder implements BridgeAware, LogBuilder { + + private static final String FQCN = DefaultLogBuilder.class.getName(); + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final Message EMPTY_MESSAGE = new SimpleMessage(Strings.EMPTY); + + private final Logger logger; + private Level level; + private Marker marker; + private Throwable throwable; + private StackTraceElement location; + private volatile boolean inUse; + private final long threadId; + private String fqcn = FQCN; + + public DefaultLogBuilder(final Logger logger, final Level level) { + this.logger = logger; + this.level = level; + this.threadId = Thread.currentThread().getId(); + this.inUse = true; + } + + public DefaultLogBuilder(final Logger logger) { + this.logger = logger; + this.inUse = false; + this.threadId = Thread.currentThread().getId(); + } + + @Override + public void setEntryPoint(String fqcn) { + this.fqcn = fqcn; + } + + /** + * This method should be considered internal. It is used to reset the LogBuilder for a new log message. + * @param level The logging level for this event. + * @return This LogBuilder instance. + */ + public LogBuilder reset(final Level level) { + this.inUse = true; + this.level = level; + this.marker = null; + this.throwable = null; + this.location = null; + return this; + } + + public LogBuilder withMarker(final Marker marker) { + this.marker = marker; + return this; + } + + public LogBuilder withThrowable(final Throwable throwable) { + this.throwable = throwable; + return this; + } + + public LogBuilder withLocation() { + location = StackLocatorUtil.getStackTraceElement(2); + return this; + } + + public LogBuilder withLocation(final StackTraceElement location) { + this.location = location; + return this; + } + + public boolean isInUse() { + return inUse; + } + + @Override + public void log(final Message message) { + if (isValid()) { + logMessage(message); + } + } + + @Override + public Message logAndGet(Supplier messageSupplier) { + Message message = null; + if (isValid()) { + logMessage(message = messageSupplier.get()); + } + return message; + } + + @Override + public void log(final CharSequence message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(final String message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(final String message, final Object... params) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, params)); + } + } + + @Override + public void log(final String message, final Supplier... params) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, LambdaUtil.getAll(params))); + } + } + + @Override + public void log(final Supplier messageSupplier) { + if (isValid()) { + logMessage(messageSupplier.get()); + } + } + + @Override + public void log(final Object message) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message)); + } + } + + @Override + public void log(final String message, final Object p0) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8)); + } + } + + @Override + public void log(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + if (isValid()) { + logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)); + } + } + + @Override + public void log() { + if (isValid()) { + logMessage(EMPTY_MESSAGE); + } + } + + private void logMessage(final Message message) { + try { + logger.logMessage(level, marker, fqcn, location, message, throwable); + } finally { + inUse = false; + } + } + + private boolean isValid() { + if (!inUse) { + LOGGER.warn("Attempt to reuse LogBuilder was ignored. {}", + StackLocatorUtil.getCallerClass(2)); + return false ; + } + if (this.threadId != Thread.currentThread().getId()) { + LOGGER.warn("LogBuilder can only be used on the owning thread. {}", + StackLocatorUtil.getCallerClass(2)); + return false; + } + return true; + } +} diff --git a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultObjectInputFilter.java similarity index 87% rename from log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.java rename to log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultObjectInputFilter.java index 6e7cee5278a..82494b34828 100644 --- a/log4j-api-java9/src/main/java/org/apache/logging/log4j/util/internal/DefaultObjectInputFilter.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultObjectInputFilter.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.util.internal; +package org.apache.logging.log4j.internal; import java.io.ObjectInputFilter; import java.util.Arrays; @@ -45,7 +45,7 @@ public DefaultObjectInputFilter() { delegate = null; } - public DefaultObjectInputFilter(ObjectInputFilter filter) { + public DefaultObjectInputFilter(final ObjectInputFilter filter) { delegate = filter; } @@ -54,13 +54,13 @@ public DefaultObjectInputFilter(ObjectInputFilter filter) { * @param filter The ObjectInputFilter. * @return The DefaultObjectInputFilter. */ - public static DefaultObjectInputFilter newInstance(ObjectInputFilter filter) { + public static DefaultObjectInputFilter newInstance(final ObjectInputFilter filter) { return new DefaultObjectInputFilter(filter); } @Override - public Status checkInput(FilterInfo filterInfo) { + public Status checkInput(final FilterInfo filterInfo) { Status status = null; if (delegate != null) { status = delegate.checkInput(filterInfo); @@ -68,7 +68,7 @@ public Status checkInput(FilterInfo filterInfo) { return status; } } - ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); + final ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); if (serialFilter != null) { status = serialFilter.checkInput(filterInfo); if (status != Status.UNDECIDED) { @@ -77,7 +77,7 @@ public Status checkInput(FilterInfo filterInfo) { } } if (filterInfo.serialClass() != null) { - String name = filterInfo.serialClass().getName(); + final String name = filterInfo.serialClass().getName(); if (isAllowedByDefault(name) || isRequiredPackage(name)) { return Status.ALLOWED; } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/LogManagerStatus.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/LogManagerStatus.java new file mode 100644 index 00000000000..6f96ae9ba72 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/LogManagerStatus.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.internal; + +/** + * Keeps track of LogManager initialization status; + */ +public class LogManagerStatus { + + private static boolean initialized = false; + + public static void setInitialized(final boolean managerStatus) { + initialized = managerStatus; + } + + public static boolean isInitialized() { + return initialized; + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/AbstractMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/AbstractMessageFactory.java index 6429ed7852a..6f8ecfa5f0f 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/AbstractMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/AbstractMessageFactory.java @@ -19,128 +19,13 @@ import java.io.Serializable; /** - * Provides an abstract superclass for {@link MessageFactory2} implementations with default implementations (and for - * {@link MessageFactory} by extension). - *

- * This class is immutable. - *

- *

Note to implementors

- *

- * Subclasses can implement the {@link MessageFactory2} methods when they can most effectively build {@link Message} - * instances. If a subclass does not implement {@link MessageFactory2} methods, these calls are routed through - * {@link #newMessage(String, Object...)} in this class. - *

+ * Provides an abstract superclass for {@link MessageFactory}. This class is now unnecessary as all default + * methods are provided by the (@link MessageFactory) interface. + * + * @deprecated MessageFactory has default methods that implement all the methods that were here. */ -public abstract class AbstractMessageFactory implements MessageFactory2, Serializable { +@Deprecated +public abstract class AbstractMessageFactory implements MessageFactory, Serializable { private static final long serialVersionUID = -1307891137684031187L; - @Override - public Message newMessage(final CharSequence message) { - return new SimpleMessage(message); - } - - /* - * (non-Javadoc) - * - * @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.Object) - */ - @Override - public Message newMessage(final Object message) { - return new ObjectMessage(message); - } - - /* - * (non-Javadoc) - * - * @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.String) - */ - @Override - public Message newMessage(final String message) { - return new SimpleMessage(message); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0) { - return newMessage(message, new Object[] { p0 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1) { - return newMessage(message, new Object[] { p0, p1 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) { - return newMessage(message, new Object[] { p0, p1, p2 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { - return newMessage(message, new Object[] { p0, p1, p2, p3 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { - return newMessage(message, new Object[] { p0, p1, p2, p3, p4 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { - return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, - final Object p6) { - return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, - final Object p6, final Object p7) { - return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, - final Object p6, final Object p7, final Object p8) { - return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8 }); - } - - /** - * @since 2.6.1 - */ - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, - final Object p6, final Object p7, final Object p8, final Object p9) { - return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 }); - } - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/BasicThreadInformation.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/BasicThreadInformation.java index 623ab890d23..9f2ed3d75a3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/BasicThreadInformation.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/BasicThreadInformation.java @@ -64,11 +64,7 @@ public boolean equals(final Object o) { if (id != that.id) { return false; } - if (name != null ? !name.equals(that.name) : that.name != null) { - return false; - } - - return true; + return name != null ? name.equals(that.name) : that.name == null; } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/Clearable.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/Clearable.java new file mode 100644 index 00000000000..5c11b1b0542 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/Clearable.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +/** + * {@link Clearable} objects may be reset to a reusable state. + * + * This type should be combined into {@link ReusableMessage} as a default method for 3.0. + * + * @since 2.11.1 + */ +interface Clearable { + + /** + * Resets the object to a clean state. + */ + void clear(); + +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/DefaultFlowMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/DefaultFlowMessageFactory.java index 5babca3251a..f7923952c10 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/DefaultFlowMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/DefaultFlowMessageFactory.java @@ -18,6 +18,11 @@ import java.io.Serializable; +import org.apache.logging.log4j.spi.LoggingSystem; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.apache.logging.log4j.util.StringBuilders; +import org.apache.logging.log4j.util.Strings; + /** * Default factory for flow messages. * @@ -31,6 +36,7 @@ public class DefaultFlowMessageFactory implements FlowMessageFactory, Serializab private final String entryText; private final String exitText; + private final MessageFactory messageFactory; /** * Constructs a message factory with {@code "Enter"} and {@code "Exit"} as the default flow strings. @@ -48,9 +54,10 @@ public DefaultFlowMessageFactory(final String entryText, final String exitText) super(); this.entryText = entryText; this.exitText = exitText; + this.messageFactory = LoggingSystem.getMessageFactory(); } - private static class AbstractFlowMessage implements FlowMessage { + private static class AbstractFlowMessage implements FlowMessage, StringBuilderFormattable { private static final long serialVersionUID = 1L; private final Message message; @@ -72,7 +79,7 @@ public String getFormattedMessage() { @Override public String getFormat() { if (message != null) { - return text + ": " + message.getFormat(); + return text + " " + message.getFormat(); } return text; } @@ -102,6 +109,15 @@ public Message getMessage() { public String getText() { return text; } + + @Override + public void formatTo(StringBuilder buffer) { + buffer.append(text); + if (message != null) { + buffer.append(" "); + StringBuilders.appendValue(buffer, message); + } + } } private static final class SimpleEntryMessage extends AbstractFlowMessage implements EntryMessage { @@ -122,15 +138,17 @@ private static final class SimpleExitMessage extends AbstractFlowMessage impleme private final boolean isVoid; SimpleExitMessage(final String exitText, final EntryMessage message) { - super(exitText, message.getMessage()); + this(exitText, message.getMessage()); + } + + SimpleExitMessage(final String exitText, final Message message) { + super(exitText, message); this.result = null; isVoid = true; } SimpleExitMessage(final String exitText, final Object result, final EntryMessage message) { - super(exitText, message.getMessage()); - this.result = result; - isVoid = false; + this(exitText, result, message.getMessage()); } SimpleExitMessage(final String exitText, final Object result, final Message message) { @@ -165,6 +183,28 @@ public String getExitText() { return exitText; } + @Override + public EntryMessage newEntryMessage(String format, Object... params) { + final boolean hasFormat = Strings.isNotEmpty(format); + final Message message; + if (params == null || params.length == 0) { + message = hasFormat ? messageFactory.newMessage(format) : null; + } else if (hasFormat) { + message = messageFactory.newMessage(format, params); + } else { + final StringBuilder sb = new StringBuilder("params("); + for (int i = 0; i < params.length; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append("{}"); + } + sb.append(")"); + message = messageFactory.newMessage(sb.toString(), params); + } + return newEntryMessage(message); + } + /* * (non-Javadoc) * @@ -176,10 +216,27 @@ public EntryMessage newEntryMessage(final Message message) { } private Message makeImmutable(final Message message) { - if (!(message instanceof ReusableMessage)) { - return message; + if (message instanceof ReusableMessage) { + return ((ReusableMessage) message).memento(); + } + return message; + } + + @Override + public ExitMessage newExitMessage(String format, Object result) { + final boolean hasFormat = Strings.isNotEmpty(format); + final Message message; + if (result == null) { + message = hasFormat ? messageFactory.newMessage(format) : null; + } else { + message = messageFactory.newMessage(hasFormat ? format : "with({})", result); } - return new SimpleMessage(message.getFormattedMessage()); + return newExitMessage(message); + } + + @Override + public ExitMessage newExitMessage(Message message) { + return new SimpleExitMessage(exitText, message); } /* diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessageFactory.java index 56ce7e71331..079b602e039 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/FlowMessageFactory.java @@ -21,7 +21,17 @@ * @since 2.6 */ public interface FlowMessageFactory { - + + /** + * Creates a new entry message based on a format string with parameters. + * + * @param message format string + * @param params parameters + * @return the new entry message + * @since 2.20 + */ + EntryMessage newEntryMessage(String message, Object... params); + /** * Creates a new entry message based on an existing message. * @@ -30,6 +40,25 @@ public interface FlowMessageFactory { */ EntryMessage newEntryMessage(Message message); + /** + * Creates a new exit message based on a return value and a forma string. + * + * @param format a format string + * @param result the return value + * @return the new exit message + * @since 2.20 + */ + ExitMessage newExitMessage(String format, Object result); + + /** + * Creates a new exit message based on no return value and an existing message. + * + * @param message the original entry message + * @return the new exit message + * @since 2.20 + */ + ExitMessage newExitMessage(Message message); + /** * Creates a new exit message based on a return value and an existing message. * diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java index a13fd99a113..cbeb7d9b8ca 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessage.java @@ -149,11 +149,7 @@ public boolean equals(final Object o) { if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) { return false; } - if (!Arrays.equals(stringArgs, that.stringArgs)) { - return false; - } - - return true; + return Arrays.equals(stringArgs, that.stringArgs); } /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java index 805e24b68e2..9e3a6166738 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java @@ -16,16 +16,18 @@ */ package org.apache.logging.log4j.message; +import java.io.Serializable; + /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by - * extension.) - * + * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods. + * + *

*

Note to implementors

*

- * This class implements all {@link MessageFactory2} methods. + * This class implements all {@link MessageFactory} methods. *

*/ -public class FormattedMessageFactory extends AbstractMessageFactory { +public class FormattedMessageFactory implements MessageFactory, Serializable { private static final long serialVersionUID = 1L; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java index caf04f728c3..b201020d000 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java @@ -103,6 +103,8 @@ public LocalizedMessage(final String baseName, final String key, final Object ar } /** + * @param bundle The ResourceBundle for this message. + * @param key The key of the message in the bundle. * @since 2.8 */ public LocalizedMessage(final ResourceBundle bundle, final String key) { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java index b7e9803509b..576b61db176 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java @@ -16,19 +16,20 @@ */ package org.apache.logging.log4j.message; +import java.io.Serializable; import java.util.ResourceBundle; /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by - * extension.) - * + * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods. + * + *

*

Note to implementors

*

- * This class does not implement any {@link MessageFactory2} methods and lets the superclass funnel those calls + * This class does not implement any {@link MessageFactory} methods and lets the superclass funnel those calls * through {@link #newMessage(String, Object...)}. *

*/ -public class LocalizedMessageFactory extends AbstractMessageFactory { +public class LocalizedMessageFactory implements MessageFactory, Serializable { private static final long serialVersionUID = -1996295808703146741L; // FIXME: cannot use ResourceBundle name for serialization until Java 8 @@ -69,7 +70,7 @@ public ResourceBundle getResourceBundle() { @Override public Message newMessage(final String key) { if (resourceBundle == null) { - return new LocalizedMessage(baseName, key); + return new LocalizedMessage(baseName, key, null); } return new LocalizedMessage(resourceBundle, key); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java index 38319d23da6..ccd5895e902 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.message; +import java.util.AbstractMap; import java.util.Collections; import java.util.Map; import java.util.TreeMap; @@ -57,19 +58,26 @@ public class MapMessage, V> implements MultiFormatStr * When set as the format specifier causes the Map to be formatted as XML. */ public enum MapFormat { - + /** The map should be formatted as XML. */ XML, - + /** The map should be formatted as JSON. */ JSON, - - /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */ - JAVA; + + /** The map should be formatted the same as documented by {@link AbstractMap#toString()}. */ + JAVA, + + /** + * The map should be formatted the same as documented by {@link AbstractMap#toString()} but without quotes. + * + * @since 2.11.2 + */ + JAVA_UNQUOTED; /** * Maps a format name to an {@link MapFormat} while ignoring case. - * + * * @param format a MapFormat name * @return a MapFormat */ @@ -77,16 +85,17 @@ public static MapFormat lookupIgnoreCase(final String format) { return XML.name().equalsIgnoreCase(format) ? XML // : JSON.name().equalsIgnoreCase(format) ? JSON // : JAVA.name().equalsIgnoreCase(format) ? JAVA // + : JAVA_UNQUOTED.name().equalsIgnoreCase(format) ? JAVA_UNQUOTED // : null; } /** * All {@code MapFormat} names. - * + * * @return All {@code MapFormat} names. */ public static String[] names() { - return new String[] {XML.name(), JSON.name(), JAVA.name()}; + return new String[] {XML.name(), JSON.name(), JAVA.name(), JAVA_UNQUOTED.name()}; } } @@ -101,7 +110,7 @@ public MapMessage() { /** * Constructs a new instance. - * + * * @param initialCapacity the initial capacity. */ public MapMessage(final int initialCapacity) { @@ -185,13 +194,14 @@ public boolean containsKey(final String key) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. */ - public void put(final String key, final String value) { + public void put(final String candidateKey, final String value) { if (value == null) { - throw new IllegalArgumentException("No value provided for key " + key); + throw new IllegalArgumentException("No value provided for key " + candidateKey); } + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); } @@ -212,7 +222,7 @@ public void putAll(final Map map) { * @return The value of the element or null if the key is not present. */ public String get(final String key) { - Object result = data.getValue(key); + final Object result = data.getValue(key); return ParameterFormatter.deepToString(result); } @@ -227,6 +237,17 @@ public String remove(final String key) { return result; } + /** + * Allows subclasses to change a candidate key to an actual key. + * + * @param candidateKey The candidate key. + * @return The candidate key. + * @since 2.12 + */ + protected String toKey(final String candidateKey) { + return candidateKey; + } + /** * Formats the Structured data as described in RFC 5424. * @@ -249,7 +270,7 @@ public String asString(final String format) { return asString(); } } - + /** * Performs the given action for each key-value pair in this data structure * until all entries have been processed or the action throws an exception. @@ -300,7 +321,7 @@ public void forEach(final BiConsumer action) { public void forEach(final TriConsumer action, final S state) { data.forEach(action, state); } - + /** * Formats the Structured data as described in RFC 5424. * @@ -324,6 +345,9 @@ private StringBuilder format(final MapFormat format, final StringBuilder sb) { asJava(sb); break; } + case JAVA_UNQUOTED: + asJavaUnquoted(sb); + break; default : { appendMap(sb); } @@ -343,8 +367,8 @@ public void asXml(final StringBuilder sb) { sb.append(" "); - int size = sb.length(); - ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); + final int size = sb.length(); + ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb); StringBuilders.escapeXml(sb, size); sb.append("\n"); } @@ -394,40 +418,37 @@ protected void appendMap(final StringBuilder sb) { sb.append(' '); } sb.append(data.getKeyAt(i)).append(Chars.EQ).append(Chars.DQUOTE); - ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); + ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb); sb.append(Chars.DQUOTE); } } protected void asJson(final StringBuilder sb) { - sb.append('{'); - for (int i = 0; i < data.size(); i++) { - if (i > 0) { - sb.append(", "); - } - sb.append(Chars.DQUOTE); - int start = sb.length(); - sb.append(data.getKeyAt(i)); - StringBuilders.escapeJson(sb, start); - sb.append(Chars.DQUOTE).append(':').append(Chars.DQUOTE); - start = sb.length(); - ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); - StringBuilders.escapeJson(sb, start); - sb.append(Chars.DQUOTE); - } - sb.append('}'); + MapMessageJsonFormatter.format(sb, data); } + protected void asJavaUnquoted(final StringBuilder sb) { + asJava(sb, false); + } protected void asJava(final StringBuilder sb) { + asJava(sb, true); + } + + private void asJava(final StringBuilder sb, final boolean quoted) { sb.append('{'); for (int i = 0; i < data.size(); i++) { if (i > 0) { sb.append(", "); } - sb.append(data.getKeyAt(i)).append(Chars.EQ).append(Chars.DQUOTE); - ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null); - sb.append(Chars.DQUOTE); + sb.append(data.getKeyAt(i)).append(Chars.EQ); + if (quoted) { + sb.append(Chars.DQUOTE); + } + ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb); + if (quoted) { + sb.append(Chars.DQUOTE); + } } sb.append('}'); } @@ -453,7 +474,7 @@ public void formatTo(final StringBuilder buffer) { } @Override - public void formatTo(String[] formats, StringBuilder buffer) { + public void formatTo(final String[] formats, final StringBuilder buffer) { format(getFormat(formats), buffer); } @@ -485,10 +506,12 @@ public int hashCode() { public Throwable getThrowable() { return null; } - + /** * Default implementation does nothing. - * + * @param key The key. + * @param value The boolean value. + * * @since 2.9 */ protected void validate(final String key, final boolean value) { @@ -497,7 +520,9 @@ protected void validate(final String key, final boolean value) { /** * Default implementation does nothing. - * + * @param key The key. + * @param value The byte value. + * * @since 2.9 */ protected void validate(final String key, final byte value) { @@ -506,7 +531,9 @@ protected void validate(final String key, final byte value) { /** * Default implementation does nothing. - * + * @param key The key. + * @param value The char value. + * * @since 2.9 */ protected void validate(final String key, final char value) { @@ -515,7 +542,9 @@ protected void validate(final String key, final char value) { /** * Default implementation does nothing. - * + * @param key The key. + * @param value The double value. + * * @since 2.9 */ protected void validate(final String key, final double value) { @@ -524,16 +553,20 @@ protected void validate(final String key, final double value) { /** * Default implementation does nothing. - * + * @param key The key. + * @param value The float value. + * * @since 2.9 */ protected void validate(final String key, final float value) { // do nothing } - + /** * Default implementation does nothing. - * + * @param key The key. + * @param value The integer value. + * * @since 2.9 */ protected void validate(final String key, final int value) { @@ -542,16 +575,20 @@ protected void validate(final String key, final int value) { /** * Default implementation does nothing. - * + * @param key The key. + * @param value The long value. + * * @since 2.9 */ protected void validate(final String key, final long value) { // do nothing } - + /** * Default implementation does nothing. - * + * @param key The key. + * @param value The Object value. + * * @since 2.9 */ protected void validate(final String key, final Object value) { @@ -560,7 +597,9 @@ protected void validate(final String key, final Object value) { /** * Default implementation does nothing. - * + * @param key The key. + * @param value The short value. + * * @since 2.9 */ protected void validate(final String key, final short value) { @@ -569,7 +608,9 @@ protected void validate(final String key, final short value) { /** * Default implementation does nothing. - * + * @param key The key. + * @param value The string value. + * * @since 2.9 */ protected void validate(final String key, final String value) { @@ -578,13 +619,14 @@ protected void validate(final String key, final String value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final boolean value) { + public M with(final String candidateKey, final boolean value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -592,13 +634,14 @@ public M with(final String key, final boolean value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final byte value) { + public M with(final String candidateKey, final byte value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -606,13 +649,14 @@ public M with(final String key, final byte value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final char value) { + public M with(final String candidateKey, final char value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -621,13 +665,14 @@ public M with(final String key, final char value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final double value) { + public M with(final String candidateKey, final double value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -635,13 +680,14 @@ public M with(final String key, final double value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final float value) { + public M with(final String candidateKey, final float value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -649,13 +695,14 @@ public M with(final String key, final float value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final int value) { + public M with(final String candidateKey, final int value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -663,13 +710,14 @@ public M with(final String key, final int value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final long value) { + public M with(final String candidateKey, final long value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -677,13 +725,14 @@ public M with(final String key, final long value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final Object value) { + public M with(final String candidateKey, final Object value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -691,13 +740,14 @@ public M with(final String key, final Object value) { /** * Adds an item to the data Map. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return this object * @since 2.9 */ @SuppressWarnings("unchecked") - public M with(final String key, final short value) { + public M with(final String candidateKey, final short value) { + final String key = toKey(candidateKey); validate(key, value); data.putValue(key, value); return (M) this; @@ -705,12 +755,13 @@ public M with(final String key, final short value) { /** * Adds an item to the data Map in fluent style. - * @param key The name of the data item. + * @param candidateKey The name of the data item. * @param value The value of the data item. * @return {@code this} */ @SuppressWarnings("unchecked") - public M with(final String key, final String value) { + public M with(final String candidateKey, final String value) { + final String key = toKey(candidateKey); put(key, value); return (M) this; } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessageJsonFormatter.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessageJsonFormatter.java new file mode 100644 index 00000000000..89b840a34ed --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessageJsonFormatter.java @@ -0,0 +1,420 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.util.IndexedStringMap; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.apache.logging.log4j.util.StringBuilders; + +/** + * The default JSON formatter for {@link MapMessage}s. + *

+ * The following types have specific handlers: + *

+ *

    + *
  • {@link Map} + *
  • {@link Collection} ({@link List}, {@link Set}, etc.) + *
  • {@link Number} ({@link BigDecimal}, {@link Double}, {@link Long}, {@link Byte}, etc.) + *
  • {@link Boolean} + *
  • {@link StringBuilderFormattable} + *
  • char/boolean/byte/short/int/long/float/double/Object arrays + *
  • {@link String} + *
+ *

+ * It supports nesting up to a maximum depth of 8, which is set by the + * {@value LoggingSystemProperties#LOGGER_MAP_MESSAGE_JSON_FORMATTER_MAX_DEPTH} property. + */ +enum MapMessageJsonFormatter {; + + public static final int MAX_DEPTH = readMaxDepth(); + + private static final char DQUOTE = '"'; + + private static final char RBRACE = ']'; + + private static final char LBRACE = '['; + + private static final char COMMA = ','; + + private static final char RCURLY = '}'; + + private static final char LCURLY = '{'; + + private static final char COLON = ':'; + + private static int readMaxDepth() { + final int maxDepth = PropertiesUtil + .getProperties() + .getIntegerProperty(LoggingSystemProperties.LOGGER_MAP_MESSAGE_JSON_FORMATTER_MAX_DEPTH, 8); + if (maxDepth < 0) { + throw new IllegalArgumentException( + "was expecting a positive maxDepth, found: " + maxDepth); + } + return maxDepth; + } + + static void format(final StringBuilder sb, final Object object) { + format(sb, object, 0); + } + + private static void format( + final StringBuilder sb, + final Object object, + final int depth) { + + if (depth >= MAX_DEPTH) { + throw new IllegalArgumentException("maxDepth has been exceeded"); + } + + // null + if (object == null) { + sb.append("null"); + } + + // map + else if (object instanceof IndexedStringMap) { + final IndexedStringMap map = (IndexedStringMap) object; + formatIndexedStringMap(sb, map, depth); + } else if (object instanceof Map) { + @SuppressWarnings("unchecked") + final Map map = (Map) object; + formatMap(sb, map, depth); + } + + // list & collection + else if (object instanceof List) { + @SuppressWarnings("unchecked") + final List list = (List) object; + formatList(sb, list, depth); + } else if (object instanceof Collection) { + @SuppressWarnings("unchecked") + final Collection collection = (Collection) object; + formatCollection(sb, collection, depth); + } + + // number & boolean + else if (object instanceof Number) { + final Number number = (Number) object; + formatNumber(sb, number); + } else if (object instanceof Boolean) { + final boolean booleanValue = (boolean) object; + formatBoolean(sb, booleanValue); + } + + // formattable + else if (object instanceof StringBuilderFormattable) { + final StringBuilderFormattable formattable = (StringBuilderFormattable) object; + formatFormattable(sb, formattable); + } + + // arrays + else if (object instanceof char[]) { + final char[] charValues = (char[]) object; + formatCharArray(sb, charValues); + } else if (object instanceof boolean[]) { + final boolean[] booleanValues = (boolean[]) object; + formatBooleanArray(sb, booleanValues); + } else if (object instanceof byte[]) { + final byte[] byteValues = (byte[]) object; + formatByteArray(sb, byteValues); + } else if (object instanceof short[]) { + final short[] shortValues = (short[]) object; + formatShortArray(sb, shortValues); + } else if (object instanceof int[]) { + final int[] intValues = (int[]) object; + formatIntArray(sb, intValues); + } else if (object instanceof long[]) { + final long[] longValues = (long[]) object; + formatLongArray(sb, longValues); + } else if (object instanceof float[]) { + final float[] floatValues = (float[]) object; + formatFloatArray(sb, floatValues); + } else if (object instanceof double[]) { + final double[] doubleValues = (double[]) object; + formatDoubleArray(sb, doubleValues); + } else if (object instanceof Object[]) { + final Object[] objectValues = (Object[]) object; + formatObjectArray(sb, objectValues, depth); + } + + // string + else { + formatString(sb, object); + } + + } + + private static void formatIndexedStringMap( + final StringBuilder sb, + final IndexedStringMap map, + final int depth) { + sb.append(LCURLY); + final int nextDepth = depth + 1; + for (int entryIndex = 0; entryIndex < map.size(); entryIndex++) { + final String key = map.getKeyAt(entryIndex); + final Object value = map.getValueAt(entryIndex); + if (entryIndex > 0) { + sb.append(COMMA); + } + sb.append(DQUOTE); + final int keyStartIndex = sb.length(); + sb.append(key); + StringBuilders.escapeJson(sb, keyStartIndex); + sb.append(DQUOTE).append(COLON); + format(sb, value, nextDepth); + } + sb.append(RCURLY); + } + + private static void formatMap( + final StringBuilder sb, + final Map map, + final int depth) { + sb.append(LCURLY); + final int nextDepth = depth + 1; + final boolean[] firstEntry = {true}; + map.forEach((final Object key, final Object value) -> { + if (key == null) { + throw new IllegalArgumentException("null keys are not allowed"); + } + if (firstEntry[0]) { + firstEntry[0] = false; + } else { + sb.append(COMMA); + } + sb.append(DQUOTE); + final String keyString = String.valueOf(key); + final int keyStartIndex = sb.length(); + sb.append(keyString); + StringBuilders.escapeJson(sb, keyStartIndex); + sb.append(DQUOTE).append(COLON); + format(sb, value, nextDepth); + }); + sb.append(RCURLY); + } + + private static void formatList( + final StringBuilder sb, + final List items, + final int depth) { + sb.append(LBRACE); + final int nextDepth = depth + 1; + for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final Object item = items.get(itemIndex); + format(sb, item, nextDepth); + } + sb.append(RBRACE); + } + + private static void formatCollection( + final StringBuilder sb, + final Collection items, + final int depth) { + sb.append(LBRACE); + final int nextDepth = depth + 1; + final boolean[] firstItem = {true}; + items.forEach((final Object item) -> { + if (firstItem[0]) { + firstItem[0] = false; + } else { + sb.append(COMMA); + } + format(sb, item, nextDepth); + }); + sb.append(RBRACE); + } + + private static void formatNumber(final StringBuilder sb, final Number number) { + if (number instanceof BigDecimal) { + final BigDecimal decimalNumber = (BigDecimal) number; + sb.append(decimalNumber.toString()); + } else if (number instanceof Double) { + final double doubleNumber = (Double) number; + sb.append(doubleNumber); + } else if (number instanceof Float) { + final float floatNumber = (float) number; + sb.append(floatNumber); + } else if (number instanceof Byte || + number instanceof Short || + number instanceof Integer || + number instanceof Long) { + final long longNumber = number.longValue(); + sb.append(longNumber); + } else { + final long longNumber = number.longValue(); + final double doubleValue = number.doubleValue(); + if (Double.compare(longNumber, doubleValue) == 0) { + sb.append(longNumber); + } else { + sb.append(doubleValue); + } + } + } + + private static void formatBoolean(final StringBuilder sb, final boolean booleanValue) { + sb.append(booleanValue); + } + + private static void formatFormattable( + final StringBuilder sb, + final StringBuilderFormattable formattable) { + sb.append(DQUOTE); + final int startIndex = sb.length(); + formattable.formatTo(sb); + StringBuilders.escapeJson(sb, startIndex); + sb.append(DQUOTE); + } + + private static void formatCharArray(final StringBuilder sb, final char[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final char item = items[itemIndex]; + sb.append(DQUOTE); + final int startIndex = sb.length(); + sb.append(item); + StringBuilders.escapeJson(sb, startIndex); + sb.append(DQUOTE); + } + sb.append(RBRACE); + } + + private static void formatBooleanArray(final StringBuilder sb, final boolean[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final boolean item = items[itemIndex]; + sb.append(item); + } + sb.append(RBRACE); + } + + private static void formatByteArray(final StringBuilder sb, final byte[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final byte item = items[itemIndex]; + sb.append(item); + } + sb.append(RBRACE); + } + + private static void formatShortArray(final StringBuilder sb, final short[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final short item = items[itemIndex]; + sb.append(item); + } + sb.append(RBRACE); + } + + private static void formatIntArray(final StringBuilder sb, final int[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final int item = items[itemIndex]; + sb.append(item); + } + sb.append(RBRACE); + } + + private static void formatLongArray(final StringBuilder sb, final long[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final long item = items[itemIndex]; + sb.append(item); + } + sb.append(RBRACE); + } + + private static void formatFloatArray(final StringBuilder sb, final float[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final float item = items[itemIndex]; + sb.append(item); + } + sb.append(RBRACE); + } + + private static void formatDoubleArray( + final StringBuilder sb, + final double[] items) { + sb.append(LBRACE); + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final double item = items[itemIndex]; + sb.append(item); + } + sb.append(RBRACE); + } + + private static void formatObjectArray( + final StringBuilder sb, + final Object[] items, + final int depth) { + sb.append(LBRACE); + final int nextDepth = depth + 1; + for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { + if (itemIndex > 0) { + sb.append(COMMA); + } + final Object item = items[itemIndex]; + format(sb, item, nextDepth); + } + sb.append(RBRACE); + } + + private static void formatString(final StringBuilder sb, final Object value) { + sb.append(DQUOTE); + final int startIndex = sb.length(); + final String valueString = String.valueOf(value); + sb.append(valueString); + StringBuilders.escapeJson(sb, startIndex); + sb.append(DQUOTE); + } + +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java index 009d5a62cb1..abe07ba4bc4 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java @@ -31,7 +31,9 @@ public interface MessageFactory { * a message object * @return a new message */ - Message newMessage(Object message); + default Message newMessage(final Object message) { + return new ObjectMessage(message); + } /** * Creates a new message based on a String. @@ -40,7 +42,9 @@ public interface MessageFactory { * a message String * @return a new message */ - Message newMessage(String message); + default Message newMessage(final String message) { + return new SimpleMessage(message); + } /** * Creates a new parameterized message. @@ -54,4 +58,182 @@ public interface MessageFactory { * @see StringFormatterMessageFactory */ Message newMessage(String message, Object... params); + + /** + * Creates a new message for the specified CharSequence. + * @param charSequence the (potentially mutable) CharSequence + * @return a new message for the specified CharSequence + */ + default Message newMessage(final CharSequence charSequence) { + return new SimpleMessage(charSequence); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0) { + return newMessage(message, new Object[] { p0 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1) { + return newMessage(message, new Object[] { p0, p1 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2) { + return newMessage(message, new Object[] { p0, p1, p2 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @param p3 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) { + return newMessage(message, new Object[] { p0, p1, p2, p3 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @param p3 a message parameter + * @param p4 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + return newMessage(message, new Object[] { p0, p1, p2, p3, p4 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @param p3 a message parameter + * @param p4 a message parameter + * @param p5 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { + return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @param p3 a message parameter + * @param p4 a message parameter + * @param p5 a message parameter + * @param p6 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + final Object p6) { + return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @param p3 a message parameter + * @param p4 a message parameter + * @param p5 a message parameter + * @param p6 a message parameter + * @param p7 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + final Object p6, final Object p7) { + return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @param p3 a message parameter + * @param p4 a message parameter + * @param p5 a message parameter + * @param p6 a message parameter + * @param p7 a message parameter + * @param p8 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + final Object p6, final Object p7, final Object p8) { + return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8 }); + } + + /** + * Creates a new parameterized message. + * + * @param message a message template, the kind of message template depends on the implementation. + * @param p0 a message parameter + * @param p1 a message parameter + * @param p2 a message parameter + * @param p3 a message parameter + * @param p4 a message parameter + * @param p5 a message parameter + * @param p6 a message parameter + * @param p7 a message parameter + * @param p8 a message parameter + * @param p9 a message parameter + * @return a new message + * @see ParameterizedMessageFactory + */ + default Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + final Object p6, final Object p7, final Object p8, final Object p9) { + return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 }); + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java index 33004dd8c73..f870888b179 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java @@ -20,162 +20,9 @@ * Creates messages. Implementations can provide different message format syntaxes. * * @see ParameterizedMessageFactory + * @deprecated MessageFactory now contains all interface methods. * @since 2.6 */ +@Deprecated public interface MessageFactory2 extends MessageFactory { - - /** - * Creates a new message for the specified CharSequence. - * @param charSequence the (potentially mutable) CharSequence - * @return a new message for the specified CharSequence - */ - Message newMessage(CharSequence charSequence); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @param p3 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2, Object p3); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @param p3 a message parameter - * @param p4 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @param p3 a message parameter - * @param p4 a message parameter - * @param p5 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @param p3 a message parameter - * @param p4 a message parameter - * @param p5 a message parameter - * @param p6 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @param p3 a message parameter - * @param p4 a message parameter - * @param p5 a message parameter - * @param p6 a message parameter - * @param p7 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @param p3 a message parameter - * @param p4 a message parameter - * @param p5 a message parameter - * @param p6 a message parameter - * @param p7 a message parameter - * @param p8 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8); - - /** - * Creates a new parameterized message. - * - * @param message a message template, the kind of message template depends on the implementation. - * @param p0 a message parameter - * @param p1 a message parameter - * @param p2 a message parameter - * @param p3 a message parameter - * @param p4 a message parameter - * @param p5 a message parameter - * @param p6 a message parameter - * @param p7 a message parameter - * @param p8 a message parameter - * @param p9 a message parameter - * @return a new message - * @see ParameterizedMessageFactory - */ - Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, - Object p7, Object p8, Object p9); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java index f75e68ba5a2..1fae3682ec0 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java @@ -16,16 +16,18 @@ */ package org.apache.logging.log4j.message; +import java.io.Serializable; + /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by - * extension.) - * + * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods. + * + *

*

Note to implementors

*

- * This class implements all {@link MessageFactory2} methods. + * This class implements all {@link MessageFactory} methods. *

*/ -public class MessageFormatMessageFactory extends AbstractMessageFactory { +public class MessageFormatMessageFactory implements MessageFactory, Serializable { private static final long serialVersionUID = 3584821740584192453L; /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterConsumer.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterConsumer.java new file mode 100644 index 00000000000..906b2cea793 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterConsumer.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +/** + * An operation that accepts two input arguments and returns no result. + * + *

+ * The third parameter lets callers pass in a stateful object to be modified with the key-value pairs, + * so the ParameterConsumer implementation itself can be stateless and potentially reusable. + *

+ * + * @param state data + * @see ReusableMessage + * @since 2.11 + */ +public interface ParameterConsumer { + + /** + * Performs an operation given the specified arguments. + * + * @param parameter the parameter + * @param parameterIndex Index of the parameter + * @param state The state data. + */ + void accept(Object parameter, int parameterIndex, S state); + +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterFormatter.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterFormatter.java index 5c4cc738faa..b424ee71af7 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterFormatter.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterFormatter.java @@ -16,11 +16,13 @@ */ package org.apache.logging.log4j.message; -import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; -import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; @@ -59,8 +61,10 @@ final class ParameterFormatter { private static final char DELIM_START = '{'; private static final char DELIM_STOP = '}'; private static final char ESCAPE_CHAR = '\\'; - - private static ThreadLocal threadLocalSimpleDateFormat = new ThreadLocal<>(); + private static final DateTimeFormatter FORMATTER = + DateTimeFormatter + .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .withZone(ZoneId.systemDefault()); private ParameterFormatter() { } @@ -186,7 +190,7 @@ static void formatMessage2(final StringBuilder buffer, final String messagePatte for (int i = 0; i < argCount; i++) { buffer.append(messagePattern, previous, indices[i]); previous = indices[i] + 2; - recursiveDeepToString(arguments[i], buffer, null); + recursiveDeepToString(arguments[i], buffer); } buffer.append(messagePattern, previous, messagePattern.length()); } @@ -211,7 +215,7 @@ static void formatMessage3(final StringBuilder buffer, final char[] messagePatte for (int i = 0; i < argCount; i++) { buffer.append(messagePattern, previous, indices[i]); previous = indices[i] + 2; - recursiveDeepToString(arguments[i], buffer, null); + recursiveDeepToString(arguments[i], buffer); } buffer.append(messagePattern, previous, patternLength); } @@ -363,7 +367,7 @@ private static void writeUnescapedEscapeChars(int escapeCounter, final StringBui private static void writeArgOrDelimPair(final Object[] arguments, final int argCount, final int currentArgument, final StringBuilder buffer) { if (currentArgument < argCount) { - recursiveDeepToString(arguments[currentArgument], buffer, null); + recursiveDeepToString(arguments[currentArgument], buffer); } else { writeDelimPair(buffer); } @@ -420,35 +424,54 @@ static String deepToString(final Object o) { return Byte.toString((Byte) o); } final StringBuilder str = new StringBuilder(); - recursiveDeepToString(o, str, null); + recursiveDeepToString(o, str); return str.toString(); } /** - * This method performs a deep toString of the given Object. - * Primitive arrays are converted using their respective Arrays.toString methods while - * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could - * contain themselves. + * This method performs a deep {@code toString()} of the given {@code Object}. *

- * dejaVu is used in case of those container types to prevent an endless recursion. - *

+ * Primitive arrays are converted using their respective {@code Arrays.toString()} methods, while + * special handling is implemented for container types, i.e. {@code Object[]}, {@code Map} and {@code Collection}, + * because those could contain themselves. *

- * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a - * behavior. - * They only check if the container is directly contained in itself, but not if a contained container contains the - * original one. Because of that, Arrays.toString(Object[]) isn't safe either. - * Confusing? Just read the last paragraph again and check the respective toString() implementation. - *

+ * It should be noted that neither {@code AbstractMap.toString()} nor {@code AbstractCollection.toString()} implement such a behavior. + * They only check if the container is directly contained in itself, but not if a contained container contains the original one. + * Because of that, {@code Arrays.toString(Object[])} isn't safe either. + * Confusing? Just read the last paragraph again and check the respective {@code toString()} implementation. *

- * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o) - * would produce a relatively hard-to-debug StackOverflowError. - *

+ * This means, in effect, that logging would produce a usable output even if an ordinary {@code System.out.println(o)} + * would produce a relatively hard-to-debug {@code StackOverflowError}. + * + * @param o the {@code Object} to convert into a {@code String} + * @param str the {@code StringBuilder} that {@code o} will be appended to + */ + static void recursiveDeepToString(final Object o, final StringBuilder str) { + recursiveDeepToString(o, str, null); + } + + /** + * This method performs a deep {@code toString()} of the given {@code Object}. + *

+ * Primitive arrays are converted using their respective {@code Arrays.toString()} methods, while + * special handling is implemented for container types, i.e. {@code Object[]}, {@code Map} and {@code Collection}, + * because those could contain themselves. + *

+ * {@code dejaVu} is used in case of those container types to prevent an endless recursion. + *

+ * It should be noted that neither {@code AbstractMap.toString()} nor {@code AbstractCollection.toString()} implement such a behavior. + * They only check if the container is directly contained in itself, but not if a contained container contains the original one. + * Because of that, {@code Arrays.toString(Object[])} isn't safe either. + * Confusing? Just read the last paragraph again and check the respective {@code toString()} implementation. + *

+ * This means, in effect, that logging would produce a usable output even if an ordinary {@code System.out.println(o)} + * would produce a relatively hard-to-debug {@code StackOverflowError}. * - * @param o the Object to convert into a String - * @param str the StringBuilder that o will be appended to - * @param dejaVu a list of container identities that were already used. + * @param o the {@code Object} to convert into a {@code String} + * @param str the {@code StringBuilder} that {@code o} will be appended to + * @param dejaVu a set of container objects directly or transitively containing {@code o} */ - static void recursiveDeepToString(final Object o, final StringBuilder str, final Set dejaVu) { + private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set dejaVu) { if (appendSpecialTypes(o, str)) { return; } @@ -467,21 +490,10 @@ private static boolean appendDate(final Object o, final StringBuilder str) { if (!(o instanceof Date)) { return false; } - final Date date = (Date) o; - final SimpleDateFormat format = getSimpleDateFormat(); - str.append(format.format(date)); + str.append(FORMATTER.format(((Date) o).toInstant())); return true; } - private static SimpleDateFormat getSimpleDateFormat() { - SimpleDateFormat result = threadLocalSimpleDateFormat.get(); - if (result == null) { - result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - threadLocalSimpleDateFormat.set(result); - } - return result; - } - /** * Returns {@code true} if the specified object is an array, a Map or a Collection. */ @@ -489,8 +501,10 @@ private static boolean isMaybeRecursive(final Object o) { return o.getClass().isArray() || o instanceof Map || o instanceof Collection; } - private static void appendPotentiallyRecursiveValue(final Object o, final StringBuilder str, - final Set dejaVu) { + private static void appendPotentiallyRecursiveValue( + final Object o, + final StringBuilder str, + final Set dejaVu) { final Class oClass = o.getClass(); if (oClass.isArray()) { appendArray(o, str, dejaVu, oClass); @@ -498,10 +512,15 @@ private static void appendPotentiallyRecursiveValue(final Object o, final String appendMap(o, str, dejaVu); } else if (o instanceof Collection) { appendCollection(o, str, dejaVu); + } else { + throw new IllegalArgumentException("was expecting a container, found " + oClass); } } - private static void appendArray(final Object o, final StringBuilder str, Set dejaVu, + private static void appendArray( + final Object o, + final StringBuilder str, + final Set dejaVu, final Class oClass) { if (oClass == byte[].class) { str.append(Arrays.toString((byte[]) o)); @@ -520,15 +539,13 @@ private static void appendArray(final Object o, final StringBuilder str, Set(); - } // special handling of container Object[] - final String id = identityToString(o); - if (dejaVu.contains(id)) { + final Set effectiveDejaVu = getOrCreateDejaVu(dejaVu); + final boolean seen = !effectiveDejaVu.add(o); + if (seen) { + final String id = identityToString(o); str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); } else { - dejaVu.add(id); final Object[] oArray = (Object[]) o; str.append('['); boolean first = true; @@ -538,24 +555,26 @@ private static void appendArray(final Object o, final StringBuilder str, Set(dejaVu)); + recursiveDeepToString(current, str, cloneDejaVu(effectiveDejaVu)); } str.append(']'); } - //str.append(Arrays.deepToString((Object[]) o)); } } - private static void appendMap(final Object o, final StringBuilder str, Set dejaVu) { - // special handling of container Map - if (dejaVu == null) { - dejaVu = new HashSet<>(); - } - final String id = identityToString(o); - if (dejaVu.contains(id)) { + /** + * Specialized handler for {@link Map}s. + */ + private static void appendMap( + final Object o, + final StringBuilder str, + final Set dejaVu) { + final Set effectiveDejaVu = getOrCreateDejaVu(dejaVu); + final boolean seen = !effectiveDejaVu.add(o); + if (seen) { + final String id = identityToString(o); str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); } else { - dejaVu.add(id); final Map oMap = (Map) o; str.append('{'); boolean isFirst = true; @@ -568,24 +587,27 @@ private static void appendMap(final Object o, final StringBuilder str, Set(dejaVu)); + recursiveDeepToString(key, str, cloneDejaVu(effectiveDejaVu)); str.append('='); - recursiveDeepToString(value, str, new HashSet<>(dejaVu)); + recursiveDeepToString(value, str, cloneDejaVu(effectiveDejaVu)); } str.append('}'); } } - private static void appendCollection(final Object o, final StringBuilder str, Set dejaVu) { - // special handling of container Collection - if (dejaVu == null) { - dejaVu = new HashSet<>(); - } - final String id = identityToString(o); - if (dejaVu.contains(id)) { + /** + * Specialized handler for {@link Collection}s. + */ + private static void appendCollection( + final Object o, + final StringBuilder str, + final Set dejaVu) { + final Set effectiveDejaVu = getOrCreateDejaVu(dejaVu); + final boolean seen = !effectiveDejaVu.add(o); + if (seen) { + final String id = identityToString(o); str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); } else { - dejaVu.add(id); final Collection oCol = (Collection) o; str.append('['); boolean isFirst = true; @@ -595,12 +617,28 @@ private static void appendCollection(final Object o, final StringBuilder str, Se } else { str.append(", "); } - recursiveDeepToString(anOCol, str, new HashSet<>(dejaVu)); + recursiveDeepToString(anOCol, str, cloneDejaVu(effectiveDejaVu)); } str.append(']'); } } + private static Set getOrCreateDejaVu(final Set dejaVu) { + return dejaVu == null + ? createDejaVu() + : dejaVu; + } + + private static Set createDejaVu() { + return Collections.newSetFromMap(new IdentityHashMap<>()); + } + + private static Set cloneDejaVu(final Set dejaVu) { + final Set clonedDejaVu = createDejaVu(); + clonedDejaVu.addAll(dejaVu); + return clonedDejaVu; + } + private static void tryObjectToString(final Object o, final StringBuilder str) { // it's just some other Object, we can only use toString(). try { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterVisitable.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterVisitable.java new file mode 100644 index 00000000000..d196e31e641 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterVisitable.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +import org.apache.logging.log4j.util.PerformanceSensitive; + +/** + * Allows message parameters to be iterated over without any allocation + * or memory copies. + * + * @since 2.11 + */ +@PerformanceSensitive("allocation") +public interface ParameterVisitable { + + /** + * Performs the given action for each parameter until all values + * have been processed or the action throws an exception. + *

+ * The second parameter lets callers pass in a stateful object to be modified with the key-value pairs, + * so the TriConsumer implementation itself can be stateless and potentially reusable. + *

+ * + * @param action The action to be performed for each key-value pair in this collection + * @param state the object to be passed as the third parameter to each invocation on the + * specified ParameterConsumer. + * @param type of the third parameter + * @since 2.11 + */ + void forEachParameter(ParameterConsumer action, S state); + +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java index c4a1bbfadb2..9ab84a47f13 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java @@ -69,7 +69,7 @@ public class ParameterizedMessage implements Message, StringBuilderFormattable { private static final int HASHVAL = 31; // storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay - private static ThreadLocal threadLocalStringBuilder = new ThreadLocal<>(); + private static final ThreadLocal threadLocalStringBuilder = new ThreadLocal<>(); private String messagePattern; private transient Object[] argArray; @@ -79,21 +79,6 @@ public class ParameterizedMessage implements Message, StringBuilderFormattable { private int[] indices; private int usedCount; - /** - * Creates a parameterized message. - * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders - * where parameters should be substituted. - * @param arguments The arguments for substitution. - * @param throwable A Throwable. - * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead - */ - @Deprecated - public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) { - this.argArray = arguments; - this.throwable = throwable; - init(messagePattern); - } - /** * Creates a parameterized message. * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders @@ -255,12 +240,8 @@ public boolean equals(final Object o) { if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) { return false; } - if (!Arrays.equals(this.argArray, that.argArray)) { - return false; - } + return Arrays.equals(this.argArray, that.argArray); //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false; - - return true; } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java index 5878cac9ad2..bc9b16b0192 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java @@ -16,9 +16,10 @@ */ package org.apache.logging.log4j.message; +import java.io.Serializable; + /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by - * extension.) + * Creates {@link FormattedMessage} instances for {@link MessageFactory}. *

* Enables the use of {} parameter markers in message strings. *

@@ -28,13 +29,14 @@ *

* This class is immutable. *

- * + * + *

*

Note to implementors

*

- * This class implements all {@link MessageFactory2} methods. + * This class implements all {@link MessageFactory} methods. *

*/ -public final class ParameterizedMessageFactory extends AbstractMessageFactory { +public final class ParameterizedMessageFactory implements MessageFactory, Serializable { /** * Instance of ParameterizedMessageFactory. */ diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java index fdf397bc503..0d9375d4b98 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java @@ -18,9 +18,10 @@ import org.apache.logging.log4j.status.StatusLogger; +import java.io.Serializable; + /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by - * extension.) + * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods. *

* Creates {@link SimpleMessage} objects that do not retain a reference to the parameter object. *

@@ -31,13 +32,14 @@ *

* This class is immutable. *

+ *

*

Note to implementors

*

- * This class does not implement any {@link MessageFactory2} methods and lets the superclass funnel those calls + * This class does not implement any {@link MessageFactory} methods and lets the superclass funnel those calls * through {@link #newMessage(String, Object...)}. *

*/ -public final class ParameterizedNoReferenceMessageFactory extends AbstractMessageFactory { +public final class ParameterizedNoReferenceMessageFactory implements MessageFactory, Serializable { private static final long serialVersionUID = 5027639245636870500L; /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java index 82e40acb100..f1ab320b0a4 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java @@ -35,7 +35,7 @@ public interface ReusableMessage extends Message, StringBuilderFormattable { * Returns the parameter array that was used to initialize this reusable message and replaces it with the specified * array. The returned parameter array will no longer be modified by this reusable message. The specified array is * now "owned" by this reusable message and can be modified if necessary for the next log event. - *

+ *

* ReusableMessages that have no parameters return the specified array. *

* This method is used by asynchronous loggers to pass the parameter array to a background thread without diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java index d75ca01f9a0..d7d7b8cf564 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java @@ -30,7 +30,7 @@ * @since 2.6 */ @PerformanceSensitive("allocation") -public final class ReusableMessageFactory implements MessageFactory2, Serializable { +public final class ReusableMessageFactory implements MessageFactory, Serializable { /** * Instance of ReusableMessageFactory.. @@ -38,9 +38,9 @@ public final class ReusableMessageFactory implements MessageFactory2, Serializab public static final ReusableMessageFactory INSTANCE = new ReusableMessageFactory(); private static final long serialVersionUID = -8970940216592525651L; - private static ThreadLocal threadLocalParameterized = new ThreadLocal<>(); - private static ThreadLocal threadLocalSimpleMessage = new ThreadLocal<>(); - private static ThreadLocal threadLocalObjectMessage = new ThreadLocal<>(); + private static final ThreadLocal threadLocalParameterized = new ThreadLocal<>(); + private static final ThreadLocal threadLocalSimpleMessage = new ThreadLocal<>(); + private static final ThreadLocal threadLocalObjectMessage = new ThreadLocal<>(); /** * Constructs a message factory. @@ -77,15 +77,15 @@ private static ReusableObjectMessage getObject() { } /** - * Switches the {@code reserved} flag off if the specified message is a ReusableParameterizedMessage, - * otherwise does nothing. This flag is used internally to verify that a reusable message is no longer in use and + * Invokes {@link Clearable#clear()} when possible. + * This flag is used internally to verify that a reusable message is no longer in use and * can be reused. * @param message the message to make available again * @since 2.7 */ public static void release(final Message message) { // LOG4J2-1583 - if (message instanceof ReusableParameterizedMessage) { - ((ReusableParameterizedMessage) message).reserved = false; + if (message instanceof Clearable) { + ((Clearable) message).clear(); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java index c4ffa22c105..9144223b213 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableObjectMessage.java @@ -24,7 +24,7 @@ * @since 2.6 */ @PerformanceSensitive("allocation") -public class ReusableObjectMessage implements ReusableMessage { +public class ReusableObjectMessage implements ReusableMessage, ParameterVisitable, Clearable { private static final long serialVersionUID = 6922476812535519960L; private transient Object obj; @@ -55,7 +55,7 @@ public void formatTo(final StringBuilder buffer) { */ @Override public String getFormat() { - return getFormattedMessage(); + return obj instanceof String ? (String) obj : null; } /** @@ -94,26 +94,45 @@ public Throwable getThrowable() { } /** - * This message does not have any parameters, so this method returns the specified array. + * This message has exactly one parameter (the object), so returns it as the first parameter in the array. * @param emptyReplacement the parameter array to return * @return the specified array */ @Override public Object[] swapParameters(final Object[] emptyReplacement) { + // it's unlikely that emptyReplacement is of length 0, but if it is, + // go ahead and allocate the memory now; + // this saves an allocation in the future when this buffer is re-used + if (emptyReplacement.length == 0) { + final Object[] params = new Object[10]; // Default reusable parameter buffer size + params[0] = obj; + return params; + } + emptyReplacement[0] = obj; return emptyReplacement; } /** - * This message does not have any parameters so this method always returns zero. - * @return 0 (zero) + * This message has exactly one parameter (the object), so always returns one. + * @return 1 */ @Override public short getParameterCount() { - return 0; + return 1; + } + + @Override + public void forEachParameter(final ParameterConsumer action, final S state) { + action.accept(obj, 0, state); } @Override public Message memento() { return new ObjectMessage(obj); } + + @Override + public void clear() { + obj = null; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java index daf299482c8..be3db8f9a6d 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java @@ -30,7 +30,7 @@ * @since 2.6 */ @PerformanceSensitive("allocation") -public class ReusableParameterizedMessage implements ReusableMessage { +public class ReusableParameterizedMessage implements ReusableMessage, ParameterVisitable, Clearable { private static final int MIN_BUILDER_SIZE = 512; private static final int MAX_PARMS = 10; @@ -73,6 +73,10 @@ public Object[] swapParameters(final Object[] emptyReplacement) { if (argCount <= emptyReplacement.length) { // copy params into the specified replacement array and return that System.arraycopy(params, 0, emptyReplacement, 0, argCount); + // Do not retain references to objects in the reusable params array. + for (int i = 0; i < argCount; i++) { + params[i] = null; + } result = emptyReplacement; } else { // replacement array is too small for current content and future content: discard it @@ -104,6 +108,14 @@ public short getParameterCount() { return (short) argCount; } + @Override + public void forEachParameter(final ParameterConsumer action, final S state) { + final Object[] parameters = getParams(); + for (short i = 0; i < argCount; i++) { + action.accept(parameters[i], i, state); + } + } + @Override public Message memento() { return new ParameterizedMessage(messagePattern, getTrimmedParams()); @@ -135,26 +147,26 @@ private void initThrowable(final Object[] params, final int argCount, final int } } - ReusableParameterizedMessage set(final String messagePattern, final Object... arguments) { + protected ReusableParameterizedMessage set(final String messagePattern, final Object... arguments) { init(messagePattern, arguments == null ? 0 : arguments.length, arguments); varargs = arguments; return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0) { + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0) { params[0] = p0; init(messagePattern, 1, params); return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1) { + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1) { params[0] = p0; params[1] = p1; init(messagePattern, 2, params); return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2) { + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2) { params[0] = p0; params[1] = p1; params[2] = p2; @@ -162,7 +174,7 @@ ReusableParameterizedMessage set(final String messagePattern, final Object p0, f return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3) { + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3) { params[0] = p0; params[1] = p1; params[2] = p2; @@ -171,7 +183,7 @@ ReusableParameterizedMessage set(final String messagePattern, final Object p0, f return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { params[0] = p0; params[1] = p1; params[2] = p2; @@ -181,7 +193,7 @@ ReusableParameterizedMessage set(final String messagePattern, final Object p0, f return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { params[0] = p0; params[1] = p1; params[2] = p2; @@ -192,7 +204,7 @@ ReusableParameterizedMessage set(final String messagePattern, final Object p0, f return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) { params[0] = p0; params[1] = p1; @@ -205,7 +217,7 @@ ReusableParameterizedMessage set(final String messagePattern, final Object p0, f return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7) { params[0] = p0; params[1] = p1; @@ -219,7 +231,7 @@ ReusableParameterizedMessage set(final String messagePattern, final Object p0, f return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) { params[0] = p0; params[1] = p1; @@ -234,7 +246,7 @@ ReusableParameterizedMessage set(final String messagePattern, final Object p0, f return this; } - ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, + protected ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) { params[0] = p0; params[1] = p1; @@ -333,4 +345,14 @@ public String toString() { return "ReusableParameterizedMessage[messagePattern=" + getFormat() + ", stringArgs=" + Arrays.toString(getParameters()) + ", throwable=" + getThrowable() + ']'; } + + @Override + public void clear() { // LOG4J2-1583 + // This method does not clear parameter values, those are expected to be swapped to a + // reusable message, which is responsible for clearing references. + reserved = false; + varargs = null; + messagePattern = null; + throwable = null; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java index 905b694d30e..4ff32e98939 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableSimpleMessage.java @@ -23,9 +23,9 @@ * @since 2.6 */ @PerformanceSensitive("allocation") -public class ReusableSimpleMessage implements ReusableMessage, CharSequence { +public class ReusableSimpleMessage implements ReusableMessage, CharSequence, ParameterVisitable, Clearable { private static final long serialVersionUID = -9199974506498249809L; - private static Object[] EMPTY_PARAMS = new Object[0]; + private static final Object[] EMPTY_PARAMS = new Object[0]; private CharSequence charSequence; public void set(final String message) { @@ -43,7 +43,7 @@ public String getFormattedMessage() { @Override public String getFormat() { - return getFormattedMessage(); + return charSequence instanceof String ? (String) charSequence : null; } @Override @@ -80,6 +80,10 @@ public short getParameterCount() { return 0; } + @Override + public void forEachParameter(final ParameterConsumer action, final S state) { + } + @Override public Message memento() { return new SimpleMessage(charSequence); @@ -101,5 +105,9 @@ public char charAt(final int index) { public CharSequence subSequence(final int start, final int end) { return charSequence.subSequence(start, end); } -} + @Override + public void clear() { + charSequence = null; + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java index 8a2fc7219ea..d33f3b9bfdc 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessage.java @@ -16,10 +16,11 @@ */ package org.apache.logging.log4j.message; +import org.apache.logging.log4j.util.StringBuilderFormattable; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import org.apache.logging.log4j.util.StringBuilderFormattable; /** * The simplest possible implementation of Message. It just returns the String given as the constructor argument. @@ -75,7 +76,7 @@ public void formatTo(final StringBuilder buffer) { */ @Override public String getFormat() { - return getFormattedMessage(); + return message; } /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessageFactory.java index e85b8c0f219..aa1c5f516eb 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessageFactory.java @@ -16,9 +16,10 @@ */ package org.apache.logging.log4j.message; +import java.io.Serializable; + /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by - * extension.) + * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods. *

* This uses is the simplest possible implementation of {@link Message}, the where you give the message to the * constructor argument as a String. @@ -29,15 +30,16 @@ *

* This class is immutable. *

- * + * + *

*

Note to implementors

*

- * This class implements all {@link MessageFactory2} methods. + * This class implements all {@link MessageFactory} methods. *

* * @since 2.5 */ -public final class SimpleMessageFactory extends AbstractMessageFactory { +public final class SimpleMessageFactory implements MessageFactory, Serializable { /** * Instance of StringFormatterMessageFactory. diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java index a99e2efae9b..dbc0daaf826 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java @@ -28,7 +28,8 @@ /** * Handles messages that consist of a format string conforming to {@link java.util.Formatter}. - * + * + *

*

Note to implementors

*

* This class implements the unrolled args API even though StringFormattedMessage does not. This leaves the room for @@ -112,6 +113,10 @@ public Object[] getParameters() { } protected String formatMessage(final String msgPattern, final Object... args) { + if (args != null && args.length == 0) { + // Avoids some exceptions for LOG4J2-3458 + return msgPattern; + } try { return String.format(locale, msgPattern, args); } catch (final IllegalFormatException ife) { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java index b6554d54f4e..00f4e0949b9 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java @@ -16,9 +16,10 @@ */ package org.apache.logging.log4j.message; +import java.io.Serializable; + /** - * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by - * extension.) + * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods. *

* Enables the use of {@link java.util.Formatter} strings in message strings. *

@@ -28,12 +29,13 @@ *

* This class is immutable. *

+ *

*

Note to implementors

*

- * This class implements all {@link MessageFactory2} methods. + * This class implements all {@link MessageFactory} methods. *

*/ -public final class StringFormatterMessageFactory extends AbstractMessageFactory { +public final class StringFormatterMessageFactory implements MessageFactory, Serializable { /** * Instance of StringFormatterMessageFactory. diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringMapMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringMapMessage.java index 9d05ce05b3c..c175dd13def 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringMapMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringMapMessage.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.message; import java.util.Map; @@ -23,7 +22,7 @@ /** * A {@link StringMapMessage} typed to {@link String}-only values. This is like the MapMessage class before 2.9. - * + * * @since 2.9 */ @PerformanceSensitive("allocation") @@ -41,7 +40,7 @@ public StringMapMessage() { /** * Constructs a new instance. - * + * * @param initialCapacity * the initial capacity. */ @@ -51,7 +50,7 @@ public StringMapMessage(final int initialCapacity) { /** * Constructs a new instance based on an existing Map. - * + * * @param map * The Map. */ diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java index e58aed37450..89ebd92f734 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java @@ -29,9 +29,9 @@ public class StructuredDataCollectionMessage implements StringBuilderFormattable MessageCollectionMessage { private static final long serialVersionUID = 5725337076388822924L; - private List structuredDataMessageList; + private final List structuredDataMessageList; - public StructuredDataCollectionMessage(List messages) { + public StructuredDataCollectionMessage(final List messages) { this.structuredDataMessageList = messages; } @@ -42,15 +42,15 @@ public Iterator iterator() { @Override public String getFormattedMessage() { - StringBuilder sb = new StringBuilder(); + final StringBuilder sb = new StringBuilder(); formatTo(sb); return sb.toString(); } @Override public String getFormat() { - StringBuilder sb = new StringBuilder(); - for (StructuredDataMessage msg : structuredDataMessageList) { + final StringBuilder sb = new StringBuilder(); + for (final StructuredDataMessage msg : structuredDataMessageList) { if (msg.getFormat() != null) { if (sb.length() > 0) { sb.append(", "); @@ -62,27 +62,27 @@ public String getFormat() { } @Override - public void formatTo(StringBuilder buffer) { - for (StructuredDataMessage msg : structuredDataMessageList) { + public void formatTo(final StringBuilder buffer) { + for (final StructuredDataMessage msg : structuredDataMessageList) { msg.formatTo(buffer); } } @Override public Object[] getParameters() { - List objectList = new ArrayList<>(); + final List objectList = new ArrayList<>(); int count = 0; - for (StructuredDataMessage msg : structuredDataMessageList) { - Object[] objects = msg.getParameters(); + for (final StructuredDataMessage msg : structuredDataMessageList) { + final Object[] objects = msg.getParameters(); if (objects != null) { objectList.add(objects); count += objects.length; } } - Object[] objects = new Object[count]; + final Object[] objects = new Object[count]; int index = 0; - for (Object[] objs : objectList) { - for (Object obj : objs) { + for (final Object[] objs : objectList) { + for (final Object obj : objs) { objects[index++] = obj; } } @@ -91,8 +91,8 @@ public Object[] getParameters() { @Override public Throwable getThrowable() { - for (StructuredDataMessage msg : structuredDataMessageList) { - Throwable t = msg.getThrowable(); + for (final StructuredDataMessage msg : structuredDataMessageList) { + final Throwable t = msg.getThrowable(); if (t != null) { return t; } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataId.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataId.java index 232f156d7bc..4790b36eb9f 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataId.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataId.java @@ -1,264 +1,312 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.message; - -import java.io.Serializable; - -import org.apache.logging.log4j.util.StringBuilderFormattable; -import org.apache.logging.log4j.util.Strings; - -/** - * The StructuredData identifier. - */ -public class StructuredDataId implements Serializable, StringBuilderFormattable { - - /** - * RFC 5424 Time Quality. - */ - public static final StructuredDataId TIME_QUALITY = new StructuredDataId("timeQuality", null, new String[] { - "tzKnown", "isSynced", "syncAccuracy"}); - - /** - * RFC 5424 Origin. - */ - public static final StructuredDataId ORIGIN = new StructuredDataId("origin", null, new String[] {"ip", - "enterpriseId", "software", "swVersion"}); - - /** - * RFC 5424 Meta. - */ - public static final StructuredDataId META = new StructuredDataId("meta", null, new String[] {"sequenceId", - "sysUpTime", "language"}); - - /** - * Reserved enterprise number. - */ - public static final int RESERVED = -1; - - private static final long serialVersionUID = 9031746276396249990L; - private static final int MAX_LENGTH = 32; - private static final String AT_SIGN = "@"; - - private final String name; - private final int enterpriseNumber; - private final String[] required; - private final String[] optional; - - /** - * Creates a StructuredDataId based on the name. - * @param name The Structured Data Element name (maximum length is 32) - * @since 2.9 - */ - public StructuredDataId(final String name) { - this(name, null, null, MAX_LENGTH); - } - - /** - * Creates a StructuredDataId based on the name. - * @param name The Structured Data Element name. - * @param maxLength The maximum length of the name. - * @since 2.9 - */ - public StructuredDataId(final String name, final int maxLength) { - this(name, null, null, maxLength); - } - - /** - * - * @param name - * @param required - * @param optional - */ - public StructuredDataId(final String name, final String[] required, final String[] optional) { - this(name, required, optional, MAX_LENGTH); - } - - /** - * A Constructor that helps conformance to RFC 5424. - * - * @param name The name portion of the id. - * @param required The list of keys that are required for this id. - * @param optional The list of keys that are optional for this id. - * @since 2.9 - */ - public StructuredDataId(final String name, final String[] required, final String[] optional, - final int maxLength) { - int index = -1; - if (name != null) { - if (maxLength > 0 && name.length() > MAX_LENGTH) { - throw new IllegalArgumentException(String.format("Length of id %s exceeds maximum of %d characters", - name, maxLength)); - } - index = name.indexOf(AT_SIGN); - } - - if (index > 0) { - this.name = name.substring(0, index); - this.enterpriseNumber = Integer.parseInt(name.substring(index + 1)); - } else { - this.name = name; - this.enterpriseNumber = RESERVED; - } - this.required = required; - this.optional = optional; - } - - /** - * A Constructor that helps conformance to RFC 5424. - * - * @param name The name portion of the id. - * @param enterpriseNumber The enterprise number. - * @param required The list of keys that are required for this id. - * @param optional The list of keys that are optional for this id. - */ - public StructuredDataId(final String name, final int enterpriseNumber, final String[] required, - final String[] optional) { - this(name, enterpriseNumber, required, optional, MAX_LENGTH); - } - - /** - * A Constructor that helps conformance to RFC 5424. - * - * @param name The name portion of the id. - * @param enterpriseNumber The enterprise number. - * @param required The list of keys that are required for this id. - * @param optional The list of keys that are optional for this id. - * @param maxLength The maximum length of the StructuredData Id key. - * @since 2.9 - */ - public StructuredDataId(final String name, final int enterpriseNumber, final String[] required, - final String[] optional, final int maxLength) { - if (name == null) { - throw new IllegalArgumentException("No structured id name was supplied"); - } - if (name.contains(AT_SIGN)) { - throw new IllegalArgumentException("Structured id name cannot contain an " + Strings.quote(AT_SIGN)); - } - if (enterpriseNumber <= 0) { - throw new IllegalArgumentException("No enterprise number was supplied"); - } - this.name = name; - this.enterpriseNumber = enterpriseNumber; - final String id = name + AT_SIGN + enterpriseNumber; - if (maxLength > 0 && id.length() > maxLength) { - throw new IllegalArgumentException("Length of id exceeds maximum of " + maxLength + " characters: " + id); - } - this.required = required; - this.optional = optional; - } - - /** - * Creates an id using another id to supply default values. - * - * @param id The original StructuredDataId. - * @return the new StructuredDataId. - */ - public StructuredDataId makeId(final StructuredDataId id) { - if (id == null) { - return this; - } - return makeId(id.getName(), id.getEnterpriseNumber()); - } - - /** - * Creates an id based on the current id. - * - * @param defaultId The default id to use if this StructuredDataId doesn't have a name. - * @param anEnterpriseNumber The enterprise number. - * @return a StructuredDataId. - */ - public StructuredDataId makeId(final String defaultId, final int anEnterpriseNumber) { - String id; - String[] req; - String[] opt; - if (anEnterpriseNumber <= 0) { - return this; - } - if (this.name != null) { - id = this.name; - req = this.required; - opt = this.optional; - } else { - id = defaultId; - req = null; - opt = null; - } - - return new StructuredDataId(id, anEnterpriseNumber, req, opt); - } - - /** - * Returns a list of required keys. - * - * @return a List of required keys or null if none have been provided. - */ - public String[] getRequired() { - return required; - } - - /** - * Returns a list of optional keys. - * - * @return a List of optional keys or null if none have been provided. - */ - public String[] getOptional() { - return optional; - } - - /** - * Returns the StructuredDataId name. - * - * @return the StructuredDataId name. - */ - public String getName() { - return name; - } - - /** - * Returns the enterprise number. - * - * @return the enterprise number. - */ - public int getEnterpriseNumber() { - return enterpriseNumber; - } - - /** - * Indicates if the id is reserved. - * - * @return true if the id uses the reserved enterprise number, false otherwise. - */ - public boolean isReserved() { - return enterpriseNumber <= 0; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(name.length() + 10); - formatTo(sb); - return sb.toString(); - } - - @Override - public void formatTo(final StringBuilder buffer) { - if (isReserved()) { - buffer.append(name); - } else { - buffer.append(name).append(AT_SIGN).append(enterpriseNumber); - } - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.message; + +import java.io.Serializable; + +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.apache.logging.log4j.util.Strings; + +/** + * The StructuredData identifier. + */ +public class StructuredDataId implements Serializable, StringBuilderFormattable { + + /** + * RFC 5424 Time Quality. + */ + public static final StructuredDataId TIME_QUALITY = new StructuredDataId("timeQuality", null, new String[] { + "tzKnown", "isSynced", "syncAccuracy"}); + + /** + * RFC 5424 Origin. + */ + public static final StructuredDataId ORIGIN = new StructuredDataId("origin", null, new String[] {"ip", + "enterpriseId", "software", "swVersion"}); + + /** + * RFC 5424 Meta. + */ + public static final StructuredDataId META = new StructuredDataId("meta", null, new String[] {"sequenceId", + "sysUpTime", "language"}); + + /** + * Reserved enterprise number. + */ + public static final String RESERVED = "-1"; + + private static final long serialVersionUID = -8252896346202183738L; + private static final int MAX_LENGTH = 32; + private static final String AT_SIGN = "@"; + + private final String name; + private final String enterpriseNumber; + private final String[] required; + private final String[] optional; + + /** + * Creates a StructuredDataId based on the name. + * @param name The Structured Data Element name (maximum length is 32) + * @since 2.9 + */ + public StructuredDataId(final String name) { + this(name, null, null, MAX_LENGTH); + } + + /** + * Creates a StructuredDataId based on the name. + * @param name The Structured Data Element name. + * @param maxLength The maximum length of the name. + * @since 2.9 + */ + public StructuredDataId(final String name, final int maxLength) { + this(name, null, null, maxLength); + } + + /** + * A Constructor that helps conformance to RFC 5424. + * @param name The name portion of the id. + * @param required The list of keys that are required for this id. + * @param optional The list of keys that are optional for this id. + */ + public StructuredDataId(final String name, final String[] required, final String[] optional) { + this(name, required, optional, MAX_LENGTH); + } + + /** + * A Constructor that helps conformance to RFC 5424. + * + * @param name The name portion of the id. + * @param required The list of keys that are required for this id. + * @param optional The list of keys that are optional for this id. + * @param maxLength The maximum length of the id. + * @since 2.9 + */ + public StructuredDataId(final String name, final String[] required, final String[] optional, int maxLength) { + int index = -1; + if (name != null) { + if (maxLength <= 0) { + maxLength = MAX_LENGTH; + } + if (name.length() > maxLength) { + throw new IllegalArgumentException(String.format("Length of id %s exceeds maximum of %d characters", + name, maxLength)); + } + index = name.indexOf(AT_SIGN); + } + + if (index > 0) { + this.name = name.substring(0, index); + this.enterpriseNumber = name.substring(index + 1).trim(); + } else { + this.name = name; + this.enterpriseNumber = RESERVED; + } + this.required = required; + this.optional = optional; + } + + /** + * A Constructor that helps conformance to RFC 5424. + * + * @param name The name portion of the id. + * @param enterpriseNumber The enterprise number. + * @param required The list of keys that are required for this id. + * @param optional The list of keys that are optional for this id. + */ + public StructuredDataId(final String name, final String enterpriseNumber, final String[] required, + final String[] optional) { + this(name, enterpriseNumber, required, optional, MAX_LENGTH); + } + + /** + * A Constructor that helps conformance to RFC 5424. + * + * @param name The name portion of the id. + * @param enterpriseNumber The enterprise number. + * @param required The list of keys that are required for this id. + * @param optional The list of keys that are optional for this id. + * @deprecated Use {@link #StructuredDataId(String, String, String[], String[])} instead. + */ + @Deprecated + public StructuredDataId(final String name, final int enterpriseNumber, final String[] required, + final String[] optional) { + this(name, String.valueOf(enterpriseNumber), required, optional, MAX_LENGTH); + } + + /** + * A Constructor that helps conformance to RFC 5424. + * + * @param name The name portion of the id. + * @param enterpriseNumber The enterprise number. + * @param required The list of keys that are required for this id. + * @param optional The list of keys that are optional for this id. + * @param maxLength The maximum length of the StructuredData Id key. + * @since 2.9 + */ + public StructuredDataId(final String name, final String enterpriseNumber, final String[] required, + final String[] optional, final int maxLength) { + if (name == null) { + throw new IllegalArgumentException("No structured id name was supplied"); + } + if (name.contains(AT_SIGN)) { + throw new IllegalArgumentException("Structured id name cannot contain an " + Strings.quote(AT_SIGN)); + } + if (RESERVED.equals(enterpriseNumber)) { + throw new IllegalArgumentException("No enterprise number was supplied"); + } + this.name = name; + this.enterpriseNumber = enterpriseNumber; + final String id = name + AT_SIGN + enterpriseNumber; + if (maxLength > 0 && id.length() > maxLength) { + throw new IllegalArgumentException("Length of id exceeds maximum of " + maxLength + " characters: " + id); + } + this.required = required; + this.optional = optional; + } + + /** + * A Constructor that helps conformance to RFC 5424. + * + * @param name The name portion of the id. + * @param enterpriseNumber The enterprise number. + * @param required The list of keys that are required for this id. + * @param optional The list of keys that are optional for this id. + * @param maxLength The maximum length of the StructuredData Id key. + * @since 2.9 + * @deprecated Use {@link #StructuredDataId(String, String, String[], String[], int)} instead. + */ + @Deprecated + public StructuredDataId(final String name, final int enterpriseNumber, final String[] required, + final String[] optional, final int maxLength) { + this(name, String.valueOf(enterpriseNumber), required, optional, maxLength); + } + + /** + * Creates an id using another id to supply default values. + * + * @param id The original StructuredDataId. + * @return the new StructuredDataId. + */ + public StructuredDataId makeId(final StructuredDataId id) { + if (id == null) { + return this; + } + return makeId(id.getName(), id.getEnterpriseNumber()); + } + + /** + * Creates an id based on the current id. + * + * @param defaultId The default id to use if this StructuredDataId doesn't have a name. + * @param anEnterpriseNumber The enterprise number. + * @return a StructuredDataId. + */ + public StructuredDataId makeId(final String defaultId, final String anEnterpriseNumber) { + String id; + String[] req; + String[] opt; + if (RESERVED.equals(anEnterpriseNumber)) { + return this; + } + if (this.name != null) { + id = this.name; + req = this.required; + opt = this.optional; + } else { + id = defaultId; + req = null; + opt = null; + } + + return new StructuredDataId(id, anEnterpriseNumber, req, opt); + } + + /** + * Creates an id based on the current id. + * + * @param defaultId The default id to use if this StructuredDataId doesn't have a name. + * @param anEnterpriseNumber The enterprise number. + * @return a StructuredDataId. + * @deprecated Use {@link StructuredDataId#makeId(String, String)} instead + */ + @Deprecated + public StructuredDataId makeId(final String defaultId, final int anEnterpriseNumber) { + return makeId(defaultId, String.valueOf(anEnterpriseNumber)); + } + + /** + * Returns a list of required keys. + * + * @return a List of required keys or null if none have been provided. + */ + public String[] getRequired() { + return required; + } + + /** + * Returns a list of optional keys. + * + * @return a List of optional keys or null if none have been provided. + */ + public String[] getOptional() { + return optional; + } + + /** + * Returns the StructuredDataId name. + * + * @return the StructuredDataId name. + */ + public String getName() { + return name; + } + + /** + * Returns the enterprise number. + * + * @return the enterprise number. + */ + public String getEnterpriseNumber() { + return enterpriseNumber; + } + + /** + * Indicates if the id is reserved. + * + * @return true if the id uses the reserved enterprise number, false otherwise. + */ + public boolean isReserved() { + return RESERVED.equals(enterpriseNumber); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(name.length() + 10); + formatTo(sb); + return sb.toString(); + } + + @Override + public void formatTo(final StringBuilder buffer) { + if (isReserved()) { + buffer.append(name); + } else { + buffer.append(name).append(AT_SIGN).append(enterpriseNumber); + } + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java index 32fa2a5b953..7c5dbfcfdaa 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataMessage.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.message; import java.util.Map; @@ -83,7 +82,7 @@ public StructuredDataMessage(final String id, final String msg, final String typ this.type = type; this.maxLength = maxLength; } - + /** * Creates a StructuredDataMessage using an ID (max 32 characters), message, type (max 32 characters), and an * initial map of structured data to include. @@ -253,7 +252,7 @@ public void formatTo(final StringBuilder buffer) { } @Override - public void formatTo(String[] formats, StringBuilder buffer) { + public void formatTo(final String[] formats, final StringBuilder buffer) { asString(getFormat(formats), null, buffer); } @@ -355,7 +354,7 @@ public final void asString(final Format format, final StructuredDataId structure } } - private void asXml(StructuredDataId structuredDataId, StringBuilder sb) { + private void asXml(final StructuredDataId structuredDataId, final StringBuilder sb) { sb.append("\n"); sb.append("").append(type).append("\n"); sb.append("").append(structuredDataId).append("\n"); @@ -373,7 +372,7 @@ public String getFormattedMessage() { } /** - * Formats the message according the the specified format. + * Formats the message according to the specified format. * @param formats An array of Strings that provide extra information about how to format the message. * StructuredDataMessage accepts only a format of "FULL" which will cause the event type to be * prepended and the event message to be appended. Specifying any other value will cause only the @@ -386,7 +385,7 @@ public String getFormattedMessage(final String[] formats) { return asString(getFormat(formats), null); } - private Format getFormat(String[] formats) { + private Format getFormat(final String[] formats) { if (formats != null && formats.length > 0) { for (int i = 0; i < formats.length; i++) { final String format = formats[i]; @@ -432,11 +431,7 @@ public boolean equals(final Object o) { if (id != null ? !id.equals(that.id) : that.id != null) { return false; } - if (message != null ? !message.equals(that.message) : that.message != null) { - return false; - } - - return true; + return message != null ? message.equals(that.message) : that.message == null; } @Override @@ -468,7 +463,7 @@ protected void validate(final String key, final byte value) { protected void validate(final String key, final char value) { validateKey(key); } - + /** * @since 2.9 */ @@ -476,7 +471,7 @@ protected void validate(final String key, final char value) { protected void validate(final String key, final double value) { validateKey(key); } - + /** * @since 2.9 */ diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java index 2229948ed40..74dd23fad3c 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ThreadDumpMessage.java @@ -19,13 +19,12 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.lang.invoke.MethodHandles; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; -import java.util.ServiceConfigurationError; -import java.util.ServiceLoader; -import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.ServiceRegistry; import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.Strings; @@ -35,7 +34,11 @@ @AsynchronouslyFormattable public class ThreadDumpMessage implements Message, StringBuilderFormattable { private static final long serialVersionUID = -1103400781608841088L; - private static ThreadInfoFactory FACTORY; + private static final Lazy FACTORY = Lazy.lazy(() -> { + final var services = ServiceRegistry.getInstance() + .getServices(ThreadInfoFactory.class, MethodHandles.lookup(), null); + return services.isEmpty() ? new BasicThreadInfoFactory() : services.get(0); + }); private volatile Map threads; private final String title; @@ -47,7 +50,7 @@ public class ThreadDumpMessage implements Message, StringBuilderFormattable { */ public ThreadDumpMessage(final String title) { this.title = title == null ? Strings.EMPTY : title; - threads = getFactory().createThreadInfo(); + threads = FACTORY.get().createThreadInfo(); } private ThreadDumpMessage(final String formattedMsg, final String title) { @@ -55,29 +58,6 @@ private ThreadDumpMessage(final String formattedMsg, final String title) { this.title = title == null ? Strings.EMPTY : title; } - private static ThreadInfoFactory getFactory() { - if (FACTORY == null) { - FACTORY = initFactory(ThreadDumpMessage.class.getClassLoader()); - } - return FACTORY; - } - - private static ThreadInfoFactory initFactory(final ClassLoader classLoader) { - final ServiceLoader serviceLoader = ServiceLoader.load(ThreadInfoFactory.class, classLoader); - ThreadInfoFactory result = null; - try { - final Iterator iterator = serviceLoader.iterator(); - while (result == null && iterator.hasNext()) { - result = iterator.next(); - } - } catch (ServiceConfigurationError | LinkageError | Exception unavailable) { // if java management classes not available - StatusLogger.getLogger().info("ThreadDumpMessage uses BasicThreadInfoFactory: " + - "could not load extended ThreadInfoFactory: {}", unavailable.toString()); - result = null; - } - return result == null ? new BasicThreadInfoFactory() : result; - } - @Override public String toString() { return getFormattedMessage(); @@ -171,9 +151,8 @@ protected Object readResolve() { *

* Implementations of this class are loaded via the standard java Service Provider interface. *

- * @see /log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory */ - public static interface ThreadInfoFactory { + public interface ThreadInfoFactory { Map createThreadInfo(); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java index b91f3b20c41..0045e6dc790 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLogger.java @@ -18,7 +18,6 @@ import java.io.PrintStream; import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; @@ -28,7 +27,7 @@ import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.spi.AbstractLogger; -import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.InternalApi; import org.apache.logging.log4j.util.Strings; /** @@ -58,41 +57,36 @@ public class SimpleLogger extends AbstractLogger { private final String logName; - public SimpleLogger(final String name, final Level defaultLevel, final boolean showLogName, - final boolean showShortLogName, final boolean showDateTime, final boolean showContextMap, - final String dateTimeFormat, final MessageFactory messageFactory, final PropertiesUtil props, - final PrintStream stream) { + public SimpleLogger(final String name, final MessageFactory messageFactory, final PrintStream stream, + final SimpleLoggerConfiguration configuration) { super(name, messageFactory); - final String lvl = props.getStringProperty(SimpleLoggerContext.SYSTEM_PREFIX + name + ".level"); - this.level = Level.toLevel(lvl, defaultLevel); - if (showShortLogName) { + level = configuration.getLoggerLevel(name); + if (configuration.isShortNameShown()) { final int index = name.lastIndexOf("."); - if (index > 0 && index < name.length()) { - this.logName = name.substring(index + 1); - } else { - this.logName = name; - } - } else if (showLogName) { - this.logName = name; + logName = index > 0 ? name.substring(index + 1) : name; + } else if (configuration.isLogNameShown()) { + logName = name; } else { - this.logName = null; + logName = null; } - this.showDateTime = showDateTime; - this.showContextMap = showContextMap; + showDateTime = configuration.isDateTimeShown(); + showContextMap = configuration.isContextMapShown(); this.stream = stream; + dateFormatter = showDateTime ? configuration.getDateTimeFormat() : null; + } - if (showDateTime) { - DateFormat format; - try { - format = new SimpleDateFormat(dateTimeFormat); - } catch (final IllegalArgumentException e) { - // If the format pattern is invalid - use the default format - format = new SimpleDateFormat(SimpleLoggerContext.DEFAULT_DATE_TIME_FORMAT); - } - this.dateFormatter = format; - } else { - this.dateFormatter = null; - } + // exposed for use in StatusLoggerFactory + @InternalApi + public SimpleLogger(final String name, final MessageFactory messageFactory, final PrintStream stream, + final Level level, final DateFormat dateFormatter, final boolean showDateTime) { + super(name, messageFactory); + this.stream = stream; + this.level = level; + this.dateFormatter = dateFormatter; + this.showDateTime = showDateTime; + this.showContextMap = false; + final int index = name.lastIndexOf("."); + logName = index > 0 ? name.substring(index + 1) : name; } @Override @@ -205,7 +199,7 @@ public void logMessage(final String fqcn, final Level mgsLevel, final Marker mar // Append date-time if so configured if (showDateTime) { final Date now = new Date(); - String dateText; + final String dateText; synchronized (dateFormatter) { dateText = dateFormatter.format(now); } @@ -224,19 +218,19 @@ public void logMessage(final String fqcn, final Level mgsLevel, final Marker mar final Map mdc = ThreadContext.getImmutableContext(); if (mdc.size() > 0) { sb.append(SPACE); - sb.append(mdc.toString()); + sb.append(mdc); sb.append(SPACE); } } final Object[] params = msg.getParameters(); - Throwable t; + final Throwable t; if (throwable == null && params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) { t = (Throwable) params[params.length - 1]; } else { t = throwable; } - stream.println(sb.toString()); + stream.println(sb); if (t != null) { stream.print(SPACE); t.printStackTrace(stream); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerConfiguration.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerConfiguration.java new file mode 100644 index 00000000000..9e06cd0fce6 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerConfiguration.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.simple; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.util.PropertyEnvironment; + +import static org.apache.logging.log4j.simple.SimpleLoggerContext.DEFAULT_DATE_TIME_FORMAT; +import static org.apache.logging.log4j.simple.SimpleLoggerContext.SYSTEM_PREFIX; + +public class SimpleLoggerConfiguration { + + protected final PropertyEnvironment environment; + + public SimpleLoggerConfiguration(final PropertyEnvironment environment) { + this.environment = environment; + } + + /** Include the ThreadContextMap in the log message */ + public boolean isContextMapShown() { + return environment.getBooleanProperty(SYSTEM_PREFIX + "showContextMap", false); + } + + /** Include the instance name in the log message? */ + public boolean isLogNameShown() { + return environment.getBooleanProperty(SYSTEM_PREFIX + "showlogname", false); + } + + /** + * Include the short name (last component) of the logger in the log message. Defaults to true - otherwise we'll be + * lost in a flood of messages without knowing who sends them. + */ + public boolean isShortNameShown() { + return environment.getBooleanProperty(SYSTEM_PREFIX + "showShortLogname", true); + } + + /** Include the current time in the log message */ + public boolean isDateTimeShown() { + return environment.getBooleanProperty(SYSTEM_PREFIX + "showdatetime", false); + } + + public Level getDefaultLevel() { + final String level = environment.getStringProperty(SYSTEM_PREFIX + "level"); + return Level.toLevel(level, Level.ERROR); + } + + public Level getLoggerLevel(final String loggerName) { + final String level = environment.getStringProperty(SYSTEM_PREFIX + loggerName + ".level"); + return Level.toLevel(level, getDefaultLevel()); + } + + public DateFormat getDateTimeFormat() { + try { + return new SimpleDateFormat(environment.getStringProperty(SYSTEM_PREFIX + "dateTimeFormat", DEFAULT_DATE_TIME_FORMAT)); + } catch (final IllegalArgumentException e) { + return new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT); + } + } + + public String getLogFileName() { + return environment.getStringProperty(SYSTEM_PREFIX + "logFile", "system.err"); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java index fe460c6a57c..6e72aa9fabc 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/simple/SimpleLoggerContext.java @@ -20,7 +20,6 @@ import java.io.FileOutputStream; import java.io.PrintStream; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.spi.ExtendedLogger; @@ -29,10 +28,13 @@ import org.apache.logging.log4j.util.PropertiesUtil; /** - * + * A simple {@link LoggerContext} implementation. */ public class SimpleLoggerContext implements LoggerContext { + /** Singleton instance. */ + static final SimpleLoggerContext INSTANCE = new SimpleLoggerContext(); + private static final String SYSTEM_OUT = "system.out"; private static final String SYSTEM_ERR = "system.err"; @@ -43,43 +45,22 @@ public class SimpleLoggerContext implements LoggerContext { /** All system properties used by SimpleLog start with this */ protected static final String SYSTEM_PREFIX = "org.apache.logging.log4j.simplelog."; - private final PropertiesUtil props; - - /** Include the instance name in the log message? */ - private final boolean showLogName; - - /** - * Include the short name (last component) of the logger in the log message. Defaults to true - otherwise we'll be - * lost in a flood of messages without knowing who sends them. - */ - private final boolean showShortName; - /** Include the current time in the log message */ - private final boolean showDateTime; - /** Include the ThreadContextMap in the log message */ - private final boolean showContextMap; - /** The date and time format to use in the log message */ - private final String dateTimeFormat; - - private final Level defaultLevel; + private final SimpleLoggerConfiguration configuration; private final PrintStream stream; private final LoggerRegistry loggerRegistry = new LoggerRegistry<>(); + /** + * Constructs a new initialized instance. + */ public SimpleLoggerContext() { - props = new PropertiesUtil("log4j2.simplelog.properties"); - - showContextMap = props.getBooleanProperty(SYSTEM_PREFIX + "showContextMap", false); - showLogName = props.getBooleanProperty(SYSTEM_PREFIX + "showlogname", false); - showShortName = props.getBooleanProperty(SYSTEM_PREFIX + "showShortLogname", true); - showDateTime = props.getBooleanProperty(SYSTEM_PREFIX + "showdatetime", false); - final String lvl = props.getStringProperty(SYSTEM_PREFIX + "level"); - defaultLevel = Level.toLevel(lvl, Level.ERROR); - - dateTimeFormat = showDateTime ? props.getStringProperty(SimpleLoggerContext.SYSTEM_PREFIX + "dateTimeFormat", - DEFAULT_DATE_TIME_FORMAT) : null; + this(new SimpleLoggerConfiguration(PropertiesUtil.getProperties("simplelog"))); + } - final String fileName = props.getStringProperty(SYSTEM_PREFIX + "logFile", SYSTEM_ERR); + public SimpleLoggerContext(final SimpleLoggerConfiguration configuration) { + this.configuration = configuration; + final String fileName = configuration.getLogFileName(); PrintStream ps; if (SYSTEM_ERR.equalsIgnoreCase(fileName)) { ps = System.err; @@ -87,8 +68,7 @@ public SimpleLoggerContext() { ps = System.out; } else { try { - final FileOutputStream os = new FileOutputStream(fileName); - ps = new PrintStream(os); + ps = new PrintStream(new FileOutputStream(fileName)); } catch (final FileNotFoundException fnfe) { ps = System.err; } @@ -96,6 +76,11 @@ public SimpleLoggerContext() { this.stream = ps; } + @Override + public Object getExternalContext() { + return null; + } + @Override public ExtendedLogger getLogger(final String name) { return getLogger(name, null); @@ -109,19 +94,24 @@ public ExtendedLogger getLogger(final String name, final MessageFactory messageF AbstractLogger.checkMessageFactory(extendedLogger, messageFactory); return extendedLogger; } - final SimpleLogger simpleLogger = new SimpleLogger(name, defaultLevel, showLogName, showShortName, showDateTime, - showContextMap, dateTimeFormat, messageFactory, props, stream); + final SimpleLogger simpleLogger = new SimpleLogger(name, messageFactory, stream, configuration); loggerRegistry.putIfAbsent(name, messageFactory, simpleLogger); return loggerRegistry.getLogger(name, messageFactory); } + /** + * Gets the LoggerRegistry. + * + * @return the LoggerRegistry. + * @since 2.17.2 + */ @Override - public boolean hasLogger(final String name) { - return false; + public LoggerRegistry getLoggerRegistry() { + return loggerRegistry; } @Override - public boolean hasLogger(final String name, final MessageFactory messageFactory) { + public boolean hasLogger(final String name) { return false; } @@ -131,8 +121,8 @@ public boolean hasLogger(final String name, final Class DEFAULT_MESSAGE_FACTORY_CLASS = - createClassForProperty("log4j2.messageFactory", ReusableMessageFactory.class, - ParameterizedMessageFactory.class); - - /** - * The default FlowMessageFactory class. - */ - public static final Class DEFAULT_FLOW_MESSAGE_FACTORY_CLASS = - createFlowClassForProperty("log4j2.flowMessageFactory", DefaultFlowMessageFactory.class); - private static final long serialVersionUID = 2L; private static final String FQCN = AbstractLogger.class.getName(); @@ -102,17 +87,21 @@ public abstract class AbstractLogger implements ExtendedLogger, Serializable { private static final String CATCHING = "Catching"; protected final String name; - private final MessageFactory2 messageFactory; + private final MessageFactory messageFactory; private final FlowMessageFactory flowMessageFactory; - private static ThreadLocal recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031 + private static final ThreadLocal recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031 + protected final transient ThreadLocal logBuilder; + /** * Creates a new logger named after this class (or subclass). */ public AbstractLogger() { - this.name = getClass().getName(); - this.messageFactory = createDefaultMessageFactory(); - this.flowMessageFactory = createDefaultFlowMessageFactory(); + final String canonicalName = getClass().getCanonicalName(); + this.name = canonicalName != null ? canonicalName : getClass().getName(); + this.messageFactory = LoggingSystem.getMessageFactory(); + this.flowMessageFactory = LoggingSystem.getFlowMessageFactory(); + this.logBuilder = new LocalLogBuilder(this); } /** @@ -121,7 +110,7 @@ public AbstractLogger() { * @param name the logger name */ public AbstractLogger(final String name) { - this(name, createDefaultMessageFactory()); + this(name, LoggingSystem.getMessageFactory()); } /** @@ -132,14 +121,15 @@ public AbstractLogger(final String name) { */ public AbstractLogger(final String name, final MessageFactory messageFactory) { this.name = name; - this.messageFactory = messageFactory == null ? createDefaultMessageFactory() : narrow(messageFactory); - this.flowMessageFactory = createDefaultFlowMessageFactory(); + this.messageFactory = messageFactory == null ? LoggingSystem.getMessageFactory() : messageFactory; + this.flowMessageFactory = LoggingSystem.getFlowMessageFactory(); + this.logBuilder = new LocalLogBuilder(this); } /** * Checks that the message factory a logger was created with is the same as the given messageFactory. If they are * different log a warning to the {@linkplain StatusLogger}. A null MessageFactory translates to the default - * MessageFactory {@link #DEFAULT_MESSAGE_FACTORY_CLASS}. + * MessageFactory {@link LoggingSystem#getMessageFactory()}. * * @param logger The logger to check * @param messageFactory The message factory to check. @@ -147,18 +137,19 @@ public AbstractLogger(final String name, final MessageFactory messageFactory) { public static void checkMessageFactory(final ExtendedLogger logger, final MessageFactory messageFactory) { final String name = logger.getName(); final MessageFactory loggerMessageFactory = logger.getMessageFactory(); + final MessageFactory currentMessageFactory = LoggingSystem.getMessageFactory(); if (messageFactory != null && !loggerMessageFactory.equals(messageFactory)) { StatusLogger.getLogger().warn( "The Logger {} was created with the message factory {} and is now requested with the " + "message factory {}, which may create log events with unexpected formatting.", name, loggerMessageFactory, messageFactory); - } else if (messageFactory == null && !loggerMessageFactory.getClass().equals(DEFAULT_MESSAGE_FACTORY_CLASS)) { + } else if (messageFactory == null && loggerMessageFactory != currentMessageFactory) { StatusLogger .getLogger() .warn("The Logger {} was created with the message factory {} and is now requested with a null " + "message factory (defaults to {}), which may create log events with unexpected " + "formatting.", - name, loggerMessageFactory, DEFAULT_MESSAGE_FACTORY_CLASS.getName()); + name, loggerMessageFactory, currentMessageFactory.getClass().getName()); } } @@ -191,53 +182,6 @@ protected Message catchingMsg(final Throwable t) { return messageFactory.newMessage(CATCHING); } - private static Class createClassForProperty(final String property, - final Class reusableParameterizedMessageFactoryClass, - final Class parameterizedMessageFactoryClass) { - try { - final String fallback = Constants.ENABLE_THREADLOCALS ? reusableParameterizedMessageFactoryClass.getName() - : parameterizedMessageFactoryClass.getName(); - final String clsName = PropertiesUtil.getProperties().getStringProperty(property, fallback); - return LoaderUtil.loadClass(clsName).asSubclass(MessageFactory.class); - } catch (final Throwable t) { - return parameterizedMessageFactoryClass; - } - } - - private static Class createFlowClassForProperty(final String property, - final Class defaultFlowMessageFactoryClass) { - try { - final String clsName = PropertiesUtil.getProperties().getStringProperty(property, defaultFlowMessageFactoryClass.getName()); - return LoaderUtil.loadClass(clsName).asSubclass(FlowMessageFactory.class); - } catch (final Throwable t) { - return defaultFlowMessageFactoryClass; - } - } - - private static MessageFactory2 createDefaultMessageFactory() { - try { - final MessageFactory result = DEFAULT_MESSAGE_FACTORY_CLASS.newInstance(); - return narrow(result); - } catch (final InstantiationException | IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - - private static MessageFactory2 narrow(final MessageFactory result) { - if (result instanceof MessageFactory2) { - return (MessageFactory2) result; - } - return new MessageFactory2Adapter(result); - } - - private static FlowMessageFactory createDefaultFlowMessageFactory() { - try { - return DEFAULT_FLOW_MESSAGE_FACTORY_CLASS.newInstance(); - } catch (final InstantiationException | IllegalAccessException e) { - throw new IllegalStateException(e); - } - } - @Override public void debug(final Marker marker, final CharSequence message) { logIfEnabled(FQCN, Level.DEBUG, marker, message, null); @@ -504,27 +448,13 @@ public void debug(final String message, final Object p0, final Object p1, final * @param fqcn The fully qualified class name of the caller. * @param format Format String for the parameters. * @param paramSuppliers The Suppliers of the parameters. + * @return The EntryMessage. */ protected EntryMessage enter(final String fqcn, final String format, final Supplier... paramSuppliers) { EntryMessage entryMsg = null; if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, paramSuppliers), null); - } - return entryMsg; - } - - /** - * Logs entry to a method with location information. - * - * @param fqcn The fully qualified class name of the caller. - * @param format The format String for the parameters. - * @param paramSuppliers The parameters to the method. - */ - @Deprecated - protected EntryMessage enter(final String fqcn, final String format, final MessageSupplier... paramSuppliers) { - EntryMessage entryMsg = null; - if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, paramSuppliers), null); + logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, + entryMsg = flowMessageFactory.newEntryMessage(format, LambdaUtil.getAll(paramSuppliers)), null); } return entryMsg; } @@ -535,11 +465,12 @@ protected EntryMessage enter(final String fqcn, final String format, final Messa * @param fqcn The fully qualified class name of the caller. * @param format The format String for the parameters. * @param params The parameters to the method. + * @return The EntryMessage. */ protected EntryMessage enter(final String fqcn, final String format, final Object... params) { EntryMessage entryMsg = null; if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, params), null); + logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = flowMessageFactory.newEntryMessage(format, params), null); } return entryMsg; } @@ -548,25 +479,8 @@ protected EntryMessage enter(final String fqcn, final String format, final Objec * Logs entry to a method with location information. * * @param fqcn The fully qualified class name of the caller. - * @param msgSupplier The Supplier of the Message. - */ - @Deprecated - protected EntryMessage enter(final String fqcn, final MessageSupplier msgSupplier) { - EntryMessage message = null; - if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) { - logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, message = flowMessageFactory.newEntryMessage( - msgSupplier.get()), null); - } - return message; - } - - /** - * Logs entry to a method with location information. - * - * @param fqcn - * The fully qualified class name of the caller. - * @param message - * the Message. + * @param message the Message. + * @return The EntryMessage. * @since 2.6 */ protected EntryMessage enter(final String fqcn, final Message message) { @@ -578,17 +492,6 @@ protected EntryMessage enter(final String fqcn, final Message message) { return flowMessage; } - @Deprecated - @Override - public void entry() { - entry(FQCN, (Object[]) null); - } - - @Override - public void entry(final Object... params) { - entry(FQCN, params); - } - /** * Logs entry to a method with location information. * @@ -606,27 +509,7 @@ protected void entry(final String fqcn, final Object... params) { } protected EntryMessage entryMsg(final String format, final Object... params) { - final int count = params == null ? 0 : params.length; - if (count == 0) { - if (Strings.isEmpty(format)) { - return flowMessageFactory.newEntryMessage(null); - } - return flowMessageFactory.newEntryMessage(new SimpleMessage(format)); - } - if (format != null) { - return flowMessageFactory.newEntryMessage(new ParameterizedMessage(format, params)); - } - final StringBuilder sb = new StringBuilder(); - sb.append("params("); - for (int i = 0; i < count; i++) { - if (i > 0) { - sb.append(", "); - } - final Object parm = params[i]; - sb.append(parm instanceof Message ? ((Message) parm).getFormattedMessage() : String.valueOf(parm)); - } - sb.append(')'); - return flowMessageFactory.newEntryMessage(new SimpleMessage(sb)); + return flowMessageFactory.newEntryMessage(format, params); } protected EntryMessage entryMsg(final String format, final MessageSupplier... paramSuppliers) { @@ -634,21 +517,12 @@ protected EntryMessage entryMsg(final String format, final MessageSupplier... pa final Object[] params = new Object[count]; for (int i = 0; i < count; i++) { params[i] = paramSuppliers[i].get(); - params[i] = params[i] != null ? ((Message) params[i]).getFormattedMessage() : null; } return entryMsg(format, params); } protected EntryMessage entryMsg(final String format, final Supplier... paramSuppliers) { - final int count = paramSuppliers == null ? 0 : paramSuppliers.length; - final Object[] params = new Object[count]; - for (int i = 0; i < count; i++) { - params[i] = paramSuppliers[i].get(); - if (params[i] instanceof Message) { - params[i] = ((Message) params[i]).getFormattedMessage(); - } - } - return entryMsg(format, params); + return entryMsg(format, LambdaUtil.getAll(paramSuppliers)); } @Override @@ -908,18 +782,6 @@ public void error(final String message, final Object p0, final Object p1, final logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } - @Deprecated - @Override - public void exit() { - exit(FQCN, (Object) null); - } - - @Deprecated - @Override - public R exit(final R result) { - return exit(FQCN, result); - } - /** * Logs exiting from a method with the result and location information. * @@ -930,7 +792,7 @@ public R exit(final R result) { */ protected R exit(final String fqcn, final R result) { if (isEnabled(Level.TRACE, EXIT_MARKER, (CharSequence) null, null)) { - logMessageSafely(fqcn, Level.TRACE, EXIT_MARKER, exitMsg(null, result), null); + logMessageSafely(fqcn, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(null, result), null); } return result; } @@ -939,29 +801,20 @@ protected R exit(final String fqcn, final R result) { * Logs exiting from a method with the result and location information. * * @param fqcn The fully qualified class name of the caller. + * @param format The format string. * @param The type of the parameter and object being returned. * @param result The result being returned from the method call. * @return the return value passed to this method. */ protected R exit(final String fqcn, final String format, final R result) { if (isEnabled(Level.TRACE, EXIT_MARKER, (CharSequence) null, null)) { - logMessageSafely(fqcn, Level.TRACE, EXIT_MARKER, exitMsg(format, result), null); + logMessageSafely(fqcn, Level.TRACE, EXIT_MARKER, flowMessageFactory.newExitMessage(format, result), null); } return result; } protected Message exitMsg(final String format, final Object result) { - if (result == null) { - if (format == null) { - return messageFactory.newMessage("Exit"); - } - return messageFactory.newMessage("Exit: " + format); - } - if (format == null) { - return messageFactory.newMessage("Exit with(" + result + ')'); - } - return messageFactory.newMessage("Exit: " + format, result); - + return flowMessageFactory.newExitMessage(format, result); } @Override @@ -1226,6 +1079,11 @@ public MF getMessageFactory() { return (MF) messageFactory; } + @Override + public FlowMessageFactory getFlowMessageFactory() { + return flowMessageFactory; + } + @Override public String getName() { return name; @@ -2083,6 +1941,24 @@ protected void logMessage(final String fqcn, final Level level, final Marker mar logMessageSafely(fqcn, level, marker, msg, msg.getThrowable()); } + public void logMessage(final Level level, final Marker marker, final String fqcn, final StackTraceElement location, + final Message message, final Throwable throwable) { + try { + incrementRecursionDepth(); + log(level, marker, fqcn, location, message, throwable); + } catch (Throwable ex) { + handleLogMessageException(ex, fqcn, message); + } finally { + decrementRecursionDepth(); + ReusableMessageFactory.release(message); + } + } + + protected void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location, + final Message message, final Throwable throwable) { + logMessage(fqcn, level, marker, message, throwable); + } + @Override public void printf(final Level level, final Marker marker, final String format, final Object... params) { if (isEnabled(level, marker, format, params)) { @@ -2113,7 +1989,7 @@ private void logMessageSafely(final String fqcn, final Level level, final Marker } @PerformanceSensitive - // NOTE: This is a hot method. Current implementation compiles to 29 bytes of byte code. + // NOTE: This is a hot method. Current implementation compiles to 33 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! private void logMessageTrackRecursion(final String fqcn, final Level level, @@ -2122,7 +1998,7 @@ private void logMessageTrackRecursion(final String fqcn, final Throwable throwable) { try { incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031 - tryLogMessage(fqcn, level, marker, msg, throwable); + tryLogMessage(fqcn, getLocation(fqcn), level, marker, msg, throwable); } finally { decrementRecursionDepth(); } @@ -2140,11 +2016,11 @@ private static int[] getRecursionDepthHolder() { private static void incrementRecursionDepth() { getRecursionDepthHolder()[0]++; } + private static void decrementRecursionDepth() { - int[] depth = getRecursionDepthHolder(); - depth[0]--; - if (depth[0] < 0) { - throw new IllegalStateException("Recursion depth became negative: " + depth[0]); + int newDepth = --getRecursionDepthHolder()[0]; + if (newDepth < 0) { + throw new IllegalStateException("Recursion depth became negative: " + newDepth); } } @@ -2159,37 +2035,41 @@ public static int getRecursionDepth() { } @PerformanceSensitive - // NOTE: This is a hot method. Current implementation compiles to 26 bytes of byte code. + // NOTE: This is a hot method. Current implementation compiles to 27 bytes of byte code. // This is within the 35 byte MaxInlineSize threshold. Modify with care! private void tryLogMessage(final String fqcn, + final StackTraceElement location, final Level level, final Marker marker, - final Message msg, + final Message message, final Throwable throwable) { try { - logMessage(fqcn, level, marker, msg, throwable); - } catch (final Exception e) { + log(level, marker, fqcn, location, message, throwable); + } catch (final Throwable t) { // LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger - handleLogMessageException(e, fqcn, msg); + handleLogMessageException(t, fqcn, message); } } + @PerformanceSensitive + // NOTE: This is a hot method. Current implementation compiles to 15 bytes of byte code. + // This is within the 35 byte MaxInlineSize threshold. Modify with care! + private StackTraceElement getLocation(final String fqcn) { + return requiresLocation() ? StackLocatorUtil.calcLocation(fqcn) : null; + } + // LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger // TODO Configuration setting to propagate exceptions back to the caller *if requested* - private void handleLogMessageException(final Exception exception, final String fqcn, final Message msg) { - if (exception instanceof LoggingException) { - throw (LoggingException) exception; + private void handleLogMessageException(final Throwable throwable, final String fqcn, final Message message) { + if (throwable instanceof LoggingException) { + throw (LoggingException) throwable; } - final String format = msg.getFormat(); - final StringBuilder sb = new StringBuilder(format.length() + 100); - sb.append(fqcn); - sb.append(" caught "); - sb.append(exception.getClass().getName()); - sb.append(" logging "); - sb.append(msg.getClass().getSimpleName()); - sb.append(": "); - sb.append(format); - StatusLogger.getLogger().warn(sb.toString(), exception); + StatusLogger.getLogger().warn("{} caught {} logging {}: {}", + fqcn, + throwable.getClass().getName(), + message.getClass().getSimpleName(), + message.getFormat(), + throwable); } @Override @@ -2797,4 +2677,124 @@ public void warn(final String message, final Object p0, final Object p1, final O final Object p7, final Object p8, final Object p9) { logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } + + protected boolean requiresLocation() { + return false; + } + + /** + * Construct a trace log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder atTrace() { + return atLevel(Level.TRACE); + } + + /** + * Construct a debug log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder atDebug() { + return atLevel(Level.DEBUG); + } + + /** + * Construct an informational log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder atInfo() { + return atLevel(Level.INFO); + } + + /** + * Construct a warning log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder atWarn() { + return atLevel(Level.WARN); + } + + /** + * Construct an error log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder atError() { + return atLevel(Level.ERROR); + } + + /** + * Construct a fatal log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder atFatal() { + return atLevel(Level.FATAL); + } + + /** + * Construct a log event that will always be logged. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder always() { + final DefaultLogBuilder builder = logBuilder.get(); + if (builder.isInUse()) { + return new DefaultLogBuilder(this); + } + return builder.reset(Level.OFF); + } + + /** + * Construct a log event. + * @return a LogBuilder. + * @since 2.13.0 + */ + @Override + public LogBuilder atLevel(final Level level) { + if (isEnabled(level)) { + return (getLogBuilder(level).reset(level)); + } else { + return LogBuilder.NOOP; + } + } + + private DefaultLogBuilder getLogBuilder(final Level level) { + final DefaultLogBuilder builder = logBuilder.get(); + return Constants.isThreadLocalsEnabled() && !builder.isInUse() ? builder : new DefaultLogBuilder(this, level); + } + + private void readObject (final ObjectInputStream s) throws ClassNotFoundException, IOException { + s.defaultReadObject( ); + try { + final Field f = this.getClass().getDeclaredField("logBuilder"); + f.setAccessible(true); + f.set(this, new LocalLogBuilder(this)); + } catch (final NoSuchFieldException | IllegalAccessException ex) { + StatusLogger.getLogger().warn("Unable to initialize LogBuilder"); + } + } + + private static class LocalLogBuilder extends ThreadLocal { + private final AbstractLogger logger; + LocalLogBuilder(final AbstractLogger logger) { + this.logger = logger; + } + + @Override + protected DefaultLogBuilder initialValue() { + return new DefaultLogBuilder(logger); + } + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java index e86f990fd6c..76b816e8c3a 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java @@ -16,8 +16,9 @@ */ package org.apache.logging.log4j.spi; +import java.util.HashSet; import java.util.Map; -import java.util.WeakHashMap; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReadWriteLock; @@ -28,16 +29,16 @@ /** * Provides an abstract base class to use for implementing LoggerAdapter. - * + * * @param the Logger class to adapt * @since 2.1 */ -public abstract class AbstractLoggerAdapter implements LoggerAdapter { +public abstract class AbstractLoggerAdapter implements LoggerAdapter, LoggerContextShutdownAware { /** * A map to store loggers for their given LoggerContexts. */ - protected final Map> registry = new WeakHashMap<>(); + protected final Map> registry = new ConcurrentHashMap<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock (true); @@ -53,6 +54,11 @@ public L getLogger(final String name) { return loggers.get(name); } + @Override + public void contextShutdown(final LoggerContext loggerContext) { + registry.remove(loggerContext); + } + /** * Gets or creates the ConcurrentMap of named loggers for a given LoggerContext. * @@ -77,6 +83,9 @@ public ConcurrentMap getLoggersInContext(final LoggerContext context) if (loggers == null) { loggers = new ConcurrentHashMap<> (); registry.put (context, loggers); + if (context instanceof LoggerContextShutdownEnabled) { + ((LoggerContextShutdownEnabled) context).addShutdownListener(this); + } } return loggers; } finally { @@ -84,6 +93,14 @@ public ConcurrentMap getLoggersInContext(final LoggerContext context) } } + /** + * For unit testing. Consider to be private. + * @return The Set of LoggerContexts. + */ + public Set getLoggerContexts() { + return new HashSet<>(registry.keySet()); + } + /** * Creates a new named logger for a given {@link LoggerContext}. * diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CleanableThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CleanableThreadContextMap.java index f32a06e6e00..9f0dd3e7df8 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CleanableThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CleanableThreadContextMap.java @@ -17,23 +17,13 @@ package org.apache.logging.log4j.spi; /** - * Extension service provider interface to implement additional custom MDC behavior for - * {@link org.apache.logging.log4j.ThreadContext}. + * Legacy interface for backward compatibility with extensions to ThreadContextMap. + * These methods have since been moved to default methods on ThreadContextMap. * * @see ThreadContextMap * @since 2.8 + * @deprecated use {@link ThreadContextMap} directly */ +@Deprecated(since = "3.0.0") public interface CleanableThreadContextMap extends ThreadContextMap2 { - - /** - * Removes all given context map keys from the current thread's context map. - * - *

If the current thread does not have a context map it is - * created as a side effect.

- - * @param keys The keys. - * @since 2.8 - */ - void removeAll(final Iterable keys); - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java index 862246e3fa4..7650c79b580 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java @@ -19,11 +19,15 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.SortedArrayStringMap; import org.apache.logging.log4j.util.StringMap; -import org.apache.logging.log4j.util.PropertiesUtil; + +import static org.apache.logging.log4j.spi.LoggingSystem.THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY; +import static org.apache.logging.log4j.spi.LoggingSystemProperties.*; /** * {@code SortedArrayStringMap}-based implementation of the {@code ThreadContextMap} interface that creates a copy of @@ -34,61 +38,38 @@ * * @since 2.7 */ -class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap, CopyOnWrite { - - /** - * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain - * {@code ThreadLocal} (value is not "true") in the implementation. - */ - public static final String INHERITABLE_MAP = "isThreadContextMapInheritable"; - - /** - * The default initial capacity. - */ - protected static final int DEFAULT_INITIAL_CAPACITY = 16; - - /** - * System property name that can be used to control the data structure's initial capacity. - */ - protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity"; +class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ThreadContextMap, CopyOnWrite { private static final StringMap EMPTY_CONTEXT_DATA = new SortedArrayStringMap(1); - - private static volatile int initialCapacity; - private static volatile boolean inheritableMap; - /** - * Initializes static variables based on system properties. Normally called when this class is initialized by the VM - * and when Log4j is reconfigured. - */ - static void init() { - final PropertiesUtil properties = PropertiesUtil.getProperties(); - initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY); - inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP); - } - static { EMPTY_CONTEXT_DATA.freeze(); - init(); } private final ThreadLocal localMap; + protected final int initialCapacity; public CopyOnWriteSortedArrayThreadContextMap() { - this.localMap = createThreadLocalMap(); + this( + PropertiesUtil.getProperties().getBooleanProperty(THREAD_CONTEXT_MAP_INHERITABLE), + PropertiesUtil.getProperties().getIntegerProperty(THREAD_CONTEXT_INITIAL_CAPACITY, THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY) + ); } - // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. - // (This method is package protected for JUnit tests.) - private ThreadLocal createThreadLocalMap() { + CopyOnWriteSortedArrayThreadContextMap(final boolean inheritableMap, final int initialCapacity) { + this.localMap = createThreadLocalMap(inheritableMap); + this.initialCapacity = initialCapacity; + } + + private ThreadLocal createThreadLocalMap(final boolean inheritableMap) { if (inheritableMap) { - return new InheritableThreadLocal() { + return new InheritableThreadLocal<>() { @Override protected StringMap childValue(final StringMap parentValue) { if (parentValue == null) { return null; } - StringMap stringMap = createStringMap(parentValue); + final StringMap stringMap = createStringMap(parentValue); stringMap.freeze(); return stringMap; } @@ -166,13 +147,13 @@ public void putAllValues(final Map values) { @Override public String get(final String key) { - return (String) getValue(key); + return getValue(key); } @Override public V getValue(final String key) { final StringMap map = localMap.get(); - return map == null ? null : map.getValue(key); + return map == null ? null : map.getValue(key); } @Override @@ -213,7 +194,7 @@ public boolean containsKey(final String key) { @Override public Map getCopy() { final StringMap map = localMap.get(); - return map == null ? new HashMap() : map.toMap(); + return map == null ? new HashMap<>() : map.toMap(); } /** @@ -266,13 +247,6 @@ public boolean equals(final Object obj) { final ThreadContextMap other = (ThreadContextMap) obj; final Map map = this.getImmutableMapOrNull(); final Map otherMap = other.getImmutableMapOrNull(); - if (map == null) { - if (otherMap != null) { - return false; - } - } else if (!map.equals(otherMap)) { - return false; - } - return true; + return Objects.equals(map, otherMap); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java index 1e69a831300..f369803c63d 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java @@ -19,12 +19,16 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.apache.logging.log4j.util.BiConsumer; -import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.Cast; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.TriConsumer; +import static org.apache.logging.log4j.spi.LoggingSystemProperties.THREAD_CONTEXT_MAP_INHERITABLE; + /** * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored is always * immutable. This means the Map can be passed to other threads without concern that it will be updated. Since it is @@ -38,27 +42,17 @@ public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyString * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain * {@code ThreadLocal} (value is not "true") in the implementation. */ - public static final String INHERITABLE_MAP = "isThreadContextMapInheritable"; + public static final String INHERITABLE_MAP = THREAD_CONTEXT_MAP_INHERITABLE; private final boolean useMap; private final ThreadLocal> localMap; - private static boolean inheritableMap; - - static { - init(); - } - - // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. - // (This method is package protected for JUnit tests.) - static ThreadLocal> createThreadLocalMap(final boolean isMapEnabled) { + static ThreadLocal> createThreadLocalMap(final boolean isMapEnabled, final boolean inheritableMap) { if (inheritableMap) { - return new InheritableThreadLocal>() { + return new InheritableThreadLocal<>() { @Override protected Map childValue(final Map parentValue) { - return parentValue != null && isMapEnabled // - ? Collections.unmodifiableMap(new HashMap<>(parentValue)) // - : null; + return parentValue != null && isMapEnabled ? Map.copyOf(parentValue) : null; } }; } @@ -66,17 +60,17 @@ protected Map childValue(final Map parentValue) return new ThreadLocal<>(); } - static void init() { - inheritableMap = PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP); - } - public DefaultThreadContextMap() { this(true); } public DefaultThreadContextMap(final boolean useMap) { + this(useMap, PropertiesUtil.getProperties().getBooleanProperty(THREAD_CONTEXT_MAP_INHERITABLE)); + } + + DefaultThreadContextMap(final boolean useMap, final boolean inheritableMap) { this.useMap = useMap; - this.localMap = createThreadLocalMap(useMap); + this.localMap = createThreadLocalMap(useMap, inheritableMap); } @Override @@ -85,20 +79,19 @@ public void put(final String key, final String value) { return; } Map map = localMap.get(); - map = map == null ? new HashMap(1) : new HashMap<>(map); + map = map == null ? new HashMap<>(1) : new HashMap<>(map); map.put(key, value); localMap.set(Collections.unmodifiableMap(map)); } + @Override public void putAll(final Map m) { if (!useMap) { return; } Map map = localMap.get(); - map = map == null ? new HashMap(m.size()) : new HashMap<>(map); - for (final Map.Entry e : m.entrySet()) { - map.put(e.getKey(), e.getValue()); - } + map = map == null ? new HashMap<>(m.size()) : new HashMap<>(map); + map.putAll(m); localMap.set(Collections.unmodifiableMap(map)); } @@ -118,6 +111,7 @@ public void remove(final String key) { } } + @Override public void removeAll(final Iterable keys) { final Map map = localMap.get(); if (map != null) { @@ -153,8 +147,7 @@ public void forEach(final BiConsumer action) { } for (final Map.Entry entry : map.entrySet()) { //BiConsumer should be able to handle values of any type V. In our case the values are of type String. - @SuppressWarnings("unchecked") - V value = (V) entry.getValue(); + final V value = Cast.cast(entry.getValue()); action.accept(entry.getKey(), value); } } @@ -167,23 +160,21 @@ public void forEach(final TriConsumer action, final } for (final Map.Entry entry : map.entrySet()) { //TriConsumer should be able to handle values of any type V. In our case the values are of type String. - @SuppressWarnings("unchecked") - V value = (V) entry.getValue(); + final V value = Cast.cast(entry.getValue()); action.accept(entry.getKey(), value, state); } } - @SuppressWarnings("unchecked") @Override public V getValue(final String key) { final Map map = localMap.get(); - return (V) (map == null ? null : map.get(key)); + return map == null ? null : Cast.cast(map.get(key)); } @Override public Map getCopy() { final Map map = localMap.get(); - return map == null ? new HashMap() : new HashMap<>(map); + return map == null ? new HashMap<>() : new HashMap<>(map); } @Override @@ -239,13 +230,6 @@ public boolean equals(final Object obj) { final ThreadContextMap other = (ThreadContextMap) obj; final Map map = this.localMap.get(); final Map otherMap = other.getImmutableMapOrNull(); - if (map == null) { - if (otherMap != null) { - return false; - } - } else if (!map.equals(otherMap)) { - return false; - } - return true; + return Objects.equals(map, otherMap); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java index 7a07a897a78..cdbc4df1b49 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java @@ -32,6 +32,8 @@ */ public class DefaultThreadContextStack implements ThreadContextStack, StringBuilderFormattable { + private static final Object[] EMPTY_OBJECT_ARRAY = {}; + private static final long serialVersionUID = 5050501L; private static final ThreadLocal STACK = new ThreadLocal<>(); @@ -261,7 +263,7 @@ public Object[] toArray() { if (result == null) { return new String[0]; } - return result.toArray(new Object[result.size()]); + return result.toArray(EMPTY_OBJECT_ARRAY); } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLogger.java index 8e943e7f468..2ce95acfdc5 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLogger.java @@ -30,7 +30,7 @@ public interface ExtendedLogger extends Logger { /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -41,7 +41,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, Message message, Throwable t); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -52,7 +52,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, CharSequence message, Throwable t); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -63,7 +63,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, Object message, Throwable t); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -74,7 +74,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, String message, Throwable t); /** - * Determine if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -84,7 +84,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, String message); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -95,7 +95,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, String message, Object... params); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -106,7 +106,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, String message, Object p0); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -118,7 +118,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -131,7 +131,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1, Object p2); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -145,7 +145,7 @@ public interface ExtendedLogger extends Logger { boolean isEnabled(Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -161,7 +161,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object Object p4); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -196,7 +196,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object Object p4, Object p5, Object p6); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -215,7 +215,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object Object p4, Object p5, Object p6, Object p7); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -235,7 +235,7 @@ boolean isEnabled(Level level, Marker marker, String message, Object p0, Object Object p4, Object p5, Object p6, Object p7, Object p8); /** - * Determines if logging is enabled. + * Tests if logging is enabled. * * @param level The logging Level to check. * @param marker A Marker or null. @@ -498,17 +498,17 @@ void logIfEnabled(String fqcn, Level level, Marker marker, String message, Objec void logIfEnabled(String fqcn, Level level, Marker marker, String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9); - /** - * Always logs a message at the specified level. It is the responsibility of the caller to ensure the specified - * level is enabled. - * - * @param fqcn The fully qualified class name of the logger entry point, used to determine the caller class and - * method when location information needs to be logged. - * @param level The logging Level to check. - * @param marker A Marker or null. - * @param message The Message. - * @param t the exception to log, including its stack trace. - */ + /** + * Logs a message at the specified level. It is the responsibility of the caller to ensure the specified + * level is enabled. + * + * @param fqcn The fully qualified class name of the logger entry point, used to determine the caller class and + * method when location information needs to be logged. + * @param level The logging Level to check. + * @param marker A Marker or null. + * @param message The Message. + * @param t the exception to log, including its stack trace. + */ void logMessage(String fqcn, Level level, Marker marker, Message message, Throwable t); /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLoggerWrapper.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLoggerWrapper.java index c3fdc8824b3..6c9c6c9a8c9 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLoggerWrapper.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLoggerWrapper.java @@ -20,6 +20,7 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.util.StackLocatorUtil; /** * Wrapper class that exposes the protected AbstractLogger methods to support wrapped loggers. @@ -214,6 +215,10 @@ public boolean isEnabled(final Level level, final Marker marker, final String me @Override public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { - logger.logMessage(fqcn, level, marker, message, t); + if (requiresLocation()) { + logger.logMessage(level, marker, fqcn, StackLocatorUtil.calcLocation(fqcn), message, t); + } else { + logger.logMessage(fqcn, level, marker, message, t); + } } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java index 186e2a8bece..2a258c441cc 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java @@ -19,11 +19,15 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.StringMap; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; + +import static org.apache.logging.log4j.spi.LoggingSystem.THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY; +import static org.apache.logging.log4j.spi.LoggingSystemProperties.*; /** * {@code SortedArrayStringMap}-based implementation of the {@code ThreadContextMap} interface that attempts not to @@ -34,52 +38,28 @@ *

* @since 2.7 */ -class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap { - - /** - * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain - * {@code ThreadLocal} (value is not "true") in the implementation. - */ - public static final String INHERITABLE_MAP = "isThreadContextMapInheritable"; - - /** - * The default initial capacity. - */ - protected static final int DEFAULT_INITIAL_CAPACITY = 16; - - /** - * System property name that can be used to control the data structure's initial capacity. - */ - protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity"; +class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ThreadContextMap { protected final ThreadLocal localMap; - - private static volatile int initialCapacity; - private static volatile boolean inheritableMap; + protected final int initialCapacity; - /** - * Initializes static variables based on system properties. Normally called when this class is initialized by the VM - * and when Log4j is reconfigured. - */ - static void init() { - final PropertiesUtil properties = PropertiesUtil.getProperties(); - initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY); - inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP); - } - - static { - init(); + public GarbageFreeSortedArrayThreadContextMap() { + this( + PropertiesUtil.getProperties().getBooleanProperty(THREAD_CONTEXT_MAP_INHERITABLE), + PropertiesUtil.getProperties().getIntegerProperty(THREAD_CONTEXT_INITIAL_CAPACITY, THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY) + ); } - public GarbageFreeSortedArrayThreadContextMap() { - this.localMap = createThreadLocalMap(); + GarbageFreeSortedArrayThreadContextMap(final boolean inheritableMap, final int initialCapacity) { + this.localMap = createThreadLocalMap(inheritableMap); + this.initialCapacity = initialCapacity; } // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. // (This method is package protected for JUnit tests.) - private ThreadLocal createThreadLocalMap() { + private ThreadLocal createThreadLocalMap(final boolean inheritableMap) { if (inheritableMap) { - return new InheritableThreadLocal() { + return new InheritableThreadLocal<>() { @Override protected StringMap childValue(final StringMap parentValue) { return parentValue != null ? createStringMap(parentValue) : null; @@ -98,6 +78,18 @@ protected StringMap childValue(final StringMap parentValue) { * @return an implementation of the {@code StringMap} used to back this thread context map */ protected StringMap createStringMap() { + return createStringMap(initialCapacity); + } + + /** + * Returns an implementation of the {@code StringMap} used to back this thread context map. + *

+ * Subclasses may override. + *

+ * @return an implementation of the {@code StringMap} used to back this thread context map + * @param initialCapacity initial capacity of the StringMap + */ + protected StringMap createStringMap(final int initialCapacity) { return new SortedArrayStringMap(initialCapacity); } @@ -157,13 +149,13 @@ public void putAllValues(final Map values) { @Override public String get(final String key) { - return (String) getValue(key); + return getValue(key); } @Override public V getValue(final String key) { final StringMap map = localMap.get(); - return map == null ? null : map.getValue(key); + return map == null ? null : map.getValue(key); } @Override @@ -201,7 +193,7 @@ public boolean containsKey(final String key) { @Override public Map getCopy() { final StringMap map = localMap.get(); - return map == null ? new HashMap() : map.toMap(); + return map == null ? new HashMap<>() : map.toMap(); } /** @@ -258,13 +250,6 @@ public boolean equals(final Object obj) { final ThreadContextMap other = (ThreadContextMap) obj; final Map map = this.getImmutableMapOrNull(); final Map otherMap = other.getImmutableMapOrNull(); - if (map == null) { - if (otherMap != null) { - return false; - } - } else if (!map.equals(otherMap)) { - return false; - } - return true; + return Objects.equals(map, otherMap); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java index 88c3b7e898c..c4ce580266f 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContext.java @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.spi; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.MessageFactory; /** @@ -24,20 +25,50 @@ public interface LoggerContext { /** - * An anchor for some other context, such as a ClassLoader or ServletContext. + * Empty array. + */ + LoggerContext[] EMPTY_ARRAY = {}; + + /** + * Gets the anchor for some other context, such as a ClassLoader or ServletContext. * @return The external context. */ Object getExternalContext(); /** - * Returns an ExtendedLogger. + * Gets an ExtendedLogger using the fully qualified name of the Class as the Logger name. + * @param cls The Class whose name should be used as the Logger name. + * @return The logger. + * @since 2.14.0 + */ + default ExtendedLogger getLogger(final Class cls) { + final String canonicalName = cls.getCanonicalName(); + return getLogger(canonicalName != null ? canonicalName : cls.getName()); + } + + + /** + * Gets an ExtendedLogger using the fully qualified name of the Class as the Logger name. + * @param cls The Class whose name should be used as the Logger name. + * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the + * logger but will log a warning if mismatched. + * @return The logger. + * @since 2.14.0 + */ + default ExtendedLogger getLogger(final Class cls, final MessageFactory messageFactory) { + final String canonicalName = cls.getCanonicalName(); + return getLogger(canonicalName != null ? canonicalName : cls.getName(), messageFactory); + } + + /** + * Gets an ExtendedLogger. * @param name The name of the Logger to return. * @return The logger with the specified name. */ ExtendedLogger getLogger(String name); /** - * Returns an ExtendedLogger. + * Gets an ExtendedLogger. * @param name The name of the Logger to return. * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change * the logger but will log a warning if mismatched. @@ -46,27 +77,90 @@ public interface LoggerContext { ExtendedLogger getLogger(String name, MessageFactory messageFactory); /** - * Detects if a Logger with the specified name exists. + * Gets the LoggerRegistry. + * + * @return the LoggerRegistry. + * @since 2.17.2 + */ + default LoggerRegistry getLoggerRegistry() { + return null; + } + + /** + * Gets an object by its name. + * @param key The object's key. + * @return The Object that is associated with the key, if any. + * @since 2.13.0 + */ + default Object getObject(final String key) { + return null; + } + + /** + * Tests if a Logger with the specified name exists. * @param name The Logger name to search for. * @return true if the Logger exists, false otherwise. */ boolean hasLogger(String name); /** - * Detects if a Logger with the specified name and MessageFactory exists. + * Tests if a Logger with the specified name and MessageFactory type exists. * @param name The Logger name to search for. - * @param messageFactory The message factory to search for. + * @param messageFactoryClass The message factory class to search for. * @return true if the Logger exists, false otherwise. * @since 2.5 */ - boolean hasLogger(String name, MessageFactory messageFactory); + boolean hasLogger(String name, Class messageFactoryClass); /** - * Detects if a Logger with the specified name and MessageFactory type exists. + * Tests if a Logger with the specified name and MessageFactory exists. * @param name The Logger name to search for. - * @param messageFactoryClass The message factory class to search for. + * @param messageFactory The message factory to search for. * @return true if the Logger exists, false otherwise. * @since 2.5 */ - boolean hasLogger(String name, Class messageFactoryClass); + boolean hasLogger(String name, MessageFactory messageFactory); + + /** + * Associates an object into the LoggerContext by name for later use. + * @param key The object's key. + * @param value The object. + * @return The previous object or null. + * @since 2.13.0 + */ + default Object putObject(final String key, final Object value) { + return null; + } + + /** + * Associates an object into the LoggerContext by name for later use if an object is not already stored with that key. + * @param key The object's key. + * @param value The object. + * @return The previous object or null. + * @since 2.13.0 + */ + default Object putObjectIfAbsent(final String key, final Object value) { + return null; + } + + /** + * Removes an object if it is present. + * @param key The object's key. + * @return The object if it was present, null if it was not. + * @since 2.13.0 + */ + default Object removeObject(final String key) { + return null; + } + + /** + * Removes an object if it is present and the provided object is stored. + * @param key The object's key. + * @param value The object. + * @return The object if it was present, null if it was not. + * @since 2.13.0 + */ + default boolean removeObject(final String key, final Object value) { + return false; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java index 235ad0c14cc..ad29c57f361 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextFactory.java @@ -23,6 +23,37 @@ */ public interface LoggerContextFactory { + /** + * Shuts down the LoggerContext. + * @param fqcn The fully qualified class name of the caller. + * @param loader The ClassLoader to use or null. + * @param currentContext If true shuts down the current Context, if false shuts down the Context appropriate + * for the caller if a more appropriate Context can be determined. + * @param allContexts if true all LoggerContexts that can be located will be shutdown. + * @since 2.13.0 + */ + default void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext, final boolean allContexts) { + if (hasContext(fqcn, loader, currentContext)) { + final LoggerContext ctx = getContext(fqcn, loader, null, currentContext); + if (ctx instanceof Terminable) { + ((Terminable) ctx).terminate(); + } + } + } + + /** + * Checks to see if a LoggerContext is installed. The default implementation returns false. + * @param fqcn The fully qualified class name of the caller. + * @param loader The ClassLoader to use or null. + * @param currentContext If true returns the current Context, if false returns the Context appropriate + * for the caller if a more appropriate Context can be determined. + * @return true if a LoggerContext has been installed, false otherwise. + * @since 2.13.0 + */ + default boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + return false; + } + /** * Creates a {@link LoggerContext}. * @@ -56,4 +87,17 @@ LoggerContext getContext(String fqcn, ClassLoader loader, Object externalContext * @param context The context to remove. */ void removeContext(LoggerContext context); + + /** + * Determines whether or not this factory and perhaps the underlying + * ContextSelector behavior depend on the callers classloader. + * + * This method should be overridden by implementations, however a default method is provided which always + * returns {@code true} to preserve the old behavior. + * + * @since 2.15.0 + */ + default boolean isClassLoaderDependent() { + return true; + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextKey.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextKey.java deleted file mode 100644 index 113bc4546e8..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextKey.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.spi; - -import org.apache.logging.log4j.message.MessageFactory; - -/** - * Creates keys used in maps for use in LoggerContext implementations. - * - * @deprecated with no replacement - no longer used - * @since 2.5 - */ -@Deprecated -public class LoggerContextKey { - - public static String create(final String name) { - return create(name, AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS); - } - - public static String create(final String name, final MessageFactory messageFactory) { - final Class messageFactoryClass = messageFactory != null ? messageFactory.getClass() - : AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS; - return create(name, messageFactoryClass); - } - - public static String create(final String name, final Class messageFactoryClass) { - final Class mfClass = messageFactoryClass != null ? messageFactoryClass - : AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS; - return name + "." + mfClass.getName(); - } - -} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownAware.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownAware.java new file mode 100644 index 00000000000..d31c5834b1f --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownAware.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.spi; + +/** + * Interface allowing interested classes to know when a LoggerContext has shutdown - if the LoggerContext + * implementation provides a way to register listeners. + */ +public interface LoggerContextShutdownAware { + + void contextShutdown(LoggerContext loggerContext); +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownEnabled.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownEnabled.java new file mode 100644 index 00000000000..f3d59c1eee0 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownEnabled.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.spi; + +import java.util.List; + +/** + * LoggerContexts implementing this are able register LoggerContextShutdownAware classes. + */ +public interface LoggerContextShutdownEnabled { + + void addShutdownListener(LoggerContextShutdownAware listener); + + List getListeners(); +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java index 821f7a2f6e0..0e3e156aac4 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerRegistry.java @@ -22,7 +22,6 @@ import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import org.apache.logging.log4j.message.MessageFactory; @@ -30,7 +29,6 @@ * Convenience class to be used by {@code LoggerContext} implementations. */ public class LoggerRegistry { - private static final String DEFAULT_FACTORY_KEY = AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS.getName(); private final MapFactory factory; private final Map> map; @@ -63,7 +61,7 @@ public Map> createOuterMap() { @Override public void putIfAbsent(final Map innerMap, final String name, final T logger) { - ((ConcurrentMap) innerMap).putIfAbsent(name, logger); + innerMap.putIfAbsent(name, logger); } } @@ -89,7 +87,7 @@ public void putIfAbsent(final Map innerMap, final String name, final } public LoggerRegistry() { - this(new ConcurrentMapFactory()); + this(new ConcurrentMapFactory<>()); } public LoggerRegistry(final MapFactory factory) { @@ -97,12 +95,16 @@ public LoggerRegistry(final MapFactory factory) { this.map = factory.createOuterMap(); } + private static String defaultFactoryKey() { + return LoggingSystem.getMessageFactory().getClass().getName(); + } + private static String factoryClassKey(final Class messageFactoryClass) { - return messageFactoryClass == null ? DEFAULT_FACTORY_KEY : messageFactoryClass.getName(); + return messageFactoryClass == null ? defaultFactoryKey() : messageFactoryClass.getName(); } private static String factoryKey(final MessageFactory messageFactory) { - return messageFactory == null ? DEFAULT_FACTORY_KEY : messageFactory.getClass().getName(); + return messageFactory == null ? defaultFactoryKey() : messageFactory.getClass().getName(); } /** @@ -111,7 +113,7 @@ private static String factoryKey(final MessageFactory messageFactory) { * @return The logger with the specified name. */ public T getLogger(final String name) { - return getOrCreateInnerMap(DEFAULT_FACTORY_KEY).get(name); + return getOrCreateInnerMap(defaultFactoryKey()).get(name); } /** @@ -126,7 +128,7 @@ public T getLogger(final String name, final MessageFactory messageFactory) { } public Collection getLoggers() { - return getLoggers(new ArrayList()); + return getLoggers(new ArrayList<>()); } public Collection getLoggers(final Collection destination) { @@ -151,7 +153,7 @@ private Map getOrCreateInnerMap(final String factoryName) { * @return true if the Logger exists, false otherwise. */ public boolean hasLogger(final String name) { - return getOrCreateInnerMap(DEFAULT_FACTORY_KEY).containsKey(name); + return getOrCreateInnerMap(defaultFactoryKey()).containsKey(name); } /** diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java new file mode 100644 index 00000000000..3f7981fc629 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.message.DefaultFlowMessageFactory; +import org.apache.logging.log4j.message.FlowMessageFactory; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.message.ReusableMessageFactory; +import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; +import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.util.InternalApi; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.LowLevelLogUtil; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; +import org.apache.logging.log4j.util.ServiceRegistry; + +import static org.apache.logging.log4j.spi.LoggingSystemProperties.*; + +/** + * Handles initializing the Log4j API through {@link Provider} discovery. This keeps track of which + * {@link LoggerContextFactory} to use in {@link LogManager} along with factories for {@link ThreadContextMap} + * and {@link ThreadContextStack} to use in {@link ThreadContext}. + * + * @since 3.0.0 + */ +public class LoggingSystem { + /** + * Resource name for a Log4j 2 provider properties file. + */ + private static final String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties"; + private static final String API_VERSION = "Log4jAPIVersion"; + private static final String[] COMPATIBLE_API_VERSIONS = {"3.0.0"}; + + public static final int THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY = 16; + + private static final Lazy SYSTEM = Lazy.relaxed(LoggingSystem::new); + + private final Lock initializationLock = new ReentrantLock(); + private volatile SystemProvider provider; + private final Lazy environmentLazy = Lazy.relaxed(PropertiesUtil::getProperties); + private final Lazy loggerContextFactoryLazy = environmentLazy.map(environment -> + getProvider().createLoggerContextFactory(environment)); + private final Lazy messageFactoryLazy = environmentLazy.map(environment -> { + final String className = environment.getStringProperty(LOGGER_MESSAGE_FACTORY_CLASS); + if (className != null) { + final MessageFactory factory = createInstance(className, MessageFactory.class); + if (factory != null) { + return factory; + } + } + return Constants.isThreadLocalsEnabled() ? new ReusableMessageFactory() : new ParameterizedMessageFactory(); + }); + private final Lazy flowMessageFactoryLazy = environmentLazy.map(environment -> { + final String className = environment.getStringProperty(LOGGER_FLOW_MESSAGE_FACTORY_CLASS); + if (className != null) { + final FlowMessageFactory factory = createInstance(className, FlowMessageFactory.class); + if (factory != null) { + return factory; + } + } + return new DefaultFlowMessageFactory(); + }); + private final Lazy> threadContextMapFactoryLazy = environmentLazy.map(environment -> + () -> getProvider().createContextMap(environment)); + private final Lazy> threadContextStackFactoryLazy = environmentLazy.map(environment -> + () -> getProvider().createContextStack(environment)); + + /** + * Acquires a lock on the initialization of locating a logging system provider. This lock should be + * {@linkplain #releaseInitializationLock() released} once the logging system provider is loaded. This lock is + * provided to allow for lazy initialization via frameworks like OSGi to wait for a provider to be installed + * before allowing initialization to continue. + * + * @see LOG4J2-373 + */ + @InternalApi + public void acquireInitializationLock() { + initializationLock.lock(); + } + + /** + * Releases a lock on the initialization phase of this logging system. + */ + @InternalApi + public void releaseInitializationLock() { + initializationLock.unlock(); + } + + private SystemProvider getProvider() { + var provider = this.provider; + if (provider == null) { + try { + initializationLock.lockInterruptibly(); + provider = this.provider; + if (provider == null) { + this.provider = provider = findProvider(); + } + } catch (InterruptedException e) { + LowLevelLogUtil.logException("Interrupted before Log4j Providers could be loaded", e); + provider = new SystemProvider(); + Thread.currentThread().interrupt(); + } finally { + releaseInitializationLock(); + } + } + return provider; + } + + private SystemProvider findProvider() { + final SortedMap providers = new TreeMap<>(); + loadDefaultProviders().forEach(p -> providers.put(p.getPriority(), p)); + loadLegacyProviders().forEach(p -> providers.put(p.getPriority(), p)); + if (providers.isEmpty()) { + return new SystemProvider(); + } + final Provider provider = providers.get(providers.lastKey()); + if (providers.size() > 1) { + final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n"); + providers.forEach((i, p) -> sb.append(p).append('\n')); + sb.append("Using ").append(provider); + LowLevelLogUtil.log(sb.toString()); + } + return new SystemProvider(provider); + } + + public void setLoggerContextFactory(final LoggerContextFactory loggerContextFactory) { + loggerContextFactoryLazy.set(loggerContextFactory); + } + + public void setMessageFactory(final MessageFactory messageFactory) { + messageFactoryLazy.set(messageFactory); + } + + public void setFlowMessageFactory(final FlowMessageFactory flowMessageFactory) { + flowMessageFactoryLazy.set(flowMessageFactory); + } + + public void setThreadContextMapFactory(final Supplier threadContextMapFactory) { + threadContextMapFactoryLazy.set(threadContextMapFactory); + } + + public void setThreadContextStackFactory(final Supplier threadContextStackFactory) { + threadContextStackFactoryLazy.set(threadContextStackFactory); + } + + /** + * Gets the LoggingSystem instance. + */ + public static LoggingSystem getInstance() { + return SYSTEM.value(); + } + + /** + * Gets the current LoggerContextFactory. This may initialize the instance if this is the first time it was + * requested. + */ + public static LoggerContextFactory getLoggerContextFactory() { + return getInstance().loggerContextFactoryLazy.value(); + } + + public static MessageFactory getMessageFactory() { + return getInstance().messageFactoryLazy.value(); + } + + public static FlowMessageFactory getFlowMessageFactory() { + return getInstance().flowMessageFactoryLazy.value(); + } + + /** + * Creates a new ThreadContextMap. + */ + public static ThreadContextMap createContextMap() { + return getInstance().threadContextMapFactoryLazy.value().get(); + } + + /** + * Creates a new ThreadContextStack. + */ + public static ThreadContextStack createContextStack() { + return getInstance().threadContextStackFactoryLazy.value().get(); + } + + private static List loadDefaultProviders() { + return ServiceRegistry.getInstance() + .getServices(Provider.class, MethodHandles.lookup(), provider -> validVersion(provider.getVersions())); + } + + private static List loadLegacyProviders() { + return LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false) + .stream() + .map(urlResource -> loadLegacyProvider(urlResource.getUrl(), urlResource.getClassLoader())) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static Provider loadLegacyProvider(final URL url, final ClassLoader classLoader) { + final Properties properties = new Properties(); + try (final InputStream in = url.openStream()) { + properties.load(in); + } catch (IOException e) { + LowLevelLogUtil.logException("Unable to load file " + url, e); + return null; + } + if (validVersion(properties.getProperty(API_VERSION))) { + return new Provider(properties, url, classLoader); + } + return null; + } + + private static boolean validVersion(final String version) { + for (final String v : COMPATIBLE_API_VERSIONS) { + if (version.startsWith(v)) { + return true; + } + } + return false; + } + + private static T tryInstantiate(final Class clazz) { + Constructor constructor; + try { + constructor = clazz.getConstructor(); + } catch (final NoSuchMethodException ignored) { + try { + constructor = clazz.getDeclaredConstructor(); + if (!(constructor.canAccess(null) || constructor.trySetAccessible())) { + LowLevelLogUtil.log("Unable to access constructor for " + clazz); + return null; + } + } catch (final NoSuchMethodException e) { + LowLevelLogUtil.logException("Unable to find a default constructor for " + clazz, e); + return null; + } + } + try { + return constructor.newInstance(); + } catch (final InvocationTargetException e) { + LowLevelLogUtil.logException("Exception thrown by constructor for " + clazz, e.getCause()); + } catch (final InstantiationException | LinkageError e) { + LowLevelLogUtil.logException("Unable to create instance of " + clazz, e); + } catch (final IllegalAccessException e) { + LowLevelLogUtil.logException("Unable to access constructor for " + clazz, e); + } + return null; + } + + private static T createInstance(final String className, final Class type) { + try { + final Class loadedClass = LoaderUtil.loadClass(className); + final Class typedClass = loadedClass.asSubclass(type); + return tryInstantiate(typedClass); + } catch (final ClassNotFoundException | ClassCastException e) { + LowLevelLogUtil.logException(String.format("Unable to load %s class '%s'", type.getSimpleName(), className), e); + return null; + } + } + + private static class SystemProvider { + private final Provider provider; + + private SystemProvider() { + this(null); + } + + private SystemProvider(final Provider provider) { + this.provider = provider; + } + + public LoggerContextFactory createLoggerContextFactory(final PropertyEnvironment environment) { + final String customFactoryClass = environment.getStringProperty(LogManager.FACTORY_PROPERTY_NAME); + if (customFactoryClass != null) { + final LoggerContextFactory customFactory = createInstance(customFactoryClass, LoggerContextFactory.class); + if (customFactory != null) { + return customFactory; + } + } + if (provider != null) { + final Class factoryClass = provider.loadLoggerContextFactory(); + if (factoryClass != null) { + final LoggerContextFactory factory = tryInstantiate(factoryClass); + if (factory != null) { + return factory; + } + } + } + LowLevelLogUtil.log("Log4j could not find a logging implementation. " + + "Please add log4j-core dependencies to classpath or module path. " + + "Using SimpleLogger to log to the console."); + return SimpleLoggerContextFactory.INSTANCE; + } + + /** + * Creates the ThreadContextMap instance used by the ThreadContext. + *

+ * If {@linkplain Constants#isThreadLocalsEnabled() Log4j can use ThreadLocals}, a garbage-free StringMap-based context map can + * be installed by setting system property {@value LoggingSystemProperties#THREAD_CONTEXT_GARBAGE_FREE_ENABLED} to {@code true}. + *

+ * Furthermore, any custom {@code ThreadContextMap} can be installed by setting system property + * {@value LoggingSystemProperties#THREAD_CONTEXT_MAP_CLASS} to the fully qualified class name of the class implementing the + * {@code ThreadContextMap} interface. (Also implement the {@code ReadOnlyThreadContextMap} interface if your custom + * {@code ThreadContextMap} implementation should be accessible to applications via the + * {@link ThreadContext#getThreadContextMap()} method.) + *

+ * Instead of system properties, the above can also be specified in a properties file named + * {@code log4j2.component.properties} in the classpath. + *

+ * + * @see ThreadContextMap + * @see ReadOnlyThreadContextMap + * @see org.apache.logging.log4j.ThreadContext + */ + public ThreadContextMap createContextMap(final PropertyEnvironment environment) { + final String customThreadContextMap = environment.getStringProperty(THREAD_CONTEXT_MAP_CLASS); + if (customThreadContextMap != null) { + final ThreadContextMap customContextMap = createInstance(customThreadContextMap, ThreadContextMap.class); + if (customContextMap != null) { + return customContextMap; + } + } + final boolean disableMap = environment.getBooleanProperty(THREAD_CONTEXT_MAP_DISABLED, + environment.getBooleanProperty(THREAD_CONTEXT_DISABLED)); + if (disableMap) { + return new NoOpThreadContextMap(); + } + final Class mapClass = provider.loadThreadContextMap(); + if (mapClass != null) { + final ThreadContextMap map = tryInstantiate(mapClass); + if (map != null) { + return map; + } + } + final boolean threadLocalsEnabled = Constants.isThreadLocalsEnabled(); + final boolean garbageFreeEnabled = environment.getBooleanProperty(THREAD_CONTEXT_GARBAGE_FREE_ENABLED); + final boolean inheritableMap = environment.getBooleanProperty(THREAD_CONTEXT_MAP_INHERITABLE); + final int initialCapacity = environment.getIntegerProperty(THREAD_CONTEXT_INITIAL_CAPACITY, + THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY); + if (threadLocalsEnabled) { + if (garbageFreeEnabled) { + return new GarbageFreeSortedArrayThreadContextMap(inheritableMap, initialCapacity); + } + return new CopyOnWriteSortedArrayThreadContextMap(inheritableMap, initialCapacity); + } + return new DefaultThreadContextMap(true, inheritableMap); + } + + public ThreadContextStack createContextStack(final PropertyEnvironment environment) { + final boolean disableStack = environment.getBooleanProperty(THREAD_CONTEXT_STACK_DISABLED, + environment.getBooleanProperty(THREAD_CONTEXT_DISABLED)); + return new DefaultThreadContextStack(!disableStack); + } + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperties.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperties.java new file mode 100644 index 00000000000..47fdd20741c --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperties.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.spi; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.message.FlowMessageFactory; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.util.EnvironmentPropertySource; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.Unbox; + +/** + * Centralized list of property name constants that can be configured in the Log4j API. These properties may be + * specified as system properties, environment variables (see {@link EnvironmentPropertySource}), or in a classpath + * resource file named {@code log4j2.component.properties}. + * + * @since 3.0.0 + * @see PropertiesUtil + */ +public final class LoggingSystemProperties { + // TODO: rename properties according to established theme in + // https://cwiki.apache.org/confluence/display/LOGGING/Properties+Enhancement + + /** + * Property to enable TRACE-level debug logging in the Log4j system itself. + *

+ * If property {@value} is either defined empty or its value equals to {@code true} (ignoring case), all internal + * logging will be printed to the console. The presence of this system property overrides any value set in the + * configuration's {@code } status attribute. + *

+ */ + public static final String SYSTEM_DEBUG = "log4j2.*.System.debug"; + + /** + * Property to override webapp detection. Without this property, the presence of the {@code Servlet} interface + * (from either {@code javax} or {@code jakarta}) is checked to see if this is a webapp. + */ + // Web.enableWebApp : calculate | true | false + public static final String SYSTEM_IS_WEBAPP = "log4j2.isWebapp"; + + /** + * Property to override the use of thread-local values for garbage-free logging. + * + * @see LOG4J2-1270 + */ + // GC.enableThreadLocals : calculate | true | false + public static final String SYSTEM_THREAD_LOCALS_ENABLED = "log4j2.enableThreadlocals"; + + /** + * Property to ignore the thread context ClassLoader when set to {@code true}. + * + * @see LoaderUtil + */ + // TODO: see if this can be removed + public static final String LOADER_IGNORE_THREAD_CONTEXT_LOADER = "log4j2.ignoreTCL"; + + /** + * Property to force use of the thread context ClassLoader. + * + * @see LoaderUtil + */ + // TODO: see if this can be removed + public static final String LOADER_FORCE_THREAD_CONTEXT_LOADER = "log4j2.forceTCLOnly"; + + /** + * Property to override the default ringbuffer size used in {@link Unbox}. The default value is 32. + */ + // GC.unboxRingBufferSize + public static final String UNBOX_RING_BUFFER_SIZE = "log4j2.unboxRingbufferSize"; + + /** + * Property to set to the fully qualified class name of a custom implementation of {@link LoggerContextFactory}. + */ + // LoggerContext.factory + public static final String LOGGER_CONTEXT_FACTORY_CLASS = "log4j2.loggerContextFactory"; + + /** + * Property to override the default {@link MessageFactory} class. + */ + // Message.messageFactory + public static final String LOGGER_MESSAGE_FACTORY_CLASS = "log4j2.messageFactory"; + + /** + * Property to override the default {@link FlowMessageFactory} class. + */ + // Message.flowMessageFactory + public static final String LOGGER_FLOW_MESSAGE_FACTORY_CLASS = "log4j2.flowMessageFactory"; + + /** + * Property to override the default maximum nesting depth of map messages to format in JSON output. The + * default value is 8. + */ + // Message.jsonFormatterMaxDepth + public static final String LOGGER_MAP_MESSAGE_JSON_FORMATTER_MAX_DEPTH = "log4j2.mapMessageJsonFormatterMaxDepth"; + + /** + * Property to override the maximum size of the StringBuilder instances used in ringbuffer log events to store + * the contents of reusable messages. After delivering messages above this size to appenders, the StringBuilder + * is trimmed to this maximum size. The default value is 518 which allows the StringBuilder to resize three times + * from its initial size. + */ + // GC.maxReusableMsgSize + public static final String GC_REUSABLE_MESSAGE_MAX_SIZE = "log4j2.maxReusableMsgSize"; + + public static final String SIMPLE_SHOW_CONTEXT_MAP = "SimpleLogger.showContextMap"; + public static final String SIMPLE_SHOW_LOG_NAME = "SimpleLogger.showLogName"; + public static final String SIMPLE_SHOW_SHORT_LOG_NAME = "SimpleLogger.showShortLogName"; + public static final String SIMPLE_SHOW_DATE_TIME = "SimpleLogger.showDateTime"; + public static final String SIMPLE_DATE_TIME_FORMAT = "SimpleLogger.dateTimeFormat"; + public static final String SIMPLE_LOG_FILE = "SimpleLogger.logFile"; + public static final String SIMPLE_LOG_LEVEL = "SimpleLogger.logLevel"; + public static final String SIMPLE_LOGGER_LOG_LEVEL = "SimpleLogger.%s.level"; + + /** + * Property that can be configured with the maximum number of status data entries to keep queued. Once the limit is + * reached, older entries will be removed as new entries are added. The default value is 200. + */ + // StatusLogger.entries + public static final String STATUS_MAX_ENTRIES = "log4j2.statusEntries"; + + /** + * Property that can be configured with the {@link Level} name to use as the default level for + * {@link StatusListener}s. The default value is {@link Level#WARN}. + */ + // StatusLogger.statusLoggerLevel + public static final String STATUS_DEFAULT_LISTENER_LEVEL = "log4j2.statusLoggerLevel"; + + /** + * Property that can be configured with a date-time format string to use as the format for timestamps + * in the status logger output. See {@link java.text.SimpleDateFormat} for supported formats. + */ + // StatusLogger.dateFormat + public static final String STATUS_DATE_FORMAT = "log4j2.statusLoggerDateFormat"; + + /** + * Property to control whether {@link ThreadContext} stores map data. If set to {@code true}, then the + * thread context map will be disabled. + */ + // ThreadContext.enableMap + public static final String THREAD_CONTEXT_MAP_DISABLED = "log4j2.disableThreadContextMap"; + + /** + * Property to control whether {@link ThreadContext} stores stack data. If set to {@code true}, then the + * thread context stack will be disabled. + */ + // ThreadContext.enableStack + public static final String THREAD_CONTEXT_STACK_DISABLED = "log4j2.disableThreadContextStack"; + + /** + * Property to control whether {@link ThreadContext} stores any data. If set to {@code true}, then the + * thread context map and stack will be disabled. + */ + // ThreadContext.enabled + public static final String THREAD_CONTEXT_DISABLED = "log4j2.disableThreadContext"; + + /** + * Property to control whether {@link ThreadContextMap} uses {@link InheritableThreadLocal} when {@code true} or + * {@link ThreadLocal} otherwise for holding map data. + */ + // ThreadContext.inheritable + public static final String THREAD_CONTEXT_MAP_INHERITABLE = "log4j2.isThreadContextMapInheritable"; + + /** + * Property to override the default {@link ThreadContextMap} class. Note that implementation classes should + * also implement {@link ReadOnlyThreadContextMap} if they should be accessible to applications via + * {@link ThreadContext#getThreadContextMap()}. + */ + // TODO: replace with LoggingSystem overrides + public static final String THREAD_CONTEXT_MAP_CLASS = "log4j2.threadContextMap"; + + /** + * Property to override the initial capacity of the thread context map. The default value is 16. + */ + // ThreadContext.initialCapacity + public static final String THREAD_CONTEXT_INITIAL_CAPACITY = "log4j2.threadContextInitialCapacity"; + + /** + * Property to override whether to use a garbage-free implementation of {@link ThreadContextMap}. + */ + // ThreadContext.garbageFree + public static final String THREAD_CONTEXT_GARBAGE_FREE_ENABLED = "log4j2.garbagefreeThreadContextMap"; + + private LoggingSystemProperties() { + throw new UnsupportedOperationException("Utility class"); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java deleted file mode 100644 index ff31515ef8f..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.spi; - -import java.util.Objects; - -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.message.MessageFactory2; -import org.apache.logging.log4j.message.SimpleMessage; - -/** - * Adapts a legacy MessageFactory to the new MessageFactory2 interface. - * - * @since 2.6 - */ -public class MessageFactory2Adapter implements MessageFactory2 { - private final MessageFactory wrapped; - - public MessageFactory2Adapter(final MessageFactory wrapped) { - this.wrapped = Objects.requireNonNull(wrapped); - } - - public MessageFactory getOriginal() { - return wrapped; - } - - @Override - public Message newMessage(final CharSequence charSequence) { - return new SimpleMessage(charSequence); - } - - @Override - public Message newMessage(final String message, final Object p0) { - return wrapped.newMessage(message, p0); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1) { - return wrapped.newMessage(message, p0, p1); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) { - return wrapped.newMessage(message, p0, p1, p2); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, - final Object p3) { - return wrapped.newMessage(message, p0, p1, p2, p3); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4) { - return wrapped.newMessage(message, p0, p1, p2, p3, p4); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { - return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { - return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, final Object p7) { - return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) { - return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8); - } - - @Override - public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) { - return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); - } - - @Override - public Message newMessage(final Object message) { - return wrapped.newMessage(message); - } - - @Override - public Message newMessage(final String message) { - return wrapped.newMessage(message); - } - - @Override - public Message newMessage(final String message, final Object... params) { - return wrapped.newMessage(message, params); - } -} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java index 03c77da7454..ab18cc55064 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Objects; import org.apache.logging.log4j.ThreadContext.ContextStack; import org.apache.logging.log4j.util.StringBuilderFormattable; @@ -46,7 +47,7 @@ public MutableThreadContextStack() { /** * Constructs a new instance. - * @param list + * @param list The list of items in the stack. */ public MutableThreadContextStack(final List list) { this.list = new ArrayList<>(list); @@ -69,8 +70,7 @@ public String pop() { return null; } final int last = list.size() - 1; - final String result = list.remove(last); - return result; + return list.remove(last); } @Override @@ -230,14 +230,7 @@ public boolean equals(final Object obj) { } final ThreadContextStack other = (ThreadContextStack) obj; final List otherAsList = other.asList(); - if (this.list == null) { - if (otherAsList != null) { - return false; - } - } else if (!this.list.equals(otherAsList)) { - return false; - } - return true; + return Objects.equals(list, otherAsList); } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/NoOpThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/NoOpThreadContextMap.java index 2f858fce32d..c458b88a9e3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/NoOpThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/NoOpThreadContextMap.java @@ -20,8 +20,9 @@ import java.util.Map; /** - * {@code ThreadContextMap} implementation used when either of system properties {@code disableThreadContextMap} or . - * {@code disableThreadContext} is {@code true}. This implementation does nothing. + * {@code ThreadContextMap} implementation used when either of system properties + * {@value LoggingSystemProperties#THREAD_CONTEXT_MAP_DISABLED} or + * {@value LoggingSystemProperties#THREAD_CONTEXT_DISABLED} is {@code true}. This implementation does nothing. * * @since 2.7 */ @@ -62,4 +63,25 @@ public void put(final String key, final String value) { @Override public void remove(final String key) { } + + @Override + public void putAll(final Map map) { + } + + @Override + public void removeAll(final Iterable keys) { + } + + @Override + public V getValue(final String key) { + return null; + } + + @Override + public void putValue(final String key, final V value) { + } + + @Override + public void putAllValues(final Map values) { + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ObjectThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ObjectThreadContextMap.java index 2148ddbc330..aa07de1baf5 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ObjectThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ObjectThreadContextMap.java @@ -16,39 +16,14 @@ */ package org.apache.logging.log4j.spi; -import java.util.Map; - /** - * Extension service provider interface to allow putting Object values in the - * {@link org.apache.logging.log4j.ThreadContext}. + * Legacy interface for backward compatibility with extensions to ThreadContextMap. + * These methods have since been moved to default methods on ThreadContextMap. * * @see ThreadContextMap * @since 2.8 + * @deprecated use {@link ThreadContextMap} directly */ +@Deprecated(since = "3.0.0") public interface ObjectThreadContextMap extends CleanableThreadContextMap { - - /** - * Returns the Object value for the specified key, or {@code null} if the specified key does not exist in this - * collection. - * - * @param key the key whose value to return - * @return the value for the specified key or {@code null} - */ - V getValue(String key); - - /** - * Puts the specified key-value pair into the collection. - * - * @param key the key to add or remove. Keys may be {@code null}. - * @param value the value to add. Values may be {@code null}. - */ - void putValue(String key, V value); - - /** - * Puts all given key-value pairs into the collection. - * - * @param values the map of key-value pairs to add - */ - void putAllValues(Map values); - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java index 5808cf8d2ea..833b2d1318b 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java @@ -189,7 +189,7 @@ public Class loadThreadContextMap() { public URL getUrl() { return url; } - + @Override public String toString() { final StringBuilder result = new StringBuilder("Provider["); @@ -220,7 +220,7 @@ public String toString() { } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } @@ -228,7 +228,7 @@ public boolean equals(Object o) { return false; } - Provider provider = (Provider) o; + final Provider provider = (Provider) o; if (priority != null ? !priority.equals(provider.priority) : provider.priority != null) { return false; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap.java index 00baba787bb..8560097e18a 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap.java @@ -19,6 +19,9 @@ import java.util.Map; import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.util.Cast; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; /** * Service provider interface to implement custom MDC behavior for {@link org.apache.logging.log4j.ThreadContext}. @@ -26,6 +29,9 @@ * Since 2.8, {@code ThreadContextMap} implementations that implement the {@link ReadOnlyThreadContextMap} interface * are accessible to applications via the {@link ThreadContext#getThreadContextMap()} method. *

+ *

+ * Since 3.0.0, {@code ThreadContextMap} combines all its extension interfaces into default methods on this interface. + *

*/ public interface ThreadContextMap { @@ -81,9 +87,90 @@ public interface ThreadContextMap { void put(final String key, final String value); /** - * Removes the the context identified by the key + * Removes the context identified by the key * parameter. * @param key The key to remove. */ void remove(final String key); + + /** + * Puts all given context map entries into the current thread's + * context map. + * + *

If the current thread does not have a context map it is + * created as a side effect.

+ * @param map The map. + * @since 3.0.0 + */ + default void putAll(Map map) { + map.forEach(this::put); + } + + /** + * Returns the context data for reading. Note that regardless of whether the returned context data has been + * {@linkplain StringMap#freeze() frozen} (made read-only) or not, callers should not attempt to modify + * the returned data structure. + * + * @return the {@code StringMap} + * @since 3.0.0 + */ + default StringMap getReadOnlyContextData() { + if (this instanceof ReadOnlyThreadContextMap) { + return ((ReadOnlyThreadContextMap) this).getReadOnlyContextData(); + } + final Map copy = getCopy(); + StringMap map = new SortedArrayStringMap(copy.size()); + copy.forEach(map::putValue); + map.freeze(); + return map; + } + + /** + * Removes all given context map keys from the current thread's context map. + * + *

If the current thread does not have a context map it is + * created as a side effect.

+ + * @param keys The keys. + * @since 3.0.0 + */ + default void removeAll(Iterable keys) { + keys.forEach(this::remove); + } + + /** + * Returns the Object value for the specified key, or {@code null} if the specified key does not exist in this + * collection. + * + * @param key the key whose value to return + * @param The type of the returned value. + * @return the value for the specified key or {@code null} + * @since 3.0.0 + */ + default V getValue(String key) { + return Cast.cast(get(key)); + } + + /** + * Puts the specified key-value pair into the collection. + * + * @param key the key to add or remove. Keys may be {@code null}. + * @param The type of the stored and returned value. + * @param value the value to add. Values may be {@code null}. + * @since 3.0.0 + */ + default void putValue(String key, V value) { + put(key, value != null ? value.toString() : null); + } + + /** + * Puts all given key-value pairs into the collection. + * + * @param values the map of key-value pairs to add + * @param The type of the value being added. + * @since 3.0.0 + */ + default void putAllValues(Map values) { + values.forEach(this::putValue); + } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap2.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap2.java index b48d5ab90bf..1ec419da32d 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap2.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMap2.java @@ -16,38 +16,14 @@ */ package org.apache.logging.log4j.spi; -import java.util.Map; - -import org.apache.logging.log4j.util.StringMap; - /** - * Extension service provider interface to implement additional custom MDC behavior for - * {@link org.apache.logging.log4j.ThreadContext}. - * - * Consider implementing {@link CleanableThreadContextMap} instead. + * Legacy interface for backward compatibility with extensions to ThreadContextMap. + * These methods have since been moved to default methods on ThreadContextMap. * * @see ThreadContextMap * @since 2.7 + * @deprecated use {@link ThreadContextMap} directly */ +@Deprecated(since = "3.0.0") public interface ThreadContextMap2 extends ThreadContextMap { - - /** - * Puts all given context map entries into the current thread's - * context map. - * - *

If the current thread does not have a context map it is - * created as a side effect.

- * @param map The map. - * @since 2.7 - */ - void putAll(final Map map); - - /** - * Returns the context data for reading. Note that regardless of whether the returned context data has been - * {@linkplain StringMap#freeze() frozen} (made read-only) or not, callers should not attempt to modify - * the returned data structure. - * - * @return the {@code StringMap} - */ - StringMap getReadOnlyContextData(); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java deleted file mode 100644 index c67fbcd699e..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.spi; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Constants; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.apache.logging.log4j.util.ProviderUtil; - -/** - * Creates the ThreadContextMap instance used by the ThreadContext. - *

- * If {@link Constants#ENABLE_THREADLOCALS Log4j can use ThreadLocals}, a garbage-free StringMap-based context map can - * be installed by setting system property {@code log4j2.garbagefree.threadContextMap} to {@code true}. - *

- * Furthermore, any custom {@code ThreadContextMap} can be installed by setting system property - * {@code log4j2.threadContextMap} to the fully qualified class name of the class implementing the - * {@code ThreadContextMap} interface. (Also implement the {@code ReadOnlyThreadContextMap} interface if your custom - * {@code ThreadContextMap} implementation should be accessible to applications via the - * {@link ThreadContext#getThreadContextMap()} method.) - *

- * Instead of system properties, the above can also be specified in a properties file named - * {@code log4j2.component.properties} in the classpath. - *

- * - * @see ThreadContextMap - * @see ReadOnlyThreadContextMap - * @see org.apache.logging.log4j.ThreadContext - * @since 2.7 - */ -public final class ThreadContextMapFactory { - private static final Logger LOGGER = StatusLogger.getLogger(); - private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap"; - private static final String GC_FREE_THREAD_CONTEXT_KEY = "log4j2.garbagefree.threadContextMap"; - - private static boolean GcFreeThreadContextKey; - private static String ThreadContextMapName; - - static { - initPrivate(); - } - - /** - * Initializes static variables based on system properties. Normally called when this class is initialized by the VM - * and when Log4j is reconfigured. - */ - public static void init() { - CopyOnWriteSortedArrayThreadContextMap.init(); - GarbageFreeSortedArrayThreadContextMap.init(); - DefaultThreadContextMap.init(); - initPrivate(); - } - - /** - * Initializes static variables based on system properties. Normally called when this class is initialized by the VM - * and when Log4j is reconfigured. - */ - private static void initPrivate() { - final PropertiesUtil properties = PropertiesUtil.getProperties(); - ThreadContextMapName = properties.getStringProperty(THREAD_CONTEXT_KEY); - GcFreeThreadContextKey = properties.getBooleanProperty(GC_FREE_THREAD_CONTEXT_KEY); - } - - private ThreadContextMapFactory() { - } - - public static ThreadContextMap createThreadContextMap() { - final ClassLoader cl = ProviderUtil.findClassLoader(); - ThreadContextMap result = null; - if (ThreadContextMapName != null) { - try { - final Class clazz = cl.loadClass(ThreadContextMapName); - if (ThreadContextMap.class.isAssignableFrom(clazz)) { - result = (ThreadContextMap) clazz.newInstance(); - } - } catch (final ClassNotFoundException cnfe) { - LOGGER.error("Unable to locate configured ThreadContextMap {}", ThreadContextMapName); - } catch (final Exception ex) { - LOGGER.error("Unable to create configured ThreadContextMap {}", ThreadContextMapName, ex); - } - } - if (result == null && ProviderUtil.hasProviders() && LogManager.getFactory() != null) { //LOG4J2-1658 - final String factoryClassName = LogManager.getFactory().getClass().getName(); - for (final Provider provider : ProviderUtil.getProviders()) { - if (factoryClassName.equals(provider.getClassName())) { - final Class clazz = provider.loadThreadContextMap(); - if (clazz != null) { - try { - result = clazz.newInstance(); - break; - } catch (final Exception e) { - LOGGER.error("Unable to locate or load configured ThreadContextMap {}", - provider.getThreadContextMap(), e); - result = createDefaultThreadContextMap(); - } - } - } - } - } - if (result == null) { - result = createDefaultThreadContextMap(); - } - return result; - } - - private static ThreadContextMap createDefaultThreadContextMap() { - if (Constants.ENABLE_THREADLOCALS) { - if (GcFreeThreadContextKey) { - return new GarbageFreeSortedArrayThreadContextMap(); - } - return new CopyOnWriteSortedArrayThreadContextMap(); - } - return new DefaultThreadContextMap(true); - } -} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java index 1097483e921..99275b98ffd 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusConsoleListener.java @@ -18,40 +18,55 @@ import java.io.IOException; import java.io.PrintStream; +import java.util.Objects; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; /** - * StatusListener that writes to the Console. + * {@link StatusListener} that writes to the console. */ @SuppressWarnings("UseOfSystemOutOrSystemErr") public class StatusConsoleListener implements StatusListener { - private Level level = Level.FATAL; + private Level level; + private String[] filters; + private final PrintStream stream; + private final Logger logger; + /** - * Creates the StatusConsoleListener using the supplied Level. - * @param level The Level of status messages that should appear on the console. + * Constructs a {@link StatusConsoleListener} instance writing to {@link System#out} using the supplied level. + * + * @param level the level of status messages that should appear on the console + * @throws NullPointerException on null {@code level} */ public StatusConsoleListener(final Level level) { this(level, System.out); } /** - * Creates the StatusConsoleListener using the supplied Level. Make sure not to use a logger stream of some sort - * to avoid creating an infinite loop of indirection! - * @param level The Level of status messages that should appear on the console. - * @param stream The PrintStream to write to. - * @throws IllegalArgumentException if the PrintStream argument is {@code null}. + * Constructs a {@link StatusConsoleListener} instance using the supplied level and stream. + *

+ * Make sure not to use a logger stream of some sort to avoid creating an infinite loop of indirection! + *

+ * + * @param level the level of status messages that should appear on the console + * @param stream the stream to write to + * @throws NullPointerException on null {@code level} or {@code stream} */ public StatusConsoleListener(final Level level, final PrintStream stream) { - if (stream == null) { - throw new IllegalArgumentException("You must provide a stream to use for this listener."); - } - this.level = level; - this.stream = stream; + this(level, stream, StatusLoggerFactory.getInstance()); + } + + StatusConsoleListener(final Level level, final PrintStream stream, final StatusLoggerFactory loggerFactory) { + this.level = Objects.requireNonNull(level, "level"); + this.stream = Objects.requireNonNull(stream, "stream"); + this.logger = Objects + .requireNonNull(loggerFactory, "loggerFactory") + .createSimpleLogger("StatusConsoleListener", level, stream); } /** @@ -77,8 +92,14 @@ public Level getStatusLevel() { */ @Override public void log(final StatusData data) { - if (!filtered(data)) { - stream.println(data.getFormattedStatus()); + final boolean filtered = filtered(data); + if (!filtered) { + logger + // Logging using _only_ the following 4 fields set by `StatusLogger#logMessage()`: + .atLevel(data.getLevel()) + .withThrowable(data.getThrowable()) + .withLocation(data.getStackTraceElement()) + .log(data.getMessage()); } } @@ -110,4 +131,5 @@ public void close() throws IOException { this.stream.close(); } } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java index 463d9d05402..9cd0fff5469 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusData.java @@ -43,7 +43,7 @@ public class StatusData implements Serializable { /** * Creates the StatusData object. - * + * * @param caller The method that created the event. * @param level The logging level. * @param msg The message String. @@ -62,7 +62,7 @@ public StatusData(final StackTraceElement caller, final Level level, final Messa /** * Returns the event's timestamp. - * + * * @return The event's timestamp. */ public long getTimestamp() { @@ -71,7 +71,7 @@ public long getTimestamp() { /** * Returns the StackTraceElement for the method that created the event. - * + * * @return The StackTraceElement. */ public StackTraceElement getStackTraceElement() { @@ -80,7 +80,7 @@ public StackTraceElement getStackTraceElement() { /** * Returns the logging level for the event. - * + * * @return The logging level. */ public Level getLevel() { @@ -89,7 +89,7 @@ public Level getLevel() { /** * Returns the message associated with the event. - * + * * @return The message associated with the event. */ public Message getMessage() { @@ -105,7 +105,7 @@ public String getThreadName() { /** * Returns the Throwable associated with the event. - * + * * @return The Throwable associated with the event. */ public Throwable getThrowable() { @@ -114,7 +114,7 @@ public Throwable getThrowable() { /** * Formats the StatusData for viewing. - * + * * @return The formatted status data as a String. */ public String getFormattedStatus() { @@ -128,7 +128,7 @@ public String getFormattedStatus() { sb.append(SPACE); sb.append(msg.getFormattedMessage()); final Object[] params = msg.getParameters(); - Throwable t; + final Throwable t; if (throwable == null && params != null && params[params.length - 1] instanceof Throwable) { t = (Throwable) params[params.length - 1]; } else { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java index 25e85ffadc0..09828929ddd 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLogger.java @@ -32,53 +32,37 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; import org.apache.logging.log4j.simple.SimpleLogger; import org.apache.logging.log4j.simple.SimpleLoggerContext; import org.apache.logging.log4j.spi.AbstractLogger; -import org.apache.logging.log4j.util.Constants; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.util.LowLevelLogUtil; /** * Records events that occur in the logging system. By default, only error messages are logged to {@link System#err}. * Normally, the Log4j StatusLogger is configured via the root {@code } node in a Log4j * configuration file. However, this can be overridden via a system property named - * "{@value SimpleLoggerContext#SYSTEM_PREFIX}StatusLogger.level" and will work with any Log4j provider. + * {@value LoggingSystemProperties#STATUS_DEFAULT_LISTENER_LEVEL} and will work with any Log4j provider. * * @see SimpleLogger * @see SimpleLoggerContext */ public final class StatusLogger extends AbstractLogger { - /** - * System property that can be configured with the number of entries in the queue. Once the limit is reached older - * entries will be removed as new entries are added. - */ - public static final String MAX_STATUS_ENTRIES = "log4j2.status.entries"; - - /** - * System property that can be configured with the {@link Level} name to use as the default level for - * {@link StatusListener}s. - */ - public static final String DEFAULT_STATUS_LISTENER_LEVEL = "log4j2.StatusLogger.level"; - private static final long serialVersionUID = 2L; private static final String NOT_AVAIL = "?"; - private static final PropertiesUtil PROPS = new PropertiesUtil("log4j2.StatusLogger.properties"); - - private static final int MAX_ENTRIES = PROPS.getIntegerProperty(MAX_STATUS_ENTRIES, 200); + private static final StatusLogger STATUS_LOGGER = StatusLoggerFactory.getInstance().createStatusLogger(); - private static final String DEFAULT_STATUS_LEVEL = PROPS.getStringProperty(DEFAULT_STATUS_LISTENER_LEVEL); - - // LOG4J2-1176: normal parameterized message remembers param object, causing memory leaks. - private static final StatusLogger STATUS_LOGGER = new StatusLogger(StatusLogger.class.getName(), - ParameterizedNoReferenceMessageFactory.INSTANCE); + static { + // now safe to use StatusLogger in LowLevelLogUtil + LowLevelLogUtil.setLogger(STATUS_LOGGER); + } private final SimpleLogger logger; + private final StatusLoggerConfiguration configuration; private final Collection listeners = new CopyOnWriteArrayList<>(); @@ -86,7 +70,7 @@ public final class StatusLogger extends AbstractLogger { // ReentrantReadWriteLock is Serializable private final ReadWriteLock listenersLock = new ReentrantReadWriteLock(); - private final Queue messages = new BoundedQueue<>(MAX_ENTRIES); + private final Queue messages; @SuppressWarnings("NonSerializableFieldInSerializableClass") // ReentrantLock is Serializable @@ -94,21 +78,37 @@ public final class StatusLogger extends AbstractLogger { private int listenersLevel; - private StatusLogger(final String name, final MessageFactory messageFactory) { - super(name, messageFactory); - this.logger = new SimpleLogger("StatusLogger", Level.ERROR, false, true, false, false, Strings.EMPTY, - messageFactory, PROPS, System.err); - this.listenersLevel = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); - - // LOG4J2-1813 if system property "log4j2.debug" is defined, print all status logging - if (isDebugPropertyEnabled()) { - logger.setLevel(Level.TRACE); - } - } - - // LOG4J2-1813 if system property "log4j2.debug" is defined, print all status logging - private boolean isDebugPropertyEnabled() { - return PropertiesUtil.getProperties().getBooleanProperty(Constants.LOG4J2_DEBUG, false, true); + /** + * Constructs the singleton instance for the STATUS_LOGGER constant. + *

+ * This is now the logger level is set: + *

+ *
    + *
  1. If the property {@value LoggingSystemProperties#SYSTEM_DEBUG} is {@code "true"}, then use {@link Level#TRACE}, otherwise,
  2. + *
  3. Use {@link Level#ERROR}
  4. + *
+ *

+ * This is now the listener level is set: + *

+ *
    + *
  1. If the property {@value LoggingSystemProperties#STATUS_DEFAULT_LISTENER_LEVEL} is set, then use it, otherwise,
  2. + *
  3. Use {@link Level#WARN}
  4. + *
+ *

+ * See: + *

    + *
  1. LOG4J2-1813 Provide shorter and more intuitive way to switch on Log4j internal debug logging. If system property + * {@value LoggingSystemProperties#SYSTEM_DEBUG} is defined, print all status logging.
  2. + *
  3. LOG4J2-3340 StatusLogger's log Level cannot be changed as advertised.
  4. + *
+ *

+ */ + StatusLogger(final SimpleLogger logger, final StatusLoggerConfiguration configuration) { + super(StatusLogger.class.getName(), ParameterizedNoReferenceMessageFactory.INSTANCE); + this.logger = logger; + this.configuration = configuration; + this.listenersLevel = configuration.getDefaultLevel().intLevel(); + messages = new BoundedQueue<>(configuration.getMaxEntries()); } /** @@ -152,7 +152,7 @@ public void removeListener(final StatusListener listener) { listenersLock.writeLock().lock(); try { listeners.remove(listener); - int lowest = Level.toLevel(DEFAULT_STATUS_LEVEL, Level.WARN).intLevel(); + int lowest = configuration.getDefaultLevel().intLevel(); for (final StatusListener statusListener : listeners) { final int level = statusListener.getStatusLevel().intLevel(); if (lowest < level) { @@ -260,17 +260,13 @@ public void logMessage(final String fqcn, final Level level, final Marker marker msgLock.unlock(); } // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled - if (isDebugPropertyEnabled()) { + if (configuration.isDebugEnabled() || listeners.isEmpty()) { logger.logMessage(fqcn, level, marker, msg, t); } else { - if (listeners.size() > 0) { - for (final StatusListener listener : listeners) { - if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) { - listener.log(data); - } + for (final StatusListener listener : listeners) { + if (data.getLevel().isMoreSpecificThan(listener.getStatusLevel())) { + listener.log(data); } - } else { - logger.logMessage(fqcn, level, marker, msg, t); } } } @@ -394,8 +390,7 @@ public boolean isEnabled(final Level level, final Marker marker, final Message m @Override public boolean isEnabled(final Level level, final Marker marker) { - // LOG4J2-1813 if system property "log4j2.debug" is defined, all status logging is enabled - if (isDebugPropertyEnabled()) { + if (configuration.isDebugEnabled()) { return true; } if (listeners.size() > 0) { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLoggerConfiguration.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLoggerConfiguration.java new file mode 100644 index 00000000000..47039d38693 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLoggerConfiguration.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.status; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.util.PropertyEnvironment; + +import static org.apache.logging.log4j.spi.LoggingSystemProperties.*; + +public class StatusLoggerConfiguration { + private final PropertyEnvironment environment; + + public StatusLoggerConfiguration(final PropertyEnvironment environment) { + this.environment = environment; + } + + public int getMaxEntries() { + return environment.getIntegerProperty(STATUS_MAX_ENTRIES, 200); + } + + public Level getDefaultLevel() { + return Level.toLevel(environment.getStringProperty(STATUS_DEFAULT_LISTENER_LEVEL), Level.WARN); + } + + public boolean isDebugEnabled() { + return environment.getBooleanProperty(SYSTEM_DEBUG, false, true); + } + + public DateFormat getDateTimeFormat() { + final String format = environment.getStringProperty(STATUS_DATE_FORMAT); + if (format != null) { + try { + return new SimpleDateFormat(format); + } catch (final IllegalArgumentException ignored) { + } + } + return null; + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLoggerFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLoggerFactory.java new file mode 100644 index 00000000000..d7cd8eb0cf1 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/status/StatusLoggerFactory.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.status; + +import java.io.PrintStream; +import java.text.DateFormat; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory; +import org.apache.logging.log4j.simple.SimpleLogger; +import org.apache.logging.log4j.util.PropertiesUtil; + +class StatusLoggerFactory { + private static final String STATUS_LOGGER = "StatusLogger"; + private final StatusLoggerConfiguration configuration; + + StatusLoggerFactory(final StatusLoggerConfiguration configuration) { + this.configuration = configuration; + } + + SimpleLogger createSimpleLogger(final String name, final Level loggerLevel, final PrintStream stream) { + final DateFormat dateFormat = configuration.getDateTimeFormat(); + return new SimpleLogger(name, ParameterizedNoReferenceMessageFactory.INSTANCE, stream, + loggerLevel, dateFormat, dateFormat != null); + } + + StatusLogger createStatusLogger() { + final Level loggerLevel = configuration.isDebugEnabled() ? Level.TRACE : Level.ERROR; + final SimpleLogger logger = createSimpleLogger(STATUS_LOGGER, loggerLevel, System.err); + return new StatusLogger(logger, configuration); + } + + static StatusLoggerFactory getInstance() { + return new StatusLoggerFactory(new StatusLoggerConfiguration(PropertiesUtil.getProperties(STATUS_LOGGER))); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Activator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Activator.java index c7910e505e1..2409119b0b3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Activator.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Activator.java @@ -16,13 +16,15 @@ */ package org.apache.logging.log4j.util; -import java.net.URL; import java.security.Permission; -import java.util.Collection; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.apache.logging.log4j.spi.LoggingSystem; import org.apache.logging.log4j.spi.Provider; import org.apache.logging.log4j.status.StatusLogger; import org.osgi.framework.AdaptPermission; @@ -31,10 +33,7 @@ import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; import org.osgi.framework.SynchronousBundleListener; -import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; /** @@ -44,15 +43,16 @@ * {@code META-INF/log4j-provider.properties} files. As with all OSGi BundleActivator classes, this class is not for * public use and is only useful in an OSGi framework environment. */ +@InternalApi public class Activator implements BundleActivator, SynchronousBundleListener { private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); private static final Logger LOGGER = StatusLogger.getLogger(); - // until we have at least one Provider, we'll lock ProviderUtil which locks LogManager. by extension. + // until we have at least one Provider, we'll lock LoggingSystem which locks LogManager.getFactory by extension. // this variable needs to be reset once the lock has been released - private boolean lockingProviderUtil; + private boolean hasLoggingSystemInitializationLock; private static void checkPermission(final Permission permission) { if (SECURITY_MANAGER != null) { @@ -100,56 +100,41 @@ private String toStateString(final int state) { } private void loadProvider(final BundleContext bundleContext, final BundleWiring bundleWiring) { - final String filter = "(APIVersion>=2.60)"; - try { - final Collection> serviceReferences = bundleContext.getServiceReferences(Provider.class, filter); - Provider maxProvider = null; - for (final ServiceReference serviceReference : serviceReferences) { - final Provider provider = bundleContext.getService(serviceReference); - if (maxProvider == null || provider.getPriority() > maxProvider.getPriority()) { - maxProvider = provider; - } - } - if (maxProvider != null) { - ProviderUtil.addProvider(maxProvider); + final List providers = loadProviders(bundleWiring); + if (!providers.isEmpty()) { + ServiceRegistry.getInstance().registerBundleServices(Provider.class, bundleContext.getBundle().getBundleId(), providers); + if (hasLoggingSystemInitializationLock) { + LoggingSystem.getInstance().releaseInitializationLock(); + hasLoggingSystemInitializationLock = false; } - } catch (final InvalidSyntaxException ex) { - LOGGER.error("Invalid service filter: " + filter, ex); } - final List urls = bundleWiring.findEntries("META-INF", "log4j-provider.properties", 0); - for (final URL url : urls) { - ProviderUtil.loadProvider(url, bundleWiring.getClassLoader()); + } + + private List loadProviders(final BundleWiring bundleWiring) { + final List providers = new ArrayList<>(); + final ClassLoader classLoader = bundleWiring.getClassLoader(); + final Iterator iterator = ServiceLoader.load(Provider.class, classLoader).iterator(); + while (iterator.hasNext()) { + try { + providers.add(iterator.next()); + } catch (ServiceConfigurationError e) { + LOGGER.error("Unable to load Log4j Provider", e); + } } + return providers; } @Override public void start(final BundleContext bundleContext) throws Exception { - ProviderUtil.STARTUP_LOCK.lock(); - lockingProviderUtil = true; - final BundleWiring self = bundleContext.getBundle().adapt(BundleWiring.class); - final List required = self.getRequiredWires(LoggerContextFactory.class.getName()); - for (final BundleWire wire : required) { - loadProvider(bundleContext, wire.getProviderWiring()); - } + final LoggingSystem system = LoggingSystem.getInstance(); + system.acquireInitializationLock(); + hasLoggingSystemInitializationLock = true; bundleContext.addBundleListener(this); - final Bundle[] bundles = bundleContext.getBundles(); - for (final Bundle bundle : bundles) { - loadProvider(bundle); - } - unlockIfReady(); - } - - private void unlockIfReady() { - if (lockingProviderUtil && !ProviderUtil.PROVIDERS.isEmpty()) { - ProviderUtil.STARTUP_LOCK.unlock(); - lockingProviderUtil = false; - } } @Override public void stop(final BundleContext bundleContext) throws Exception { bundleContext.removeBundleListener(this); - unlockIfReady(); } @Override @@ -157,7 +142,10 @@ public void bundleChanged(final BundleEvent event) { switch (event.getType()) { case BundleEvent.STARTED: loadProvider(event.getBundle()); - unlockIfReady(); + break; + + case BundleEvent.STOPPED: + ServiceRegistry.getInstance().unregisterBundleServices(event.getBundle().getBundleId()); break; default: diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Assert.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Assert.java similarity index 98% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/Assert.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/Assert.java index da7f45afc02..7a7a925992b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Assert.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Assert.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.util; import java.util.Collection; import java.util.Map; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java index 1d2ca59bd05..82e3ad6d179 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java @@ -24,7 +24,7 @@ * @see ReadOnlyStringMap * @since 2.7 */ -public interface BiConsumer { +public interface BiConsumer extends java.util.function.BiConsumer { /** * Performs the operation given the specified arguments. diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Cast.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Cast.java new file mode 100644 index 00000000000..d85e76caafb --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Cast.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +@InternalApi +public final class Cast { + public static T cast(final Object o) { + @SuppressWarnings("unchecked") final T t = (T) o; + return t; + } + + private Cast() { + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Chars.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Chars.java index c6642eac3ff..d2803eb5a2e 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Chars.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Chars.java @@ -19,6 +19,7 @@ /** * Consider this class private. */ +@InternalApi public final class Chars { /** Carriage Return. */ @@ -33,6 +34,9 @@ public final class Chars { /** Line Feed. */ public static final char LF = '\n'; + /** NUL. */ + public static final char NUL = 0; + /** Single Quote [']. */ public static final char QUOTE = '\''; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java index c0d18d7061b..0044eef6763 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java @@ -16,29 +16,70 @@ */ package org.apache.logging.log4j.util; +import org.apache.logging.log4j.spi.LoggingSystemProperties; + /** * Log4j API Constants. * * @since 2.6.2 */ +@InternalApi public final class Constants { + + private static final LazyBoolean isWebApp = new LazyBoolean(() -> PropertiesUtil.getProperties() + .getBooleanProperty(LoggingSystemProperties.SYSTEM_IS_WEBAPP, + isClassAvailable("javax.servlet.Servlet") || isClassAvailable("jakarta.servlet.Servlet"))); + /** * {@code true} if we think we are running in a web container, based on the boolean value of system property * "log4j2.is.webapp", or (if this system property is not set) whether the {@code javax.servlet.Servlet} class * is present in the classpath. */ - public static final boolean IS_WEB_APP = PropertiesUtil.getProperties().getBooleanProperty( - "log4j2.is.webapp", isClassAvailable("javax.servlet.Servlet")); + public static boolean isWebApp() { + return isWebApp.getAsBoolean(); + } + + public static void setWebApp(final boolean webApp) { + isWebApp.setAsBoolean(webApp); + } + + public static void resetWebApp() { + isWebApp.reset(); + } + + /** + * @deprecated use {@link #isWebApp()} + */ + @Deprecated(since = "3.0.0", forRemoval = true) + public static final boolean IS_WEB_APP = isWebApp(); + + private static final LazyBoolean threadLocalsEnabled = new LazyBoolean( + () -> !isWebApp() && PropertiesUtil.getProperties().getBooleanProperty(LoggingSystemProperties.SYSTEM_THREAD_LOCALS_ENABLED, true)); /** * Kill switch for object pooling in ThreadLocals that enables much of the LOG4J2-1270 no-GC behaviour. *

- * {@code True} for non-{@link #IS_WEB_APP web apps}, disable by setting system property + * {@code True} for non-{@link #isWebApp()} web apps}, disable by setting system property * "log4j2.enable.threadlocals" to "false". *

*/ - public static final boolean ENABLE_THREADLOCALS = !IS_WEB_APP && PropertiesUtil.getProperties().getBooleanProperty( - "log4j2.enable.threadlocals", true); + public static boolean isThreadLocalsEnabled() { + return threadLocalsEnabled.getAsBoolean(); + } + + public static void setThreadLocalsEnabled(final boolean enabled) { + threadLocalsEnabled.setAsBoolean(enabled); + } + + public static void resetThreadLocalsEnabled() { + threadLocalsEnabled.reset(); + } + + /** + * @deprecated use {@link #isThreadLocalsEnabled()} + */ + @Deprecated(since = "3.0.0", forRemoval = true) + public static final boolean ENABLE_THREADLOCALS = isThreadLocalsEnabled(); public static final int JAVA_MAJOR_VERSION = getMajorVersion(); @@ -46,23 +87,12 @@ public final class Constants { * Maximum size of the StringBuilders used in RingBuffer LogEvents to store the contents of reusable Messages. * After a large message has been delivered to the appenders, the StringBuilder is trimmed to this size. *

- * The default value is {@value}, which allows the StringBuilder to resize three times from its initial size. - * Users can override with system property "log4j.maxReusableMsgSize". + * The default value is 518, which allows the StringBuilder to resize three times from its initial size. + * Users can override with system property {@value LoggingSystemProperties#GC_REUSABLE_MESSAGE_MAX_SIZE}. *

* @since 2.9 */ - public static final int MAX_REUSABLE_MESSAGE_SIZE = size("log4j.maxReusableMsgSize", (128 * 2 + 2) * 2 + 2); - - /** - * Name of the system property that will turn on TRACE level internal log4j2 status logging. - *

- * If system property {@value} is defined, regardless of the property value, all internal log4j2 logging will be - * printed to the console. The presence of this system property overrides any value set in the configuration's - * {@code } status attribute, as well as any value set for - * system property {@code org.apache.logging.log4j.simplelog.StatusLogger.level}. - *

- */ - public static final String LOG4J2_DEBUG = "log4j2.debug"; + public static final int MAX_REUSABLE_MESSAGE_SIZE = size(LoggingSystemProperties.GC_REUSABLE_MESSAGE_MAX_SIZE, (128 * 2 + 2) * 2 + 2); private static int size(final String property, final int defaultValue) { return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); @@ -82,6 +112,16 @@ private static boolean isClassAvailable(final String className) { } } + /** + * The empty array. + */ + public static final Object[] EMPTY_OBJECT_ARRAY = {}; + + /** + * The empty array. + */ + public static final byte[] EMPTY_BYTE_ARRAY = {}; + /** * Prevent class instantiation. */ @@ -89,9 +129,12 @@ private Constants() { } private static int getMajorVersion() { - final String version = System.getProperty("java.version"); + return getMajorVersion(System.getProperty("java.version")); + } + + static int getMajorVersion(final String version) { final String[] parts = version.split("-|\\."); - boolean isJEP223; + final boolean isJEP223; try { final int token = Integer.parseInt(parts[0]); isJEP223 = token != 1; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/EnglishEnums.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnglishEnums.java index be6b346daec..e4c819faba3 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/EnglishEnums.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnglishEnums.java @@ -20,7 +20,7 @@ /** * Consider this class private. - * + * *

* Helps convert English Strings to English Enum values. *

@@ -29,6 +29,7 @@ * avoid problems on the Turkish locale. Do not use with Turkish enum values. *

*/ +@InternalApi public final class EnglishEnums { private EnglishEnums() { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java index af8c23e20fa..3fd116efe3d 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java @@ -16,27 +16,47 @@ */ package org.apache.logging.log4j.util; +import java.util.Collection; import java.util.Map; /** - * PropertySource implementation that uses environment variables as a source. All environment variables must begin - * with {@code LOG4J_} so as not to conflict with other variables. Normalized environment variables follow a scheme - * like this: {@code log4j2.fooBarProperty} would normalize to {@code LOG4J_FOO_BAR_PROPERTY}. + * PropertySource implementation that uses environment variables as a source. + * All environment variables must begin with {@code LOG4J_} so as not to + * conflict with other variables. Normalized environment variables follow a + * scheme like this: {@code log4j2.fooBarProperty} would normalize to + * {@code LOG4J_FOO_BAR_PROPERTY}. * * @since 2.10.0 */ public class EnvironmentPropertySource implements PropertySource { + + private static final String PREFIX = "LOG4J_"; + private static final int DEFAULT_PRIORITY = 100; + @Override public int getPriority() { - return -100; + return DEFAULT_PRIORITY; + } + + private void logException(SecurityException e) { + // There is no status logger yet. + LowLevelLogUtil.logException( + "The system environment variables are not available to Log4j due to security restrictions: " + e, e); } @Override public void forEach(final BiConsumer action) { - for (final Map.Entry entry : System.getenv().entrySet()) { + final Map getenv; + try { + getenv = System.getenv(); + } catch (final SecurityException e) { + logException(e); + return; + } + for (final Map.Entry entry : getenv.entrySet()) { final String key = entry.getKey(); - if (key.startsWith("LOG4J_")) { - action.accept(key.substring(6), entry.getValue()); + if (key.startsWith(PREFIX)) { + action.accept(key.substring(PREFIX.length()), entry.getValue()); } } } @@ -44,12 +64,45 @@ public void forEach(final BiConsumer action) { @Override public CharSequence getNormalForm(final Iterable tokens) { final StringBuilder sb = new StringBuilder("LOG4J"); + boolean empty = true; for (final CharSequence token : tokens) { + empty = false; sb.append('_'); for (int i = 0; i < token.length(); i++) { sb.append(Character.toUpperCase(token.charAt(i))); } } - return sb.toString(); + return empty ? null : sb.toString(); + } + + @Override + public Collection getPropertyNames() { + try { + return System.getenv().keySet(); + } catch (final SecurityException e) { + logException(e); + return PropertySource.super.getPropertyNames(); + } } + + @Override + public String getProperty(String key) { + try { + return System.getenv(key); + } catch (final SecurityException e) { + logException(e); + return PropertySource.super.getProperty(key); + } + } + + @Override + public boolean containsProperty(String key) { + try { + return System.getenv().containsKey(key); + } catch (final SecurityException e) { + logException(e); + return PropertySource.super.containsProperty(key); + } + } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/FilteredObjectInputStream.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/FilteredObjectInputStream.java index df97fd29b92..435f22c8a44 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/FilteredObjectInputStream.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/FilteredObjectInputStream.java @@ -56,7 +56,7 @@ public FilteredObjectInputStream() throws IOException, SecurityException { this.allowedClasses = new HashSet<>(); } - public FilteredObjectInputStream(InputStream in) throws IOException { + public FilteredObjectInputStream(final InputStream in) throws IOException { super(in); this.allowedClasses = new HashSet<>(); } @@ -75,9 +75,10 @@ public Collection getAllowedClasses() { return allowedClasses; } + @SuppressWarnings("BanSerializableRead") @Override protected Class resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException { - String name = desc.getName(); + final String name = desc.getName(); if (!(isAllowedByDefault(name) || allowedClasses.contains(name))) { throw new InvalidObjectException("Class is not allowed for deserialization: " + name); } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedReadOnlyStringMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedReadOnlyStringMap.java index 5c67b014bf0..9209deda326 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedReadOnlyStringMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedReadOnlyStringMap.java @@ -44,6 +44,7 @@ public interface IndexedReadOnlyStringMap extends ReadOnlyStringMap { * or {@code null} if the specified index is less than zero or greater or equal to the size of this collection. * * @param index the index of the value to return + * @param The type of the returned value. * @return the value at the specified index or {@code null} */ V getValueAt(final int index); diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalApi.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalApi.java new file mode 100644 index 00000000000..d7b45267867 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalApi.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Indicates that the annotated element is considered an internal API to Log4j and should not be used by external + * code. Internal APIs do not provide any stability guarantees between versions. + * + * @since 3.0.0 + */ +@Retention(RetentionPolicy.CLASS) +@Documented +public @interface InternalApi { +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.java new file mode 100644 index 00000000000..06472e30abd --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +/** + * Exception thrown when an error occurs while accessing internal resources. This is generally used to + * convert checked exceptions to runtime exceptions. + */ +public class InternalException extends RuntimeException { + + private static final long serialVersionUID = 6366395965071580537L; + + /** + * Construct an exception with a message. + * + * @param message The reason for the exception + */ + public InternalException(final String message) { + super(message); + } + + /** + * Construct an exception with a message and underlying cause. + * + * @param message The reason for the exception + * @param cause The underlying cause of the exception + */ + public InternalException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Construct an exception with an underlying cause. + * + * @param cause The underlying cause of the exception + */ + public InternalException(final Throwable cause) { + super(cause); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java index a9f16845f82..515e24fb886 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java @@ -81,6 +81,7 @@ public static Message get(final MessageSupplier supplier) { * Returns a Message, either the value supplied by the specified function, or a new Message created by the specified * Factory. * @param supplier a lambda expression or {@code null} + * @param messageFactory The MessageFactory. * @return the Message resulting from evaluating the lambda expression or the Message created by the factory for * supplied values that are not of type Message */ diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Lazy.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Lazy.java new file mode 100644 index 00000000000..1c08f6d8d8c --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Lazy.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Provides a lazily-initialized value from a {@code Supplier}. + * + * @param type of value + */ +public interface Lazy extends Supplier { + T value(); + + @Override + default T get() { + return value(); + } + + default Lazy map(final Function function) { + return lazy(() -> function.apply(value())); + } + + boolean isInitialized(); + + void set(final T newValue); + + /** + * Creates a lazy value using the provided Supplier for initialization guarded by a Lock. + */ + static Lazy lazy(final Supplier supplier) { + Objects.requireNonNull(supplier); + return new LazyUtil.SafeLazy<>(supplier); + } + + /** + * Creates a lazy value using the provided constant value. + */ + static Lazy value(final T value) { + return new LazyUtil.Constant<>(value); + } + + /** + * Creates a relaxed lazy value using the provided Supplier for initialization which may be invoked more than once + * in order to set the initialized value. + */ + static Lazy relaxed(final Supplier supplier) { + Objects.requireNonNull(supplier); + return new LazyUtil.ReleaseAcquireLazy<>(supplier); + } + + /** + * Creates a pure lazy value using the provided Supplier to initialize the value. The supplier may be invoked more + * than once, and the return value should be a purely computed value as the result may be a different instance + * each time. This is useful for building cache tables and other pure computations. + */ + static Lazy pure(final Supplier supplier) { + Objects.requireNonNull(supplier); + return new LazyUtil.PureLazy<>(supplier); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyBoolean.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyBoolean.java new file mode 100644 index 00000000000..f41f5cfd5ef --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyBoolean.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.util; + +import java.util.function.BooleanSupplier; + +public class LazyBoolean implements BooleanSupplier { + private final BooleanSupplier supplier; + private volatile boolean initialized; + private volatile boolean value; + + public LazyBoolean(final BooleanSupplier supplier) { + this.supplier = supplier; + } + + @Override + public boolean getAsBoolean() { + boolean uninitialized = !initialized; + boolean value = this.value; + if (uninitialized) { + synchronized (this) { + uninitialized = !initialized; + if (uninitialized) { + this.value = value = supplier.getAsBoolean(); + initialized = true; + } + } + } + return value; + } + + public synchronized void setAsBoolean(final boolean b) { + initialized = false; + value = b; + initialized = true; + } + + public void reset() { + initialized = false; + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyUtil.java new file mode 100644 index 00000000000..0150aaf289a --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LazyUtil.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +final class LazyUtil { + private static final Object NULL = new Object() { + @Override + public String toString() { + return "null"; + } + }; + + static Object wrapNull(final Object value) { + return value == null ? NULL : value; + } + + static T unwrapNull(final Object value) { + return value == NULL ? null : Cast.cast(value); + } + + static class Constant implements Lazy { + private final T value; + + Constant(final T value) { + this.value = value; + } + + @Override + public T value() { + return value; + } + + @Override + public boolean isInitialized() { + return true; + } + + @Override + public void set(final T newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return String.valueOf(value); + } + } + + static class SafeLazy implements Lazy { + private final Lock lock = new ReentrantLock(); + private final Supplier supplier; + private volatile Object value; + + SafeLazy(final Supplier supplier) { + this.supplier = supplier; + } + + @Override + public T value() { + Object value = this.value; + if (value == null) { + lock.lock(); + try { + value = this.value; + if (value == null) { + value = supplier.get(); + this.value = wrapNull(value); + } + } finally { + lock.unlock(); + } + } + return unwrapNull(value); + } + + @Override + public void set(final T newValue) { + value = newValue; + } + + public void reset() { + value = null; + } + + @Override + public boolean isInitialized() { + return value != null; + } + + @Override + public String toString() { + return isInitialized() ? String.valueOf(value) : "Lazy value not initialized"; + } + } + + static class ReleaseAcquireLazy implements Lazy { + private static final VarHandle VALUE; + static { + try { + VALUE = MethodHandles.lookup() + .findVarHandle(ReleaseAcquireLazy.class, "value", Object.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + + private final Supplier supplier; + private volatile Object value; + + ReleaseAcquireLazy(final Supplier supplier) { + this.supplier = supplier; + } + + @Override + public T value() { + final var currentValue = VALUE.getAcquire(this); + if (currentValue != null) { + return unwrapNull(currentValue); + } + final T newValue = supplier.get(); + if (VALUE.compareAndExchangeRelease(this, null, wrapNull(newValue)) == null) { + return newValue; + } + final Object value = VALUE.getAcquire(this); + return unwrapNull(value); + } + + @Override + public void set(final T newValue) { + // equivalent to VALUE.setVolatile(this, newValue) + value = newValue; + } + + @Override + public boolean isInitialized() { + final var current = VALUE.getAcquire(this); + return current != null; + } + + @Override + public String toString() { + return isInitialized() ? String.valueOf(VALUE.getOpaque(value)) : "Lazy value not initialized"; + } + } + + static class PureLazy implements Lazy { + private final Supplier supplier; + private Object value; + + public PureLazy(final Supplier supplier) { + this.supplier = supplier; + } + + @Override + public T value() { + Object value = this.value; + if (value == null) { + value = supplier.get(); + this.value = wrapNull(value); + } + return unwrapNull(value); + } + + @Override + public boolean isInitialized() { + return value != null; + } + + @Override + public void set(final T newValue) { + value = newValue; + } + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java index 762670475c9..14c4af7dd8c 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java @@ -21,13 +21,13 @@ import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedHashSet; -import java.util.List; import java.util.Objects; +import org.apache.logging.log4j.spi.LoggingSystemProperties; + /** * Consider this class private. Utility class for ClassLoaders. * @@ -36,14 +36,10 @@ * @see Thread#getContextClassLoader() * @see ClassLoader#getSystemClassLoader() */ +@InternalApi public final class LoaderUtil { - /** - * System property to set to ignore the thread context ClassLoader. - * - * @since 2.1 - */ - public static final String IGNORE_TCCL_PROPERTY = "log4j.ignoreTCL"; + private static final ClassLoader[] EMPTY_CLASS_LOADER_ARRAY = {}; private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); @@ -53,6 +49,8 @@ public final class LoaderUtil { private static final boolean GET_CLASS_LOADER_DISABLED; + protected static Boolean forceTcclOnly; + private static final PrivilegedAction TCCL_GETTER = new ThreadContextClassLoaderGetter(); static { @@ -73,6 +71,26 @@ public final class LoaderUtil { private LoaderUtil() { } + /** + * Returns the ClassLoader to use. + * @return the ClassLoader. + */ + public static ClassLoader getClassLoader() { + return getClassLoader(LoaderUtil.class, null); + } + + // TODO: this method could use some explanation + public static ClassLoader getClassLoader(final Class class1, final Class class2) { + final ClassLoader threadContextClassLoader = getThreadContextClassLoader(); + final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader(); + final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader(); + + if (isChild(threadContextClassLoader, loader1)) { + return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2; + } + return isChild(loader1, loader2) ? loader1 : loader2; + } + /** * Gets the current Thread ClassLoader. Returns the system ClassLoader if the TCCL is {@code null}. If the system * ClassLoader is {@code null} as well, then the ClassLoader for this class is returned. If running with a @@ -90,6 +108,26 @@ public static ClassLoader getThreadContextClassLoader() { return SECURITY_MANAGER == null ? TCCL_GETTER.run() : AccessController.doPrivileged(TCCL_GETTER); } + /** + * Determines if one ClassLoader is a child of another ClassLoader. Note that a {@code null} ClassLoader is + * interpreted as the system ClassLoader as per convention. + * + * @param loader1 the ClassLoader to check for childhood. + * @param loader2 the ClassLoader to check for parenthood. + * @return {@code true} if the first ClassLoader is a strict descendant of the second ClassLoader. + */ + private static boolean isChild(final ClassLoader loader1, final ClassLoader loader2) { + if (loader1 != null && loader2 != null) { + ClassLoader parent = loader1.getParent(); + while (parent != null && parent != loader2) { + parent = parent.getParent(); + } + // once parent is null, we're at the system CL, which would indicate they have separate ancestry + return parent != null; + } + return loader1 != null; + } + /** * */ @@ -106,25 +144,52 @@ public ClassLoader run() { } public static ClassLoader[] getClassLoaders() { - List classLoaders = new ArrayList<>(); - ClassLoader tcl = getThreadContextClassLoader(); - classLoaders.add(tcl); - ClassLoader current = LoaderUtil.class.getClassLoader(); - if (current != tcl) { - classLoaders.add(current); - ClassLoader parent = current.getParent(); - while (parent != null && !classLoaders.contains(parent)) { - classLoaders.add(parent); + final Collection classLoaders = new LinkedHashSet<>(); + final ClassLoader tcl = getThreadContextClassLoader(); + if (tcl != null) { + classLoaders.add(tcl); + } + final ModuleLayer layer = LoaderUtil.class.getModule().getLayer(); + if (layer == null) { + if (!isForceTccl()) { + accumulateClassLoaders(LoaderUtil.class.getClassLoader(), classLoaders); + accumulateClassLoaders(tcl == null ? null : tcl.getParent(), classLoaders); + final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + if (systemClassLoader != null) { + classLoaders.add(systemClassLoader); + } + } + } else { + accumulateLayerClassLoaders(layer, classLoaders); + if (layer != ModuleLayer.boot()) { + for (final Module module : ModuleLayer.boot().modules()) { + accumulateClassLoaders(module.getClassLoader(), classLoaders); + } } } - ClassLoader parent = tcl; - while (parent != null && !classLoaders.contains(parent)) { - classLoaders.add(parent); + return classLoaders.toArray(EMPTY_CLASS_LOADER_ARRAY); + } + + private static void accumulateLayerClassLoaders(final ModuleLayer layer, final Collection classLoaders) { + for (final Module module : layer.modules()) { + accumulateClassLoaders(module.getClassLoader(), classLoaders); + } + if (layer.parents().size() > 0) { + for (final ModuleLayer parent : layer.parents()) { + accumulateLayerClassLoaders(parent, classLoaders); + } } - if (!classLoaders.contains(ClassLoader.getSystemClassLoader())) { - classLoaders.add(ClassLoader.getSystemClassLoader()); + } + + /** + * Adds the provided loader to the loaders collection, and traverses up the tree until either a null + * value or a classloader which has already been added is encountered. + */ + private static void accumulateClassLoaders(final ClassLoader loader, final Collection loaders) { + // Some implementations may use null to represent the bootstrap class loader. + if (loader != null && loaders.add(loader)) { + accumulateClassLoaders(loader.getParent(), loaders); } - return classLoaders.toArray(new ClassLoader[classLoaders.size()]); } /** @@ -147,8 +212,9 @@ public static boolean isClassAvailable(final String className) { } /** - * Loads a class by name. This method respects the {@link #IGNORE_TCCL_PROPERTY} Log4j property. If this property is - * specified and set to anything besides {@code false}, then the default ClassLoader will be used. + * Loads a class by name. This method respects the {@value LoggingSystemProperties#LOADER_IGNORE_THREAD_CONTEXT_LOADER} + * Log4j property. If this property is specified and set to anything besides {@code false}, then this class's + * ClassLoader will be used as specified by {@link Class#forName(String)}. * * @param className The class name. * @return the Class for the given name. @@ -160,15 +226,19 @@ public static Class loadClass(final String className) throws ClassNotFoundExc return Class.forName(className); } try { - return getThreadContextClassLoader().loadClass(className); + final ClassLoader tccl = getThreadContextClassLoader(); + if (tccl != null) { + return tccl.loadClass(className); + } } catch (final Throwable ignored) { - return Class.forName(className); } + return Class.forName(className); } /** * Loads and instantiates a Class using the default constructor. * + * @param the type of the class modeled by the {@code Class} object. * @param clazz The class. * @return new instance of the class. * @throws IllegalAccessException if the class can't be instantiated through a public constructor @@ -190,17 +260,17 @@ public static T newInstanceOf(final Class clazz) * Loads and instantiates a Class using the default constructor. * * @param className The class name. + * @param The class's type. * @return new instance of the class. * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders * @throws IllegalAccessException if the class can't be instantiated through a public constructor * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws NoSuchMethodException if there isn't a no-args constructor on the class * @throws InvocationTargetException if there was an exception whilst constructing the class * @since 2.1 */ @SuppressWarnings("unchecked") public static T newInstanceOf(final String className) throws ClassNotFoundException, IllegalAccessException, - InstantiationException, NoSuchMethodException, InvocationTargetException { + InstantiationException, InvocationTargetException { return newInstanceOf((Class) loadClass(className)); } @@ -214,15 +284,14 @@ public static T newInstanceOf(final String className) throws ClassNotFoundEx * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders * @throws IllegalAccessException if the class can't be instantiated through a public constructor * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws NoSuchMethodException if there isn't a no-args constructor on the class * @throws InvocationTargetException if there was an exception whilst constructing the class * @throws ClassCastException if the constructed object isn't type compatible with {@code T} * @since 2.1 */ public static T newCheckedInstanceOf(final String className, final Class clazz) - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, + throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { - return clazz.cast(newInstanceOf(className)); + return newInstanceOf(loadClass(className).asSubclass(clazz)); } /** @@ -235,13 +304,12 @@ public static T newCheckedInstanceOf(final String className, final Class * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders * @throws IllegalAccessException if the class can't be instantiated through a public constructor * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws NoSuchMethodException if there isn't a no-args constructor on the class * @throws InvocationTargetException if there was an exception whilst constructing the class * @throws ClassCastException if the constructed object isn't type compatible with {@code T} * @since 2.5 */ public static T newCheckedInstanceOfProperty(final String propertyName, final Class clazz) - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, + throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { final String className = PropertiesUtil.getProperties().getStringProperty(propertyName); if (className == null) { @@ -253,12 +321,26 @@ public static T newCheckedInstanceOfProperty(final String propertyName, fina private static boolean isIgnoreTccl() { // we need to lazily initialize this, but concurrent access is not an issue if (ignoreTCCL == null) { - final String ignoreTccl = PropertiesUtil.getProperties().getStringProperty(IGNORE_TCCL_PROPERTY, null); + final String ignoreTccl = PropertiesUtil.getProperties().getStringProperty(LoggingSystemProperties.LOADER_IGNORE_THREAD_CONTEXT_LOADER, null); ignoreTCCL = ignoreTccl != null && !"false".equalsIgnoreCase(ignoreTccl.trim()); } return ignoreTCCL; } + private static boolean isForceTccl() { + if (forceTcclOnly == null) { + // PropertiesUtil.getProperties() uses that code path so don't use that! + try { + forceTcclOnly = System.getSecurityManager() == null ? + Boolean.getBoolean(LoggingSystemProperties.LOADER_FORCE_THREAD_CONTEXT_LOADER) : + AccessController.doPrivileged((PrivilegedAction) () -> Boolean.getBoolean(LoggingSystemProperties.LOADER_FORCE_THREAD_CONTEXT_LOADER)); + } catch (final SecurityException se) { + forceTcclOnly = false; + } + } + return forceTcclOnly; + } + /** * Finds classpath {@linkplain URL resources}. * @@ -267,7 +349,11 @@ private static boolean isIgnoreTccl() { * @since 2.1 */ public static Collection findResources(final String resource) { - final Collection urlResources = findUrlResources(resource); + return findResources(resource, true); + } + + public static Collection findResources(final String resource, final boolean useTccl) { + final Collection urlResources = findUrlResources(resource, useTccl); final Collection resources = new LinkedHashSet<>(urlResources.size()); for (final UrlResource urlResource : urlResources) { resources.add(urlResource.getUrl()); @@ -275,12 +361,19 @@ public static Collection findResources(final String resource) { return resources; } - static Collection findUrlResources(final String resource) { + /** + * This method will only find resources that follow the JPMS rules for encapsulation. Resources + * on the class path should be found as normal along with resources with no package name in all + * modules. Resources within packages in modules must declare those resources open to org.apache.logging.log4j. + * @param resource The resource to locate. + * @return The located resources. + */ + public static Collection findUrlResources(final String resource, boolean useTccl) { // @formatter:off final ClassLoader[] candidates = { - getThreadContextClassLoader(), - LoaderUtil.class.getClassLoader(), - GET_CLASS_LOADER_DISABLED ? null : ClassLoader.getSystemClassLoader()}; + getThreadContextClassLoader(), + isForceTccl() ? null : LoaderUtil.class.getClassLoader(), + isForceTccl() || GET_CLASS_LOADER_DISABLED ? null : ClassLoader.getSystemClassLoader()}; // @formatter:on final Collection resources = new LinkedHashSet<>(); for (final ClassLoader cl : candidates) { @@ -301,11 +394,11 @@ static Collection findUrlResources(final String resource) { /** * {@link URL} and {@link ClassLoader} pair. */ - static class UrlResource { + public static class UrlResource { private final ClassLoader classLoader; private final URL url; - UrlResource(final ClassLoader classLoader, final URL url) { + public UrlResource(final ClassLoader classLoader, final URL url) { this.classLoader = classLoader; this.url = url; } @@ -332,11 +425,7 @@ public boolean equals(final Object o) { if (classLoader != null ? !classLoader.equals(that.classLoader) : that.classLoader != null) { return false; } - if (url != null ? !url.equals(that.url) : that.url != null) { - return false; - } - - return true; + return url != null ? url.equals(that.url) : that.url == null; } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.java index 5cf40cabee6..da363defc07 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LowLevelLogUtil.java @@ -14,13 +14,11 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.util; -import java.io.OutputStream; import java.io.PrintWriter; -import java.io.Writer; -import java.util.Objects; + +import org.apache.logging.log4j.Logger; /** * PrintWriter-based logging utility for classes too low level to use {@link org.apache.logging.log4j.status.StatusLogger}. @@ -29,49 +27,89 @@ * * @since 2.6 */ -final class LowLevelLogUtil { +@InternalApi +public final class LowLevelLogUtil { + + interface ErrorLogger { + void error(final String message); + + void error(final Throwable throwable); + + void error(final String message, final Throwable throwable); + } + + private static class StandardErrorLogger implements ErrorLogger { + private final PrintWriter stderr = new PrintWriter(System.err, true); + + @Override + public void error(final String message) { + stderr.println("ERROR: " + message); + } + + @Override + public void error(final Throwable throwable) { + throwable.printStackTrace(stderr); + } + + @Override + public void error(final String message, final Throwable throwable) { + error(message); + error(throwable); + } + } + + private static class DelegateErrorLogger implements ErrorLogger { + private final Logger logger; - private static PrintWriter writer = new PrintWriter(System.err, true); + private DelegateErrorLogger(final Logger logger) { + this.logger = logger; + } + + @Override + public void error(final String message) { + logger.error(message); + } + + @Override + public void error(final Throwable throwable) { + logger.error(throwable); + } + + @Override + public void error(final String message, final Throwable throwable) { + logger.error(message, throwable); + } + } + + private static ErrorLogger errorLogger = new StandardErrorLogger(); + + /** + * Sets the low level logging strategy to use a delegate Logger. + */ + public static void setLogger(final Logger logger) { + errorLogger = new DelegateErrorLogger(logger); + } /** * Logs the given message. - * + * * @param message the message to log * @since 2.9.2 */ public static void log(final String message) { if (message != null) { - writer.println(message); + errorLogger.error(message); } } public static void logException(final Throwable exception) { if (exception != null) { - exception.printStackTrace(writer); + errorLogger.error(exception); } } public static void logException(final String message, final Throwable exception) { - log(message); - logException(exception); - } - - /** - * Sets the underlying OutputStream where exceptions are printed to. - * - * @param out the OutputStream to log to - */ - public static void setOutputStream(final OutputStream out) { - LowLevelLogUtil.writer = new PrintWriter(Objects.requireNonNull(out), true); - } - - /** - * Sets the underlying Writer where exceptions are printed to. - * - * @param writer the Writer to log to - */ - public static void setWriter(final Writer writer) { - LowLevelLogUtil.writer = new PrintWriter(Objects.requireNonNull(writer), true); + errorLogger.error(message, exception); } private LowLevelLogUtil() { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/MessageSupplier.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/MessageSupplier.java index f38f75c1c83..8bed8d6b7e9 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/MessageSupplier.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/MessageSupplier.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.util; import org.apache.logging.log4j.message.Message; @@ -28,18 +27,11 @@ *

Implementors are free to cache values or return a new or distinct value each time the supplier is invoked. * *

DEPRECATED: this class should not normally be used outside a Java 8+ lambda syntax. Instead, - * {@link Supplier Supplier} should be used as an anonymous class. Both this and {@link Supplier} will be + * {@link Supplier Supplier<Message>} should be used as an anonymous class. Both this and {@link Supplier} will be * removed in 3.0. *

* * @since 2.4 */ -public interface MessageSupplier { - - /** - * Gets a Message. - * - * @return a Message - */ - Message get(); +public interface MessageSupplier extends java.util.function.Supplier { } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/NameUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/NameUtil.java new file mode 100644 index 00000000000..97bedb0b52f --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/NameUtil.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; + +/** + * + */ +@InternalApi +public final class NameUtil { + + private NameUtil() {} + + public static String getSubName(final String name) { + if (Strings.isEmpty(name)) { + return null; + } + final int i = name.lastIndexOf('.'); + return i > 0 ? name.substring(0, i) : Strings.EMPTY; + } + + /** + * Calculates the MD5 hash + * of the given input string encoded using the default platform + * {@link Charset charset}. + *

+ * MD5 has severe vulnerabilities and should not be used for sharing any + * sensitive information. This function should only be used to create + * unique identifiers, e.g., configuration element names. + * + * @param input string to be hashed + * @return string composed of 32 hexadecimal digits of the calculated hash + */ + public static String md5(final String input) { + Objects.requireNonNull(input, "input"); + try { + final byte[] inputBytes = input.getBytes(); + final MessageDigest digest = MessageDigest.getInstance("MD5"); + final byte[] bytes = digest.digest(inputBytes); + final StringBuilder md5 = new StringBuilder(bytes.length * 2); + for (final byte b : bytes) { + final String hex = Integer.toHexString(0xFF & b); + if (hex.length() == 1) { + md5.append('0'); + } + md5.append(hex); + } + return md5.toString(); + } + // Every implementation of the Java platform is required to support MD5. + // Hence, this catch block should be unreachable. + // See https://docs.oracle.com/javase/8/docs/api/java/security/MessageDigest.html + // for details. + catch (final NoSuchAlgorithmException error) { + throw new RuntimeException(error); + } + } + +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PerformanceSensitive.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PerformanceSensitive.java index 694175cc12d..3d7f1f16e2e 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PerformanceSensitive.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PerformanceSensitive.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.util; import java.lang.annotation.Retention; @@ -32,6 +31,9 @@ // No @Target: No restrictions yet on what code elements may be annotated or not. @Retention(RetentionPolicy.CLASS) // Currently no need to reflectively discover this annotation at runtime. public @interface PerformanceSensitive { - /** Description of why this is written the way it is. */ + /** + * Is this ever used? + * @return An array of strings. + */ String[] value() default ""; } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PrivateSecurityManagerStackTraceUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PrivateSecurityManagerStackTraceUtil.java new file mode 100644 index 00000000000..2f2be079864 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PrivateSecurityManagerStackTraceUtil.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * Internal utility to share a fast implementation of {@code #getCurrentStackTrace()} + * with the java 9 implementation of {@link StackLocator}. + */ +final class PrivateSecurityManagerStackTraceUtil { + + private static final PrivateSecurityManager SECURITY_MANAGER; + + static { + PrivateSecurityManager psm; + try { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new RuntimePermission("createSecurityManager")); + } + psm = new PrivateSecurityManager(); + } catch (final SecurityException ignored) { + psm = null; + } + + SECURITY_MANAGER = psm; + } + + private PrivateSecurityManagerStackTraceUtil() { + // Utility Class + } + + static boolean isEnabled() { + return SECURITY_MANAGER != null; + } + + /** + * Returns the current execution stack as a Deque of classes. + *

+ * The size of the Deque is the number of methods on the execution stack. The first element is the class that started + * execution on this thread, the next element is the class that was called next, and so on, until the last element: the + * method that called {@link SecurityManager#getClassContext()} to capture the stack. + *

+ * + * @return the execution stack. + */ + // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int) + static Deque> getCurrentStackTrace() { + final Class[] array = SECURITY_MANAGER.getClassContext(); + final Deque> classes = new ArrayDeque<>(array.length); + for (final Class clazz : array) { + classes.push(clazz); + } + return classes; + } + + private static final class PrivateSecurityManager extends SecurityManager { + + @Override + protected Class[] getClassContext() { + return super.getClassContext(); + } + + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java index 685aabecc81..0c1622c1b2a 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java @@ -16,36 +16,16 @@ */ package org.apache.logging.log4j.util; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Method; - -/** - * @Since 2.9 - */ +@InternalApi public class ProcessIdUtil { public static final String DEFAULT_PROCESSID = "-"; public static String getProcessId() { try { - // LOG4J2-2126 use reflection to improve compatibility with Android Platform which does not support JMX extensions - Class managementFactoryClass = Class.forName("java.lang.management.ManagementFactory"); - Method getRuntimeMXBean = managementFactoryClass.getDeclaredMethod("getRuntimeMXBean"); - Class runtimeMXBeanClass = Class.forName("java.lang.management.RuntimeMXBean"); - Method getName = runtimeMXBeanClass.getDeclaredMethod("getName"); - - Object runtimeMXBean = getRuntimeMXBean.invoke(null); - String name = (String) getName.invoke(runtimeMXBean); - //String name = ManagementFactory.getRuntimeMXBean().getName(); //JMX not allowed on Android - return name.split("@")[0]; // likely works on most platforms - } catch (final Exception ex) { - try { - return new File("/proc/self").getCanonicalFile().getName(); // try a Linux-specific way - } catch (final IOException ignoredUseDefault) { - // Ignore exception. - } + return Long.toString(ProcessHandle.current().pid()); + } catch(final Exception ex) { + return DEFAULT_PROCESSID; } - return DEFAULT_PROCESSID; } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.java index 6f3e8f44b2f..bb96fc498ee 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.util; +import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Properties; @@ -27,17 +29,24 @@ */ public class PropertiesPropertySource implements PropertySource { + private static final int DEFAULT_PRIORITY = 200; private static final String PREFIX = "log4j2."; private final Properties properties; + private final int priority; public PropertiesPropertySource(final Properties properties) { + this(properties, DEFAULT_PRIORITY); + } + + public PropertiesPropertySource(final Properties properties, final int priority) { this.properties = properties; + this.priority = priority; } @Override public int getPriority() { - return 0; + return priority; } @Override @@ -49,6 +58,23 @@ public void forEach(final BiConsumer action) { @Override public CharSequence getNormalForm(final Iterable tokens) { - return PREFIX + Util.joinAsCamelCase(tokens); + final CharSequence camelCase = Util.joinAsCamelCase(tokens); + return camelCase.length() > 0 ? PREFIX + camelCase : null; + } + + @Override + public Collection getPropertyNames() { + return properties.stringPropertyNames(); + } + + @Override + public String getProperty(String key) { + return properties.getProperty(key); + } + + @Override + public boolean containsProperty(String key) { + return getProperty(key) != null; } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java index f5e1ecb5610..643e52b7348 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java @@ -16,19 +16,18 @@ */ package org.apache.logging.log4j.util; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; +import java.lang.invoke.MethodHandles; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.ResourceBundle; import java.util.ServiceLoader; import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; /** * Consider this class private. @@ -43,10 +42,13 @@ * * @see PropertySource */ -public final class PropertiesUtil { +@InternalApi +public class PropertiesUtil implements PropertyEnvironment { private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties"; - private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME); + private static final String LOG4J_SYSTEM_PROPERTIES_FILE_NAME = "log4j2.system.properties"; + private static final Lazy COMPONENT_PROPERTIES = + Lazy.lazy(() -> new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME)); private final Environment environment; @@ -56,7 +58,7 @@ public final class PropertiesUtil { * @param props the Properties to use by default */ public PropertiesUtil(final Properties props) { - this.environment = new Environment(new PropertiesPropertySource(props)); + this(new PropertiesPropertySource(props)); } /** @@ -66,32 +68,19 @@ public PropertiesUtil(final Properties props) { * @param propertiesFileName the location of properties file to load */ public PropertiesUtil(final String propertiesFileName) { - this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName)); + this(propertiesFileName, true); + } + + private PropertiesUtil(final String propertiesFileName, final boolean useTccl) { + this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName, useTccl)); } /** - * Loads and closes the given property input stream. If an error occurs, log to the status logger. - * - * @param in a property input stream. - * @param source a source object describing the source, like a resource string or a URL. - * @return a new Properties object + * Constructs a PropertiesUtil for a give property source as source of additional properties. + * @param source a property source */ - static Properties loadClose(final InputStream in, final Object source) { - final Properties props = new Properties(); - if (null != in) { - try { - props.load(in); - } catch (final IOException e) { - LowLevelLogUtil.logException("Unable to read " + source, e); - } finally { - try { - in.close(); - } catch (final IOException e) { - LowLevelLogUtil.logException("Unable to close " + source, e); - } - } - } - return props; + PropertiesUtil(final PropertySource source) { + this.environment = new Environment(source); } /** @@ -100,94 +89,33 @@ static Properties loadClose(final InputStream in, final Object source) { * @return the main Log4j PropertiesUtil instance. */ public static PropertiesUtil getProperties() { - return LOG4J_PROPERTIES; - } - - /** - * Returns {@code true} if the specified property is defined, regardless of its value (it may not have a value). - * - * @param name the name of the property to verify - * @return {@code true} if the specified property is defined, regardless of its value - */ - public boolean hasProperty(final String name) { - return environment.containsKey(name); + return COMPONENT_PROPERTIES.value(); } - /** - * Gets the named property as a boolean value. If the property matches the string {@code "true"} (case-insensitive), - * then it is returned as the boolean value {@code true}. Any other non-{@code null} text in the property is - * considered {@code false}. - * - * @param name the name of the property to look up - * @return the boolean value of the property or {@code false} if undefined. - */ - public boolean getBooleanProperty(final String name) { - return getBooleanProperty(name, false); - } - - /** - * Gets the named property as a boolean value. - * - * @param name the name of the property to look up - * @param defaultValue the default value to use if the property is undefined - * @return the boolean value of the property or {@code defaultValue} if undefined. - */ - public boolean getBooleanProperty(final String name, final boolean defaultValue) { - final String prop = getStringProperty(name); - return prop == null ? defaultValue : "true".equalsIgnoreCase(prop); + public static PropertyEnvironment getProperties(final String namespace) { + return new Environment(new PropertyFilePropertySource(String.format("log4j2.%s.properties", namespace))); } - /** - * Gets the named property as a boolean value. - * - * @param name the name of the property to look up - * @param defaultValueIfAbsent the default value to use if the property is undefined - * @param defaultValueIfPresent the default value to use if the property is defined but not assigned - * @return the boolean value of the property or {@code defaultValue} if undefined. - */ - public boolean getBooleanProperty(final String name, final boolean defaultValueIfAbsent, - final boolean defaultValueIfPresent) { - final String prop = getStringProperty(name); - return prop == null ? defaultValueIfAbsent - : prop.isEmpty() ? defaultValueIfPresent : "true".equalsIgnoreCase(prop); + public static ResourceBundle getCharsetsResourceBundle() { + return ResourceBundle.getBundle("Log4j-charsets"); } - /** - * Gets the named property as a Charset value. - * - * @param name the name of the property to look up - * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined. - */ - public Charset getCharsetProperty(final String name) { - return getCharsetProperty(name, Charset.defaultCharset()); + @Override + public void addPropertySource(PropertySource propertySource) { + if (environment != null) { + environment.addPropertySource(propertySource); + } } /** - * Gets the named property as a Charset value. If we cannot find the named Charset, see if it is mapped in - * file {@code Log4j-charsets.properties} on the class path. + * Returns {@code true} if the specified property is defined, regardless of its value (it may not have a value). * - * @param name the name of the property to look up - * @param defaultValue the default value to use if the property is undefined - * @return the Charset value of the property or {@code defaultValue} if undefined. + * @param name the name of the property to verify + * @return {@code true} if the specified property is defined, regardless of its value */ - public Charset getCharsetProperty(final String name, final Charset defaultValue) { - final String charsetName = getStringProperty(name); - if (charsetName == null) { - return defaultValue; - } - if (Charset.isSupported(charsetName)) { - return Charset.forName(charsetName); - } - ResourceBundle bundle = getCharsetsResourceBundle(); - if (bundle.containsKey(name)) { - String mapped = bundle.getString(name); - if (Charset.isSupported(mapped)) { - return Charset.forName(mapped); - } - } - LowLevelLogUtil.log("Unable to get Charset '" + charsetName + "' for property '" + name + "', using default " - + defaultValue + " and continuing."); - return defaultValue; + @Override + public boolean hasProperty(final String name) { + return environment.hasProperty(name); } /** @@ -209,65 +137,15 @@ public double getDoubleProperty(final String name, final double defaultValue) { return defaultValue; } - /** - * Gets the named property as an integer. - * - * @param name the name of the property to look up - * @param defaultValue the default value to use if the property is undefined - * @return the parsed integer value of the property or {@code defaultValue} if it was undefined or could not be - * parsed. - */ - public int getIntegerProperty(final String name, final int defaultValue) { - final String prop = getStringProperty(name); - if (prop != null) { - try { - return Integer.parseInt(prop); - } catch (final Exception ignored) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Gets the named property as a long. - * - * @param name the name of the property to look up - * @param defaultValue the default value to use if the property is undefined - * @return the parsed long value of the property or {@code defaultValue} if it was undefined or could not be parsed. - */ - public long getLongProperty(final String name, final long defaultValue) { - final String prop = getStringProperty(name); - if (prop != null) { - try { - return Long.parseLong(prop); - } catch (final Exception ignored) { - return defaultValue; - } - } - return defaultValue; - } - /** * Gets the named property as a String. * * @param name the name of the property to look up * @return the String value of the property or {@code null} if undefined. */ + @Override public String getStringProperty(final String name) { - return environment.get(name); - } - - /** - * Gets the named property as a String. - * - * @param name the name of the property to look up - * @param defaultValue the default value to use if the property is undefined - * @return the String value of the property or {@code defaultValue} if undefined. - */ - public String getStringProperty(final String name, final String defaultValue) { - final String prop = getStringProperty(name); - return (prop == null) ? defaultValue : prop; + return environment.getStringProperty(name); } /** @@ -307,68 +185,110 @@ public void reload() { * * @since 2.10.0 */ - private static class Environment { - - private final Set sources = new TreeSet<>(new PropertySource.Comparator()); - private final Map literal = new ConcurrentHashMap<>(); - private final Map normalized = new ConcurrentHashMap<>(); + private static class Environment implements PropertyEnvironment { + + private final Set sources = new ConcurrentSkipListSet<>(new PropertySource.Comparator()); + /** + * Maps a key to its value in the lowest priority source that contains it. + */ + private final Map literal = new ConcurrentHashMap<>(); + /** + * Maps a key to the value associated to its normalization in the lowest + * priority source that contains it. + */ + private final Map normalized = new ConcurrentHashMap<>(); private final Map, String> tokenized = new ConcurrentHashMap<>(); private Environment(final PropertySource propertySource) { - sources.add(propertySource); - for (final PropertySource source : ServiceLoader.load(PropertySource.class)) { - sources.add(source); + final PropertyFilePropertySource sysProps = new PropertyFilePropertySource(LOG4J_SYSTEM_PROPERTIES_FILE_NAME); + try { + sysProps.forEach((key, value) -> { + if (System.getProperty(key) == null) { + System.setProperty(key, value); + } + }); + } catch (final SecurityException ex) { + // Access to System Properties is restricted so just skip it. } + sources.add(propertySource); + final ServiceRegistry registry = ServiceRegistry.getInstance(); + // Does not log errors using StatusLogger, which depends on PropertiesUtil being initialized. + sources.addAll(registry.getServices(PropertySource.class, MethodHandles.lookup(), null, /*verbose=*/false)); reload(); } + @Override + public void addPropertySource(final PropertySource propertySource) { + sources.add(propertySource); + } + private synchronized void reload() { literal.clear(); normalized.clear(); tokenized.clear(); - for (final PropertySource source : sources) { - source.forEach(new BiConsumer() { - @Override - public void accept(final String key, final String value) { - literal.put(key, value); - final List tokens = PropertySource.Util.tokenize(key); - if (tokens.isEmpty()) { - normalized.put(source.getNormalForm(Collections.singleton(key)), value); - } else { - normalized.put(source.getNormalForm(tokens), value); - tokenized.put(tokens, value); + // 1. Collects all property keys from enumerable sources. + final Set keys = new HashSet<>(); + sources.stream() + .map(PropertySource::getPropertyNames) + .forEach(keys::addAll); + // 2. Fills the property caches. Sources with higher priority values don't override the previous ones. + keys.stream() + .filter(Objects::nonNull) + .forEach(key -> { + final List tokens = PropertySource.Util.tokenize(key); + final boolean hasTokens = !tokens.isEmpty(); + sources.forEach(source -> { + if (source.containsProperty(key)) { + final String value = source.getProperty(key); + literal.putIfAbsent(key, value); + if (hasTokens) { + tokenized.putIfAbsent(tokens, value); + } } - } + if (hasTokens) { + final String normalKey = Objects.toString(source.getNormalForm(tokens), null); + if (normalKey != null && source.containsProperty(normalKey)) { + normalized.putIfAbsent(key, source.getProperty(normalKey)); + } + } + }); }); - } - } - - private static boolean hasSystemProperty(final String key) { - try { - return System.getProperties().containsKey(key); - } catch (final SecurityException ignored) { - return false; - } } - private String get(final String key) { + @Override + public String getStringProperty(final String key) { if (normalized.containsKey(key)) { return normalized.get(key); } if (literal.containsKey(key)) { return literal.get(key); } - if (hasSystemProperty(key)) { - return System.getProperty(key); + final List tokens = PropertySource.Util.tokenize(key); + final boolean hasTokens = !tokens.isEmpty(); + for (final PropertySource source : sources) { + if (hasTokens) { + final String normalKey = Objects.toString(source.getNormalForm(tokens), null); + if (normalKey != null && source.containsProperty(normalKey)) { + return source.getProperty(normalKey); + } + } + if (source.containsProperty(key)) { + return source.getProperty(key); + } } - return tokenized.get(PropertySource.Util.tokenize(key)); + return tokenized.get(tokens); } - private boolean containsKey(final String key) { + @Override + public boolean hasProperty(final String key) { + List tokens = PropertySource.Util.tokenize(key); return normalized.containsKey(key) || - literal.containsKey(key) || - hasSystemProperty(key) || - tokenized.containsKey(PropertySource.Util.tokenize(key)); + literal.containsKey(key) || + tokenized.containsKey(tokens) || + sources.stream().anyMatch(s -> { + final CharSequence normalizedKey = s.getNormalForm(tokens); + return s.containsProperty(key) || normalizedKey != null && s.containsProperty(normalizedKey.toString()); + }); } } @@ -404,10 +324,6 @@ public static Properties extractSubset(final Properties properties, final String return subset; } - static ResourceBundle getCharsetsResourceBundle() { - return ResourceBundle.getBundle("Log4j-charsets"); - } - /** * Partitions a properties map based on common key prefixes up to the first period. * @@ -417,24 +333,40 @@ static ResourceBundle getCharsetsResourceBundle() { * @since 2.6 */ public static Map partitionOnCommonPrefixes(final Properties properties) { + return partitionOnCommonPrefixes(properties, false); + } + + /** + * Partitions a properties map based on common key prefixes up to the first period. + * + * @param properties properties to partition + * @param includeBaseKey when true if a key exists with no '.' the key will be included. + * @return the partitioned properties where each key is the common prefix (minus the period) and the values are + * new property maps without the prefix and period in the key + * @since 2.17.2 + */ + public static Map partitionOnCommonPrefixes(final Properties properties, + final boolean includeBaseKey) { final Map parts = new ConcurrentHashMap<>(); for (final String key : properties.stringPropertyNames()) { - final String prefix = key.substring(0, key.indexOf('.')); + final int idx = key.indexOf('.'); + if (idx < 0) { + if (includeBaseKey) { + if (!parts.containsKey(key)) { + parts.put(key, new Properties()); + } + parts.get(key).setProperty("", properties.getProperty(key)); + } + continue; + } + final String prefix = key.substring(0, idx); if (!parts.containsKey(prefix)) { parts.put(prefix, new Properties()); } - parts.get(prefix).setProperty(key.substring(key.indexOf('.') + 1), properties.getProperty(key)); + parts.get(prefix).setProperty(key.substring(idx + 1), properties.getProperty(key)); } return parts; } - /** - * Returns true if system properties tell us we are running on Windows. - * - * @return true if system properties tell us we are running on Windows. - */ - public boolean isOsWindows() { - return getStringProperty("os.name", "").startsWith("Windows"); - } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyEnvironment.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyEnvironment.java new file mode 100644 index 00000000000..fb53ec42372 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyEnvironment.java @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.ResourceBundle; + +public interface PropertyEnvironment { + + /** + * Allows a PropertySource to be added after PropertiesUtil has been created. + * @param propertySource the PropertySource to add. + */ + void addPropertySource(PropertySource propertySource); + + /** + * Returns {@code true} if the specified property is defined, regardless of its value (it may not have a value). + * + * @param name the name of the property to verify + * @return {@code true} if the specified property is defined, regardless of its value + */ + boolean hasProperty(String name); + + /** + * Gets the named property as a boolean value. If the property matches the string {@code "true"} (case-insensitive), + * then it is returned as the boolean value {@code true}. Any other non-{@code null} text in the property is + * considered {@code false}. + * + * @param name the name of the property to look up + * @return the boolean value of the property or {@code false} if undefined. + */ + default boolean getBooleanProperty(String name) { + return getBooleanProperty(name, false); + } + + /** + * Gets the named property as a boolean value. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the boolean value of the property or {@code defaultValue} if undefined. + */ + default boolean getBooleanProperty(String name, boolean defaultValue) { + final String prop = getStringProperty(name); + return prop == null ? defaultValue : "true".equalsIgnoreCase(prop); + } + + /** + * Gets the named property as a boolean value. + * + * @param name the name of the property to look up + * @param defaultValueIfAbsent the default value to use if the property is undefined + * @param defaultValueIfPresent the default value to use if the property is defined but not assigned + * @return the boolean value of the property or {@code defaultValue} if undefined. + */ + default boolean getBooleanProperty(String name, boolean defaultValueIfAbsent, + boolean defaultValueIfPresent) { + final String prop = getStringProperty(name); + return prop == null ? defaultValueIfAbsent + : prop.isEmpty() ? defaultValueIfPresent : "true".equalsIgnoreCase(prop); + } + + /** + * Retrieves a property that may be prefixed by more than one string. + * @param prefixes The array of prefixes. + * @param key The key to locate. + * @param supplier The method to call to derive the default value. If the value is null, null will be returned + * if no property is found. + * @return The value or null if it is not found. + * @since 2.13.0 + */ + default Boolean getBooleanProperty(String[] prefixes, String key, Supplier supplier) { + for (final String prefix : prefixes) { + if (hasProperty(prefix + key)) { + return getBooleanProperty(prefix + key); + } + } + return supplier != null ? supplier.get() : null; + } + + /** + * Gets the named property as a Charset value. + * + * @param name the name of the property to look up + * @return the Charset value of the property or {@link Charset#defaultCharset()} if undefined. + */ + default Charset getCharsetProperty(String name) { + return getCharsetProperty(name, Charset.defaultCharset()); + } + + /** + * Gets the named property as a Charset value. If we cannot find the named Charset, see if it is mapped in + * file {@code Log4j-charsets.properties} on the class path. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the Charset value of the property or {@code defaultValue} if undefined. + */ + default Charset getCharsetProperty(String name, Charset defaultValue) { + final String charsetName = getStringProperty(name); + if (charsetName == null) { + return defaultValue; + } + if (Charset.isSupported(charsetName)) { + return Charset.forName(charsetName); + } + final ResourceBundle bundle = PropertiesUtil.getCharsetsResourceBundle(); + if (bundle.containsKey(name)) { + final String mapped = bundle.getString(name); + if (Charset.isSupported(mapped)) { + return Charset.forName(mapped); + } + } + LowLevelLogUtil.log("Unable to get Charset '" + charsetName + "' for property '" + name + "', using default " + + defaultValue + " and continuing."); + return defaultValue; + } + + /** + * Gets the named property as an integer. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the parsed integer value of the property or {@code defaultValue} if it was undefined or could not be + * parsed. + */ + default int getIntegerProperty(String name, int defaultValue) { + final String prop = getStringProperty(name); + if (prop != null) { + try { + return Integer.parseInt(prop); + } catch (final Exception ignored) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Retrieves a property that may be prefixed by more than one string. + * @param prefixes The array of prefixes. + * @param key The key to locate. + * @param supplier The method to call to derive the default value. If the value is null, null will be returned + * if no property is found. + * @return The value or null if it is not found. + * @since 2.13.0 + */ + default Integer getIntegerProperty(String[] prefixes, String key, Supplier supplier) { + for (final String prefix : prefixes) { + if (hasProperty(prefix + key)) { + return getIntegerProperty(prefix + key, 0); + } + } + return supplier != null ? supplier.get() : null; + } + + /** + * Gets the named property as a long. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the parsed long value of the property or {@code defaultValue} if it was undefined or could not be parsed. + */ + default long getLongProperty(String name, long defaultValue) { + final String prop = getStringProperty(name); + if (prop != null) { + try { + return Long.parseLong(prop); + } catch (final Exception ignored) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Retrieves a property that may be prefixed by more than one string. + * @param prefixes The array of prefixes. + * @param key The key to locate. + * @param supplier The method to call to derive the default value. If the value is null, null will be returned + * if no property is found. + * @return The value or null if it is not found. + * @since 2.13.0 + */ + default Long getLongProperty(String[] prefixes, String key, Supplier supplier) { + for (final String prefix : prefixes) { + if (hasProperty(prefix + key)) { + return getLongProperty(prefix + key, 0); + } + } + return supplier != null ? supplier.get() : null; + } + + /** + * Retrieves a Duration where the String is of the format nnn[unit] where nnn represents an integer value + * and unit represents a time unit. + * @param name The property name. + * @param defaultValue The default value. + * @return The value of the String as a Duration or the default value, which may be null. + * @since 2.13.0 + */ + default Duration getDurationProperty(String name, Duration defaultValue) { + final String prop = getStringProperty(name); + if (prop != null) { + return TimeUnit.getDuration(prop); + } + return defaultValue; + } + + /** + * Retrieves a property that may be prefixed by more than one string. + * @param prefixes The array of prefixes. + * @param key The key to locate. + * @param supplier The method to call to derive the default value. If the value is null, null will be returned + * if no property is found. + * @return The value or null if it is not found. + * @since 2.13.0 + */ + default Duration getDurationProperty(String[] prefixes, String key, Supplier supplier) { + for (final String prefix : prefixes) { + if (hasProperty(prefix + key)) { + return getDurationProperty(prefix + key, null); + } + } + return supplier != null ? supplier.get() : null; + } + + /** + * Retrieves a property that may be prefixed by more than one string. + * @param prefixes The array of prefixes. + * @param key The key to locate. + * @param supplier The method to call to derive the default value. If the value is null, null will be returned + * if no property is found. + * @return The value or null if it is not found. + * @since 2.13.0 + */ + default String getStringProperty(String[] prefixes, String key, Supplier supplier) { + for (final String prefix : prefixes) { + final String result = getStringProperty(prefix + key); + if (result != null) { + return result; + } + } + return supplier != null ? supplier.get() : null; + } + + /** + * Gets the named property as a String. + * + * @param name the name of the property to look up + * @return the String value of the property or {@code null} if undefined. + */ + String getStringProperty(String name); + + /** + * Gets the named property as a String. + * + * @param name the name of the property to look up + * @param defaultValue the default value to use if the property is undefined + * @return the String value of the property or {@code defaultValue} if undefined. + */ + default String getStringProperty(String name, String defaultValue) { + final String prop = getStringProperty(name); + return (prop == null) ? defaultValue : prop; + } + + /** + * Returns true if system properties tell us we are running on Windows. + * + * @return true if system properties tell us we are running on Windows. + */ + default boolean isOsWindows() { + return getStringProperty("os.name", "").startsWith("Windows"); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java index 3adbb24ea35..318011524ae 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java @@ -29,24 +29,23 @@ public class PropertyFilePropertySource extends PropertiesPropertySource { public PropertyFilePropertySource(final String fileName) { - super(loadPropertiesFile(fileName)); + this(fileName, true); } - private static Properties loadPropertiesFile(final String fileName) { + public PropertyFilePropertySource(final String fileName, final boolean useTccl) { + super(loadPropertiesFile(fileName, useTccl)); + } + + private static Properties loadPropertiesFile(final String fileName, final boolean useTccl) { final Properties props = new Properties(); - for (final URL url : LoaderUtil.findResources(fileName)) { + for (final URL url : LoaderUtil.findResources(fileName, useTccl)) { try (final InputStream in = url.openStream()) { props.load(in); - } catch (IOException e) { + } catch (final IOException e) { LowLevelLogUtil.logException("Unable to read " + url, e); } } return props; } - @Override - public int getPriority() { - return 0; - } - } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java index 75399d922e1..4a3e29f0741 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java @@ -18,6 +18,9 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -34,7 +37,7 @@ public interface PropertySource { /** * Returns the order in which this PropertySource has priority. A higher value means that the source will be - * applied later so as to take precedence over other property sources. + * searched later and can be overridden by other property sources. * * @return priority value */ @@ -45,7 +48,17 @@ public interface PropertySource { * * @param action action to perform on each key/value pair */ - void forEach(BiConsumer action); + default void forEach(final BiConsumer action) { + } + + /** + * Returns the list of all property names. + * + * @return list of property names + */ + default Collection getPropertyNames() { + return Collections.emptySet(); + } /** * Converts a list of property name tokens into a normal form. For example, a list of tokens such as @@ -54,7 +67,30 @@ public interface PropertySource { * @param tokens list of property name tokens * @return a normalized property name using the given tokens */ - CharSequence getNormalForm(Iterable tokens); + default CharSequence getNormalForm(final Iterable tokens) { + return null; + } + + /** + * For PropertySources that cannot iterate over all the potential properties this provides a direct lookup. + * @param key The key to search for. + * @return The value or null; + * @since 2.13.0 + */ + default String getProperty(final String key) { + return null; + } + + + /** + * For PropertySources that cannot iterate over all the potential properties this provides a direct lookup. + * @param key The key to search for. + * @return The value or null; + * @since 2.13.0 + */ + default boolean containsProperty(final String key) { + return false; + } /** * Comparator for ordering PropertySource instances by priority. @@ -76,9 +112,19 @@ public int compare(final PropertySource o1, final PropertySource o2) { * @since 2.10.0 */ final class Util { - private static final String PREFIXES = "(?i:^log4j2?[-._/]?|^org\\.apache\\.logging\\.log4j\\.)?"; - private static final Pattern PROPERTY_TOKENIZER = Pattern.compile(PREFIXES + "([A-Z]*[a-z0-9]+|[A-Z0-9]+)[-._/]?"); + private static final Pattern PREFIX_PATTERN = Pattern.compile( + // just lookahead for AsyncLogger + "(^log4j2?[-._/]?|^org\\.apache\\.logging\\.log4j\\.)|(?=AsyncLogger(Config)?\\.)", + Pattern.CASE_INSENSITIVE); + private static final Pattern PROPERTY_TOKENIZER = Pattern.compile("([A-Z]*[a-z0-9]+|[A-Z0-9]+)[-._/]?"); private static final Map> CACHE = new ConcurrentHashMap<>(); + static { + // Add legacy properties without Log4j prefix + CACHE.put("disableThreadContext", Arrays.asList("disable", "thread", "context")); + CACHE.put("disableThreadContextStack", Arrays.asList("disable", "thread", "context", "stack")); + CACHE.put("disableThreadContextMap", Arrays.asList("disable", "thread", "context", "map")); + CACHE.put("isThreadContextMapInheritable", Arrays.asList("is", "thread", "context", "map", "inheritable")); + } /** * Converts a property name string into a list of tokens. This will strip a prefix of {@code log4j}, @@ -93,10 +139,16 @@ public static List tokenize(final CharSequence value) { if (CACHE.containsKey(value)) { return CACHE.get(value); } - List tokens = new ArrayList<>(); - final Matcher matcher = PROPERTY_TOKENIZER.matcher(value); - while (matcher.find()) { - tokens.add(matcher.group(1).toLowerCase()); + final List tokens = new ArrayList<>(); + int start = 0; + final Matcher prefixMatcher = PREFIX_PATTERN.matcher(value); + if (prefixMatcher.find(start)) { + start = prefixMatcher.end(); + final Matcher matcher = PROPERTY_TOKENIZER.matcher(value); + while (matcher.find(start)) { + tokens.add(matcher.group(1).toLowerCase()); + start = matcher.end(); + } } CACHE.put(value, tokens); return tokens; diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java deleted file mode 100644 index 200a6f320a6..00000000000 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import java.io.IOException; -import java.net.URL; -import java.util.Collection; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Properties; -import java.util.ServiceLoader; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.spi.Provider; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * Consider this class private. Utility class for Log4j {@link Provider}s. When integrating with an application - * container framework, any Log4j Providers not accessible through standard classpath scanning should - * {@link #loadProvider(java.net.URL, ClassLoader)} a classpath accordingly. - */ -public final class ProviderUtil { - - /** - * Resource name for a Log4j 2 provider properties file. - */ - protected static final String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties"; - - /** - * Loaded providers. - */ - protected static final Collection PROVIDERS = new HashSet<>(); - - /** - * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support. - * - * @since 2.1 - */ - protected static final Lock STARTUP_LOCK = new ReentrantLock(); - - private static final String API_VERSION = "Log4jAPIVersion"; - private static final String[] COMPATIBLE_API_VERSIONS = {"2.6.0"}; - private static final Logger LOGGER = StatusLogger.getLogger(); - - // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and - // wait for a Provider to be installed. See LOG4J2-373 - private static volatile ProviderUtil instance; - - private ProviderUtil() { - for (ClassLoader classLoader : LoaderUtil.getClassLoaders()) { - try { - loadProviders(classLoader); - } catch (Throwable ex) { - LOGGER.debug("Unable to retrieve provider from ClassLoader {}", classLoader, ex); - } - } - for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) { - loadProvider(resource.getUrl(), resource.getClassLoader()); - } - } - - protected static void addProvider(final Provider provider) { - PROVIDERS.add(provider); - LOGGER.debug("Loaded Provider {}", provider); - } - - /** - * Loads an individual Provider implementation. This method is really only useful for the OSGi bundle activator and - * this class itself. - * - * @param url the URL to the provider properties file - * @param cl the ClassLoader to load the provider classes with - */ - protected static void loadProvider(final URL url, final ClassLoader cl) { - try { - final Properties props = PropertiesUtil.loadClose(url.openStream(), url); - if (validVersion(props.getProperty(API_VERSION))) { - final Provider provider = new Provider(props, url, cl); - PROVIDERS.add(provider); - LOGGER.debug("Loaded Provider {}", provider); - } - } catch (final IOException e) { - LOGGER.error("Unable to open {}", url, e); - } - } - - protected static void loadProviders(final ClassLoader cl) { - final ServiceLoader serviceLoader = ServiceLoader.load(Provider.class, cl); - for (final Provider provider : serviceLoader) { - if (validVersion(provider.getVersions()) && !PROVIDERS.contains(provider)) { - PROVIDERS.add(provider); - } - } - } - - /** - * @deprecated Use {@link #loadProvider(java.net.URL, ClassLoader)} instead. Will be removed in 3.0. - */ - @Deprecated - protected static void loadProviders(final Enumeration urls, final ClassLoader cl) { - if (urls != null) { - while (urls.hasMoreElements()) { - loadProvider(urls.nextElement(), cl); - } - } - } - - public static Iterable getProviders() { - lazyInit(); - return PROVIDERS; - } - - public static boolean hasProviders() { - lazyInit(); - return !PROVIDERS.isEmpty(); - } - - /** - * Lazily initializes the ProviderUtil singleton. - * - * @since 2.1 - */ - protected static void lazyInit() { - // noinspection DoubleCheckedLocking - if (instance == null) { - try { - STARTUP_LOCK.lockInterruptibly(); - try { - if (instance == null) { - instance = new ProviderUtil(); - } - } finally { - STARTUP_LOCK.unlock(); - } - } catch (final InterruptedException e) { - LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e); - Thread.currentThread().interrupt(); - } - } - } - - public static ClassLoader findClassLoader() { - return LoaderUtil.getThreadContextClassLoader(); - } - - private static boolean validVersion(final String version) { - for (final String v : COMPATIBLE_API_VERSIONS) { - if (version.startsWith(v)) { - return true; - } - } - return false; - } -} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ReadOnlyStringMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ReadOnlyStringMap.java index df9a1c80552..cd76c5730b7 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ReadOnlyStringMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ReadOnlyStringMap.java @@ -88,6 +88,7 @@ public interface ReadOnlyStringMap extends Serializable { * Returns the value for the specified key, or {@code null} if the specified key does not exist in this collection. * * @param key the key whose value to return. + * @param The type of the value to be returned. * @return the value for the specified key or {@code null}. */ V getValue(final String key); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ReflectionUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ReflectionUtil.java similarity index 94% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/ReflectionUtil.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/ReflectionUtil.java index ffee439d1da..811214057d4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ReflectionUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ReflectionUtil.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.util; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; @@ -171,8 +171,7 @@ public static Constructor getDefaultConstructor(final Class clazz) { /** * Constructs a new {@code T} object using the default constructor of its class. Any exceptions thrown by the - * constructor will be rethrown by this method, possibly wrapped in an - * {@link java.lang.reflect.UndeclaredThrowableException}. + * constructor will be rethrown by this method, possibly wrapped in an {@link InternalException}. * * @param clazz the class to use for instantiation. * @param the type of the object to construct. @@ -180,6 +179,7 @@ public static Constructor getDefaultConstructor(final Class clazz) { * @throws IllegalArgumentException if the given class is abstract, an interface, an array class, a primitive type, * or void * @throws IllegalStateException if access is denied to the constructor, or there are no default constructors + * @throws InternalException wrapper of the underlying exception if checked */ public static T instantiate(final Class clazz) { Objects.requireNonNull(clazz, "No class provided"); @@ -193,8 +193,13 @@ public static T instantiate(final Class clazz) { } catch (final IllegalAccessException e) { throw new IllegalStateException(e); } catch (final InvocationTargetException e) { - Throwables.rethrow(e.getCause()); - throw new InternalError("Unreachable"); + if (e.getCause() != null) { + if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException) e.getCause(); + } + throw new InternalException(e.getCause()); + } + throw new InternalException("Error creating new instance of " + clazz.getName(), e); } } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java new file mode 100644 index 00000000000..99a24561ea4 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * This class should be considered internal. + */ +@InternalApi +public final class ServiceLoaderUtil { + + private ServiceLoaderUtil() { + } + + /** + * Retrieves the available services from the caller's classloader. + * + * Broken services will be ignored. + * + * @param The service type. + * @param serviceType The class of the service. + * @param lookup The calling class data. + * @return A stream of service instances. + */ + public static Stream loadServices(final Class serviceType, Lookup lookup) { + return loadServices(serviceType, lookup, false); + } + + /** + * Retrieves the available services from the caller's classloader and possibly + * the thread context classloader. + * + * Broken services will be ignored. + * + * @param The service type. + * @param serviceType The class of the service. + * @param lookup The calling class data. + * @param useTccl If true the thread context classloader will also be used. + * @return A stream of service instances. + */ + public static Stream loadServices(final Class serviceType, Lookup lookup, boolean useTccl) { + return loadServices(serviceType, lookup, useTccl, true); + } + + static Stream loadServices(final Class serviceType, final Lookup lookup, final boolean useTccl, + final boolean verbose) { + final ClassLoader classLoader = lookup.lookupClass().getClassLoader(); + Stream services = loadClassloaderServices(serviceType, lookup, classLoader, verbose); + if (useTccl) { + final ClassLoader contextClassLoader = LoaderUtil.getThreadContextClassLoader(); + if (contextClassLoader != classLoader) { + services = Stream.concat(services, + loadClassloaderServices(serviceType, lookup, contextClassLoader, verbose)); + } + } + final Set> classes = new HashSet<>(); + // only the first occurrence of a class + return services.filter(service -> classes.add(service.getClass())); + } + + static Stream loadClassloaderServices(final Class serviceType, final Lookup lookup, + final ClassLoader classLoader, final boolean verbose) { + return StreamSupport.stream(new ServiceLoaderSpliterator(serviceType, lookup, classLoader, verbose), false); + } + + static Iterable callServiceLoader(Lookup lookup, Class serviceType, ClassLoader classLoader, + boolean verbose) { + try { + // Creates a lambda in the caller's domain that calls `ServiceLoader` + final MethodHandle loadHandle = lookup.findStatic(ServiceLoader.class, "load", + MethodType.methodType(ServiceLoader.class, Class.class, ClassLoader.class)); + final CallSite callSite = LambdaMetafactory.metafactory(lookup, + "run", + MethodType.methodType(PrivilegedAction.class, Class.class, ClassLoader.class), + MethodType.methodType(Object.class), + loadHandle, + MethodType.methodType(ServiceLoader.class)); + final PrivilegedAction> action = (PrivilegedAction>) callSite + .getTarget()// + .bindTo(serviceType) + .bindTo(classLoader) + .invoke(); + final ServiceLoader serviceLoader; + if (System.getSecurityManager() == null) { + serviceLoader = action.run(); + } else { + final MethodHandle privilegedHandle = lookup.findStatic(AccessController.class, "doPrivileged", + MethodType.methodType(Object.class, PrivilegedAction.class)); + serviceLoader = (ServiceLoader) privilegedHandle.invoke(action); + } + return serviceLoader; + } catch (Throwable e) { + if (verbose) { + StatusLogger.getLogger().error("Unable to load services for service {}", serviceType, e); + } + } + return Collections.emptyList(); + } + + private static class ServiceLoaderSpliterator implements Spliterator { + + private final Iterator serviceIterator; + private final Logger logger; + private final String serviceName; + + public ServiceLoaderSpliterator(final Class serviceType, final Lookup lookup, final ClassLoader classLoader, + final boolean verbose) { + this.serviceIterator = callServiceLoader(lookup, serviceType, classLoader, verbose).iterator(); + this.logger = verbose ? StatusLogger.getLogger() : null; + this.serviceName = serviceType.toString(); + } + + @Override + public boolean tryAdvance(Consumer action) { + while (serviceIterator.hasNext()) { + try { + action.accept(serviceIterator.next()); + return true; + } catch (ServiceConfigurationError e) { + if (logger != null) { + logger.warn("Unable to load service class for service {}", serviceName, e); + } + } + } + return false; + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + + @Override + public int characteristics() { + return NONNULL | IMMUTABLE; + } + + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java new file mode 100644 index 00000000000..9f36072c0e1 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceRegistry.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.lang.invoke.MethodHandles.Lookup; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Registry for service instances loaded from {@link ServiceLoader}. This abstracts the differences between using a flat + * classpath, a module path, and OSGi modules. + * + * @since 3.0.0 + */ +@InternalApi +public class ServiceRegistry { + private static final Lazy INSTANCE = Lazy.relaxed(ServiceRegistry::new); + + + /** + * Returns the singleton ServiceRegistry instance. + */ + public static ServiceRegistry getInstance() { + return INSTANCE.get(); + } + + private final Map, List> mainServices = new ConcurrentHashMap<>(); + private final Map, List>> bundleServices = new ConcurrentHashMap<>(); + + private ServiceRegistry() { + } + + /** + * Gets service instances loaded from the calling context and any previously registered bundle services. The {@code loaderCallerContext} parameter is + * provided to ensure the caller can specify which calling function context to + * load services. + * + * @param serviceType service class + * @param lookup MethodHandle lookup created in same module as caller context + * to use for loading services + * @param validator if non-null, used to validate service instances, + * removing invalid instances from the returned list + * @param type of service + * @return loaded service instances + */ + public List getServices(final Class serviceType, final Lookup lookup, final Predicate validator) { + return getServices(serviceType, lookup, validator, true); + } + + /** + * Set 'verbose' to false if the `StatusLogger` is not available yet. + */ + List getServices(final Class serviceType, final Lookup lookup, final Predicate validator, boolean verbose) { + final List services = getMainServices(serviceType, lookup, validator, verbose); + return Stream.concat(services.stream(), bundleServices.values().stream().flatMap(map -> { + final Stream stream = map.getOrDefault(serviceType, List.of()).stream().map(serviceType::cast); + return validator != null ? stream.filter(validator) : stream; + })).distinct().collect(Collectors.toCollection(ArrayList::new)); + } + + List getMainServices(final Class serviceType, final Lookup lookup, final Predicate validator, boolean verbose) { + final List existing = mainServices.get(serviceType); + if (existing != null) { + return Cast.cast(existing); + } + final List services = ServiceLoaderUtil.loadServices(serviceType, lookup, false, verbose) + .filter(validator != null ? validator : unused -> true) + .collect(Collectors.toList()); + final List oldValue = Cast.cast(mainServices.putIfAbsent(serviceType, services)); + return oldValue != null ? oldValue : services; + } + + /** + * Loads and registers services from an OSGi context. + * + * @param serviceType service class + * @param bundleId bundle id to load services from + * @param bundleClassLoader bundle ClassLoader to load services from + * @param type of service + */ + public void loadServicesFromBundle( + final Class serviceType, final long bundleId, final ClassLoader bundleClassLoader) { + final List services = new ArrayList<>(); + try { + final ServiceLoader serviceLoader = ServiceLoader.load(serviceType, bundleClassLoader); + final Iterator iterator = serviceLoader.iterator(); + while (iterator.hasNext()) { + try { + services.add(iterator.next()); + } catch (final ServiceConfigurationError sce) { + final String message = String.format("Unable to load %s service in bundle id %d", + serviceType.getName(), bundleId); + LowLevelLogUtil.logException(message, sce); + } + } + } catch (final ServiceConfigurationError e) { + final String message = String.format("Unable to load any %s services in bundle id %d", + serviceType.getName(), bundleId); + LowLevelLogUtil.logException(message, e); + } + registerBundleServices(serviceType, bundleId, services); + } + + /** + * Registers a list of service instances from an OSGi context. + * + * @param serviceType service class + * @param bundleId bundle id where services are being registered + * @param services list of services to register for this bundle + * @param type of service + */ + public void registerBundleServices(final Class serviceType, final long bundleId, final List services) { + final List currentServices = Cast.cast(bundleServices.computeIfAbsent(bundleId, ignored -> new ConcurrentHashMap<>()) + .computeIfAbsent(serviceType, ignored -> new ArrayList())); + currentServices.addAll(services); + } + + /** + * Unregisters all services instances from an OSGi context. + * + * @param bundleId bundle id where services are being unregistered + */ + public void unregisterBundleServices(final long bundleId) { + bundleServices.remove(bundleId); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java index 6d3eadb571e..1f8bddb1995 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java @@ -67,17 +67,12 @@ public class SortedArrayStringMap implements IndexedStringMap { private static final long serialVersionUID = -5748905872274478116L; private static final int HASHVAL = 31; - private static final TriConsumer PUT_ALL = new TriConsumer() { - @Override - public void accept(final String key, final Object value, final StringMap contextData) { - contextData.putValue(key, value); - } - }; + private static final TriConsumer PUT_ALL = (key, value, contextData) -> contextData.putValue(key, value); /** * An empty array instance to share when the table is not inflated. */ - private static final String[] EMPTY = {}; + private static final String[] EMPTY = Strings.EMPTY_ARRAY; private static final String FROZEN = "Frozen collection cannot be modified"; private transient String[] keys = EMPTY; @@ -96,7 +91,7 @@ public void accept(final String key, final Object value, final StringMap context Method[] methods = ObjectInputStream.class.getMethods(); Method setMethod = null; Method getMethod = null; - for (Method method : methods) { + for (final Method method : methods) { if (method.getName().equals("setObjectInputFilter")) { setMethod = method; } else if (method.getName().equals("getObjectInputFilter")) { @@ -106,16 +101,16 @@ public void accept(final String key, final Object value, final StringMap context Method newMethod = null; try { if (setMethod != null) { - Class clazz = Class.forName("org.apache.logging.log4j.util.internal.DefaultObjectInputFilter"); + final Class clazz = Class.forName("org.apache.logging.log4j.internal.DefaultObjectInputFilter"); methods = clazz.getMethods(); - for (Method method : methods) { + for (final Method method : methods) { if (method.getName().equals("newInstance") && Modifier.isStatic(method.getModifiers())) { newMethod = method; break; } } } - } catch (ClassNotFoundException ex) { + } catch (final ClassNotFoundException ex) { // Ignore the exception } newObjectInputFilter = newMethod; @@ -156,10 +151,19 @@ public SortedArrayStringMap(final ReadOnlyStringMap other) { public SortedArrayStringMap(final Map map) { resize(ceilingNextPowerOfTwo(map.size())); for (final Map.Entry entry : map.entrySet()) { - putValue(entry.getKey(), entry.getValue()); + // The key might not actually be a String. + putValue(Objects.toString(entry.getKey(), null), entry.getValue()); } } + /** + * For unit testing. + * @return The threshold. + */ + public int getThreshold() { + return threshold; + } + private void assertNotFrozen() { if (immutable) { throw new UnsupportedOperationException(FROZEN); @@ -502,6 +506,9 @@ public String toString() { * Save the state of the {@code SortedArrayStringMap} instance to a stream (i.e., * serialize it). * + * @param s The ObjectOutputStream. + * @throws IOException if there is an error serializing the object to the stream. + * * @serialData The capacity of the SortedArrayStringMap (the length of the * bucket array) is emitted (int), followed by the * size (an int, the number of key-value @@ -542,28 +549,29 @@ private static byte[] marshall(final Object obj) throws IOException { return null; } final ByteArrayOutputStream bout = new ByteArrayOutputStream(); - try (ObjectOutputStream oos = new ObjectOutputStream(bout)) { + try (final ObjectOutputStream oos = new ObjectOutputStream(bout)) { oos.writeObject(obj); oos.flush(); return bout.toByteArray(); } } - private static Object unmarshall(final byte[] data, ObjectInputStream inputStream) + @SuppressWarnings("BanSerializableRead") + private static Object unmarshall(final byte[] data, final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { final ByteArrayInputStream bin = new ByteArrayInputStream(data); Collection allowedClasses = null; - ObjectInputStream ois; + final ObjectInputStream ois; if (inputStream instanceof FilteredObjectInputStream) { allowedClasses = ((FilteredObjectInputStream) inputStream).getAllowedClasses(); ois = new FilteredObjectInputStream(bin, allowedClasses); } else { try { - Object obj = getObjectInputFilter.invoke(inputStream); - Object filter = newObjectInputFilter.invoke(null, obj); + final Object obj = getObjectInputFilter.invoke(inputStream); + final Object filter = newObjectInputFilter.invoke(null, obj); ois = new ObjectInputStream(bin); setObjectInputFilter.invoke(ois, filter); - } catch (IllegalAccessException | InvocationTargetException ex) { + } catch (final IllegalAccessException | InvocationTargetException ex) { throw new StreamCorruptedException("Unable to set ObjectInputFilter on stream"); } } @@ -590,6 +598,9 @@ private static int ceilingNextPowerOfTwo(final int x) { /** * Reconstitute the {@code SortedArrayStringMap} instance from a stream (i.e., * deserialize it). + * @param s The ObjectInputStream. + * @throws IOException If there is an error reading the input stream. + * @throws ClassNotFoundException if the class to be instantiated could not be found. */ private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { if (!(s instanceof FilteredObjectInputStream) && setObjectInputFilter == null) { diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java index 3dfcd9bd26b..a9f562cfdad 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java @@ -16,78 +16,27 @@ */ package org.apache.logging.log4j.util; -import java.lang.reflect.Method; -import java.util.Stack; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.function.Predicate; /** - * Consider this class private. Provides various methods to determine the caller class.

Background

- *

- * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more - * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available, - * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]} - * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a - * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation - * depending on the runtime ClassLoader hierarchy). - *

- *

- * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this - * change was back-ported to Java 7 in version 1.7.0_25 which changed the behavior of the call and caused it to be off - * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of - * libraries and frameworks relying on the API which brought much more attention to the intended API removal. - *

- *

- * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing - * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not - * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java. - * It does, however, work just fine in Sun JDK 1.6, OpenJDK 1.6, Oracle/OpenJDK 1.7, and Oracle/OpenJDK 1.8. Other Java - * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to - * examination of every virtual frame of execution. - *

+ * Consider this class private. Determines the caller's class. */ +@InternalApi public final class StackLocator { - private static PrivateSecurityManager SECURITY_MANAGER; - - // Checkstyle Suppress: the lower-case 'u' ticks off CheckStyle... - // CHECKSTYLE:OFF - static final int JDK_7u25_OFFSET; - // CHECKSTYLE:OFF - - private static final Method GET_CALLER_CLASS; - - private static final StackLocator INSTANCE; - - static { - Method getCallerClass; - int java7u25CompensationOffset = 0; - try { - final Class sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection"); - getCallerClass = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class); - Object o = getCallerClass.invoke(null, 0); - getCallerClass.invoke(null, 0); - if (o == null || o != sunReflectionClass) { - getCallerClass = null; - java7u25CompensationOffset = -1; - } else { - o = getCallerClass.invoke(null, 1); - if (o == sunReflectionClass) { - System.out.println("WARNING: Java 1.7.0_25 is in use which has a broken implementation of Reflection.getCallerClass(). " + - " Please consider upgrading to Java 1.7.0_40 or later."); - java7u25CompensationOffset = 1; - } - } - } catch (final Exception | LinkageError e) { - System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance."); - getCallerClass = null; - java7u25CompensationOffset = -1; - } + private final static StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); - GET_CALLER_CLASS = getCallerClass; - JDK_7u25_OFFSET = java7u25CompensationOffset; + private final static StackWalker STACK_WALKER = StackWalker.getInstance(); - INSTANCE = new StackLocator(); - } + private final static StackLocator INSTANCE = new StackLocator(); + /** + * Gets the singleton instance. + * + * @return the singleton instance. + */ public static StackLocator getInstance() { return INSTANCE; } @@ -95,160 +44,82 @@ public static StackLocator getInstance() { private StackLocator() { } - // TODO: return Object.class instead of null (though it will have a null ClassLoader) - // (MS) I believe this would work without any modifications elsewhere, but I could be wrong + @PerformanceSensitive + public Class getCallerClass(final Class sentinelClass, final Predicate> callerPredicate) { + if (sentinelClass == null) { + throw new IllegalArgumentException("sentinelClass cannot be null"); + } + if (callerPredicate == null) { + throw new IllegalArgumentException("callerPredicate cannot be null"); + } + return WALKER.walk(s -> s + .map(StackWalker.StackFrame::getDeclaringClass) + // Skip until the sentinel class is found + .dropWhile(clazz -> !sentinelClass.equals(clazz)) + // Skip until the predicate evaluates to true, also ignoring recurrences of the sentinel + .dropWhile(clazz -> sentinelClass.equals(clazz) || !callerPredicate.test(clazz)) + .findFirst().orElse(null)); + } - // migrated from ReflectiveCallerClassUtility @PerformanceSensitive - public Class getCallerClass(final int depth) { - if (depth < 0) { - throw new IndexOutOfBoundsException(Integer.toString(depth)); - } - // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke - // since Reflection.getCallerClass ignores the call to Method.invoke() - try { - return (Class) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET); - } catch (final Exception e) { - // theoretically this could happen if the caller class were native code - // TODO: return Object.class - return null; - } + public Class getCallerClass(final String fqcn) { + return getCallerClass(fqcn, ""); } - // migrated from Log4jLoggerFactory @PerformanceSensitive public Class getCallerClass(final String fqcn, final String pkg) { - boolean next = false; - Class clazz; - for (int i = 2; null != (clazz = getCallerClass(i)); i++) { - if (fqcn.equals(clazz.getName())) { - next = true; - continue; - } - if (next && clazz.getName().startsWith(pkg)) { - return clazz; - } - } - // TODO: return Object.class - return null; + return WALKER.walk(s -> s + .dropWhile(f -> !f.getClassName().equals(fqcn)) + .dropWhile(f -> f.getClassName().equals(fqcn)) + .dropWhile(f -> !f.getClassName().startsWith(pkg)) + .findFirst()) + .map(StackWalker.StackFrame::getDeclaringClass) + .orElse(null); } - // added for use in LoggerAdapter implementations mainly @PerformanceSensitive public Class getCallerClass(final Class anchor) { - boolean next = false; - Class clazz; - for (int i = 2; null != (clazz = getCallerClass(i)); i++) { - if (anchor.equals(clazz)) { - next = true; - continue; - } - if (next) { - return clazz; - } - } - return Object.class; + return WALKER.walk(s -> s.dropWhile(f -> !f.getDeclaringClass().equals(anchor)). + dropWhile(f -> f.getDeclaringClass().equals(anchor)).findFirst()). + map(StackWalker.StackFrame::getDeclaringClass).orElse(null); } - // migrated from ThrowableProxy + /** + * Gets the Class of the method that called this method at the location up the call stack by the given stack + * frame depth. + * + * @param depth The stack frame count to walk. + * @return A class or null. + * @throws IndexOutOfBoundsException if depth is negative. + */ @PerformanceSensitive - public Stack> getCurrentStackTrace() { - // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int) - if (getSecurityManager() != null) { - final Class[] array = getSecurityManager().getClassContext(); - final Stack> classes = new Stack<>(); - classes.ensureCapacity(array.length); - for (final Class clazz : array) { - classes.push(clazz); - } - return classes; - } - // slower version using getCallerClass where we cannot use a SecurityManager - final Stack> classes = new Stack<>(); - Class clazz; - for (int i = 1; null != (clazz = getCallerClass(i)); i++) { - classes.push(clazz); - } - return classes; - } - - public StackTraceElement calcLocation(final String fqcnOfLogger) { - if (fqcnOfLogger == null) { - return null; - } - // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace(). - final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); - StackTraceElement last = null; - for (int i = stackTrace.length - 1; i > 0; i--) { - final String className = stackTrace[i].getClassName(); - if (fqcnOfLogger.equals(className)) { - return last; - } - last = stackTrace[i]; - } - return null; + public Class getCallerClass(final int depth) { + return WALKER.walk(s -> s.skip(depth).findFirst()).map(StackWalker.StackFrame::getDeclaringClass).orElse(null); } - public StackTraceElement getStackTraceElement(final int depth) { - // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and - // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark. - final StackTraceElement[] elements = new Throwable().getStackTrace(); - int i = 0; - for (final StackTraceElement element : elements) { - if (isValid(element)) { - if (i == depth) { - return element; - } - ++i; - } + @PerformanceSensitive + public Deque> getCurrentStackTrace() { + // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int) + if (PrivateSecurityManagerStackTraceUtil.isEnabled()) { + return PrivateSecurityManagerStackTraceUtil.getCurrentStackTrace(); } - throw new IndexOutOfBoundsException(Integer.toString(depth)); - } + final Deque> stack = new ArrayDeque<>(); + return WALKER.walk(s -> { + s.forEach(f -> stack.add(f.getDeclaringClass())); + return stack; + }); - private boolean isValid(final StackTraceElement element) { - // ignore native methods (oftentimes are repeated frames) - if (element.isNativeMethod()) { - return false; - } - final String cn = element.getClassName(); - // ignore OpenJDK internal classes involved with reflective invocation - if (cn.startsWith("sun.reflect.")) { - return false; - } - final String mn = element.getMethodName(); - // ignore use of reflection including: - // Method.invoke - // InvocationHandler.invoke - // Constructor.newInstance - if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) { - return false; - } - // ignore use of Java 1.9+ reflection classes - if (cn.startsWith("jdk.internal.reflect.")) { - return false; - } - // ignore Class.newInstance - if (cn.equals("java.lang.Class") && mn.equals("newInstance")) { - return false; - } - // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods - if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) { - return false; - } - // any others? - return true; } - protected PrivateSecurityManager getSecurityManager() { - return SECURITY_MANAGER; + public StackTraceElement calcLocation(final String fqcnOfLogger) { + return STACK_WALKER.walk( + s -> s.dropWhile(f -> !f.getClassName().equals(fqcnOfLogger)) // drop the top frames until we reach the logger + .dropWhile(f -> f.getClassName().equals(fqcnOfLogger)) // drop the logger frames + .findFirst()).map(StackWalker.StackFrame::toStackTraceElement).orElse(null); } - private static final class PrivateSecurityManager extends SecurityManager { - - @Override - protected Class[] getClassContext() { - return super.getClassContext(); - } - + public StackTraceElement getStackTraceElement(final int depth) { + return STACK_WALKER.walk(s -> s.skip(depth).findFirst()) + .map(StackWalker.StackFrame::toStackTraceElement).orElse(null); } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java index 56812fe4469..fe549822c95 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java @@ -16,13 +16,19 @@ */ package org.apache.logging.log4j.util; -import java.util.Stack; +import java.util.Deque; +import java.util.NoSuchElementException; +import java.util.function.Predicate; + +import org.apache.logging.log4j.status.StatusLogger; /** * Consider this class private. Provides various methods to determine the caller class.

Background

*/ +@InternalApi public final class StackLocatorUtil { private static StackLocator stackLocator = null; + private static volatile boolean errorLogged; static { stackLocator = StackLocator.getInstance(); @@ -43,18 +49,62 @@ public static Class getCallerClass(final int depth) { public static StackTraceElement getStackTraceElement(final int depth) { return stackLocator.getStackTraceElement(depth + 1); } + + /** + * Equivalent to {@link #getCallerClass(String, String)} with an empty {@code pkg}. + */ // migrated from ClassLoaderContextSelector @PerformanceSensitive public static Class getCallerClass(final String fqcn) { return getCallerClass(fqcn, Strings.EMPTY); } - // migrated from Log4jLoggerFactory + /** + * Search for a calling class. + * + * @param fqcn Root class name whose caller to search for. + * @param pkg Package name prefix that must be matched after the {@code fqcn} has been found. + * @return The caller class that was matched, or null if one could not be located. + */ @PerformanceSensitive public static Class getCallerClass(final String fqcn, final String pkg) { return stackLocator.getCallerClass(fqcn, pkg); } + /** + * Gets the ClassLoader of the class that called this method at the location up the call stack by the given + * stack frame depth. + *

+ * This method returns {@code null} if: + *

+ *
    + *
  • {@code sun.reflect.Reflection.getCallerClass(int)} is not present.
  • + *
  • An exception is caught calling {@code sun.reflect.Reflection.getCallerClass(int)}.
  • + *
  • Some Class implementations may use null to represent the bootstrap class loader.
  • + *
+ * + * @param depth The stack frame count to walk. + * @return A class or null. + * @throws IndexOutOfBoundsException if depth is negative. + */ + @PerformanceSensitive + public static ClassLoader getCallerClassLoader(final int depth) { + final Class callerClass = stackLocator.getCallerClass(depth + 1); + return callerClass != null ? callerClass.getClassLoader() : null; + } + + /** + * Search for a calling class. + * + * @param sentinelClass Sentinel class at which to begin searching + * @param callerPredicate Predicate checked after the sentinelClass is found + * @return the first matching class after sentinelClass is found. + */ + @PerformanceSensitive + public static Class getCallerClass(final Class sentinelClass, final Predicate> callerPredicate) { + return stackLocator.getCallerClass(sentinelClass, callerPredicate); + } + // added for use in LoggerAdapter implementations mainly @PerformanceSensitive public static Class getCallerClass(final Class anchor) { @@ -63,11 +113,19 @@ public static Class getCallerClass(final Class anchor) { // migrated from ThrowableProxy @PerformanceSensitive - public static Stack> getCurrentStackTrace() { + public static Deque> getCurrentStackTrace() { return stackLocator.getCurrentStackTrace(); } public static StackTraceElement calcLocation(final String fqcnOfLogger) { - return stackLocator.calcLocation(fqcnOfLogger); + try { + return stackLocator.calcLocation(fqcnOfLogger); + } catch (final NoSuchElementException ex) { + if (!errorLogged) { + errorLogged = true; + StatusLogger.getLogger().warn("Unable to locate stack trace element for {}", fqcnOfLogger, ex); + } + return null; + } } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java index a17d195b875..d0c931d039b 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.util; +import java.lang.reflect.Array; +import java.util.Collections; import java.util.Map.Entry; import static java.lang.Character.toLowerCase; @@ -23,7 +25,23 @@ /** * Consider this class private. */ +@InternalApi public final class StringBuilders { + + private static final Object timeClass; + + static { + Object obj; + try { + Class clazz = Class.forName("java.sql.Time"); + long current = System.currentTimeMillis(); + obj = clazz.getDeclaredConstructor(Long.TYPE).newInstance(current); + } catch(Exception ex) { + obj = null; + } + timeClass = obj; + } + private StringBuilders() { } @@ -35,7 +53,9 @@ private StringBuilders() { * @return {@code "value"} */ public static StringBuilder appendDqValue(final StringBuilder sb, final Object value) { - return sb.append(Chars.DQUOTE).append(value).append(Chars.DQUOTE); + sb.append(Chars.DQUOTE); + appendValue(sb, value); + return sb.append(Chars.DQUOTE); } /** @@ -58,7 +78,25 @@ public static StringBuilder appendKeyDqValue(final StringBuilder sb, final Entry * @return the specified StringBuilder */ public static StringBuilder appendKeyDqValue(final StringBuilder sb, final String key, final Object value) { - return sb.append(key).append(Chars.EQ).append(Chars.DQUOTE).append(value).append(Chars.DQUOTE); + sb.append(key).append(Chars.EQ); + return appendDqValue(sb, value); + } + + /** + * Appends a {@code key="value"} preceded by a joiner sequence when the given StringBuilder is non-empty. + * + * @param sb a string builder + * @param key a key + * @param value a value + * @param joiner a joining sequence to append when non-empty + * @return the string builder provided + */ + public static StringBuilder appendKeyDqValueWithJoiner( + final StringBuilder sb, final String key, final Object value, final CharSequence joiner) { + if (sb.length() > 0) { + sb.append(joiner); + } + return appendKeyDqValue(sb, key, value); } /** @@ -97,12 +135,21 @@ public static boolean appendSpecificTypes(final StringBuilder stringBuilder, fin stringBuilder.append(((Float) obj).floatValue()); } else if (obj instanceof Byte) { stringBuilder.append(((Byte) obj).byteValue()); + } else if (isTime(obj) || obj instanceof java.time.temporal.Temporal) { + stringBuilder.append(obj); } else { return false; } return true; } + /* + Check to see if obj is an instance of java.sql.time without requiring the java.sql module. + */ + private static boolean isTime(final Object obj) { + return obj.getClass().isInstance(timeClass); + } + /** * Returns true if the specified section of the left CharSequence equals the specified section of the right * CharSequence. @@ -169,77 +216,143 @@ public static void trimToMaxSize(final StringBuilder stringBuilder, final int ma } public static void escapeJson(final StringBuilder toAppendTo, final int start) { - for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change + int escapeCount = 0; + for (int i = start; i < toAppendTo.length(); i++) { + final char c = toAppendTo.charAt(i); + switch (c) { + case '\b': + case '\t': + case '\f': + case '\n': + case '\r': + case '"': + case '\\': + escapeCount++; + break; + default: + if (Character.isISOControl(c)) { + escapeCount += 5; + } + } + } + + final int lastChar = toAppendTo.length() - 1; + toAppendTo.setLength(toAppendTo.length() + escapeCount); + int lastPos = toAppendTo.length() - 1; + + for (int i = lastChar; lastPos > i; i--) { final char c = toAppendTo.charAt(i); switch (c) { case '\b': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'b'); + lastPos = escapeAndDecrement(toAppendTo, lastPos, 'b'); break; case '\t': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 't'); + lastPos = escapeAndDecrement(toAppendTo, lastPos, 't'); break; case '\f': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'f'); + lastPos = escapeAndDecrement(toAppendTo, lastPos, 'f'); break; case '\n': - // Json string newline character must be encoded as literal "\n" - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'n'); + lastPos = escapeAndDecrement(toAppendTo, lastPos, 'n'); break; case '\r': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'r'); + lastPos = escapeAndDecrement(toAppendTo, lastPos, 'r'); break; case '"': case '\\': - // only " and \ need to be escaped; other escapes are optional - toAppendTo.insert(i, '\\'); + lastPos = escapeAndDecrement(toAppendTo, lastPos, c); break; default: if (Character.isISOControl(c)) { - // all iso control characters are in U+00xx - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, "u0000"); - toAppendTo.setCharAt(i + 4, Chars.getUpperCaseHex((c & 0xF0) >> 4)); - toAppendTo.setCharAt(i + 5, Chars.getUpperCaseHex(c & 0xF)); + // all iso control characters are in U+00xx, JSON output format is "\\u00XX" + toAppendTo.setCharAt(lastPos--, Chars.getUpperCaseHex(c & 0xF)); + toAppendTo.setCharAt(lastPos--, Chars.getUpperCaseHex((c & 0xF0) >> 4)); + toAppendTo.setCharAt(lastPos--, '0'); + toAppendTo.setCharAt(lastPos--, '0'); + toAppendTo.setCharAt(lastPos--, 'u'); + toAppendTo.setCharAt(lastPos--, '\\'); + } else { + toAppendTo.setCharAt(lastPos, c); + lastPos--; } } } } + private static int escapeAndDecrement(final StringBuilder toAppendTo, int lastPos, final char c) { + toAppendTo.setCharAt(lastPos--, c); + toAppendTo.setCharAt(lastPos--, '\\'); + return lastPos; + } + public static void escapeXml(final StringBuilder toAppendTo, final int start) { - for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change + int escapeCount = 0; + for (int i = start; i < toAppendTo.length(); i++) { + final char c = toAppendTo.charAt(i); + switch (c) { + case '&': + escapeCount += 4; + break; + case '<': + case '>': + escapeCount += 3; + break; + case '"': + case '\'': + escapeCount += 5; + } + } + + final int lastChar = toAppendTo.length() - 1; + toAppendTo.setLength(toAppendTo.length() + escapeCount); + int lastPos = toAppendTo.length() - 1; + + for (int i = lastChar; lastPos > i; i--) { final char c = toAppendTo.charAt(i); switch (c) { case '&': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "amp;"); + toAppendTo.setCharAt(lastPos--, ';'); + toAppendTo.setCharAt(lastPos--, 'p'); + toAppendTo.setCharAt(lastPos--, 'm'); + toAppendTo.setCharAt(lastPos--, 'a'); + toAppendTo.setCharAt(lastPos--, '&'); break; case '<': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "lt;"); + toAppendTo.setCharAt(lastPos--, ';'); + toAppendTo.setCharAt(lastPos--, 't'); + toAppendTo.setCharAt(lastPos--, 'l'); + toAppendTo.setCharAt(lastPos--, '&'); break; case '>': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "gt;"); + toAppendTo.setCharAt(lastPos--, ';'); + toAppendTo.setCharAt(lastPos--, 't'); + toAppendTo.setCharAt(lastPos--, 'g'); + toAppendTo.setCharAt(lastPos--, '&'); break; case '"': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "quot;"); + toAppendTo.setCharAt(lastPos--, ';'); + toAppendTo.setCharAt(lastPos--, 't'); + toAppendTo.setCharAt(lastPos--, 'o'); + toAppendTo.setCharAt(lastPos--, 'u'); + toAppendTo.setCharAt(lastPos--, 'q'); + toAppendTo.setCharAt(lastPos--, '&'); break; case '\'': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "apos;"); + toAppendTo.setCharAt(lastPos--, ';'); + toAppendTo.setCharAt(lastPos--, 's'); + toAppendTo.setCharAt(lastPos--, 'o'); + toAppendTo.setCharAt(lastPos--, 'p'); + toAppendTo.setCharAt(lastPos--, 'a'); + toAppendTo.setCharAt(lastPos--, '&'); break; + default: + toAppendTo.setCharAt(lastPos--, c); } } } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringMap.java index e4ce7e88201..ef1c28f796b 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringMap.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringMap.java @@ -95,4 +95,4 @@ public interface StringMap extends ReadOnlyStringMap { * @throws UnsupportedOperationException if this collection has been {@linkplain #isFrozen() frozen}. */ void remove(final String key); -} \ No newline at end of file +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java index f6608d61f65..8a6fdfefb08 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java @@ -19,24 +19,34 @@ import java.util.Iterator; import java.util.Locale; import java.util.Objects; +import java.util.Optional; /** * Consider this class private. - * + * * @see Apache Commons Lang */ +@InternalApi public final class Strings { + private static final ThreadLocal tempStr = ThreadLocal.withInitial(StringBuilder::new); + /** * The empty string. */ public static final String EMPTY = ""; - + private static final String COMMA_DELIMITED_RE = "\\s*,\\s*"; + + /** + * The empty string array. + */ + public static final String[] EMPTY_ARRAY = {}; + /** * OS-dependent line separator, defaults to {@code "\n"} if the system property {@code ""line.separator"} cannot be * read. */ - public static final String LINE_SEPARATOR = PropertiesUtil.getProperties().getStringProperty("line.separator", + public static final String LINE_SEPARATOR = SystemPropertiesPropertySource.getSystemProperty("line.separator", "\n"); private Strings() { @@ -45,7 +55,7 @@ private Strings() { /** * Returns a double quoted string. - * + * * @param str a String * @return {@code "str"} */ @@ -54,14 +64,23 @@ public static String dquote(final String str) { } /** - * Checks if a String is blank. A blank string is one that is {@code null}, empty, or when trimmed using - * {@link String#trim()} is empty. + * Checks if a String is blank. A blank string is one that is either + * {@code null}, empty, or all characters are {@link Character#isWhitespace(char)}. * * @param s the String to check, may be {@code null} - * @return {@code true} if the String is {@code null}, empty, or trims to empty. + * @return {@code true} if the String is {@code null}, empty, or all characters are {@link Character#isWhitespace(char)} */ public static boolean isBlank(final String s) { - return s == null || s.trim().isEmpty(); + if (s == null || s.isEmpty()) { + return true; + } + for (int i = 0; i < s.length(); i++) { + final char c = s.charAt(i); + if (!Character.isWhitespace(c)) { + return false; + } + } + return true; } /** @@ -127,9 +146,46 @@ public static boolean isNotEmpty(final CharSequence cs) { return !isEmpty(cs); } + /** + *

Gets the leftmost {@code len} characters of a String.

+ * + *

If {@code len} characters are not available, or the + * String is {@code null}, the String will be returned without + * an exception. An empty String is returned if len is negative.

+ * + *
+     * StringUtils.left(null, *)    = null
+     * StringUtils.left(*, -ve)     = ""
+     * StringUtils.left("", *)      = ""
+     * StringUtils.left("abc", 0)   = ""
+     * StringUtils.left("abc", 2)   = "ab"
+     * StringUtils.left("abc", 4)   = "abc"
+     * 
+ * + *

+ * Copied from Apache Commons Lang org.apache.commons.lang3.StringUtils. + *

+ * + * @param str the String to get the leftmost characters from, may be null + * @param len the length of the required String + * @return the leftmost characters, {@code null} if null String input + */ + public static String left(final String str, final int len) { + if (str == null) { + return null; + } + if (len < 0) { + return EMPTY; + } + if (str.length() <= len) { + return str; + } + return str.substring(0, len); + } + /** * Returns a quoted string. - * + * * @param str a String * @return {@code 'str'} */ @@ -143,10 +199,10 @@ public static String quote(final String str) { * @return a new string * @see String#toLowerCase(Locale) */ - public String toRootUpperCase(final String str) { + public static String toRootUpperCase(final String str) { return str.toUpperCase(Locale.ROOT); } - + /** *

* Removes control characters (char <= 32) from both ends of this String returning {@code null} if the String is @@ -176,6 +232,18 @@ public static String trimToNull(final String str) { return isEmpty(ts) ? null : ts; } + /** + * Removes control characters from both ends of this String returning {@code Optional.empty()} if the String is + * empty ("") after the trim or if it is {@code null}. + * @param str The String to trim. + * @return An Optional containing the String. + * + * @see #trimToNull(String) + */ + public static Optional trimToOptional(final String str) { + return Optional.ofNullable(str).map(String::trim).filter(s -> !s.isEmpty()); + } + /** *

Joins the elements of the provided {@code Iterable} into * a single String containing the provided elements.

@@ -216,7 +284,7 @@ public static String join(final Iterator iterator, final char separator) { } final Object first = iterator.next(); if (!iterator.hasNext()) { - return Objects.toString(first); + return Objects.toString(first, EMPTY); } // two or more elements @@ -236,4 +304,56 @@ public static String join(final Iterator iterator, final char separator) { return buf.toString(); } + /** + * Splits a comma separated list ignoring whitespace surrounding the list item. + * @param string The string to split. + * @return An array of strings. + */ + public static String[] splitList(String string) { + return string != null ? string.split(COMMA_DELIMITED_RE) : new String[0]; + } + + /** + * Concatenates 2 Strings without allocation. + * @param str1 the first string. + * @param str2 the second string. + * @return the concatenated String. + */ + public static String concat(final String str1, final String str2) { + if (isEmpty(str1)) { + return str2; + } else if (isEmpty(str2)) { + return str1; + } + final StringBuilder sb = tempStr.get(); + try { + return sb.append(str1).append(str2).toString(); + } finally { + sb.setLength(0); + } + } + + /** + * Creates a new string repeating given {@code str} {@code count} times. + * @param str input string + * @param count the repetition count + * @return the new string + * @throws IllegalArgumentException if either {@code str} is null or {@code count} is negative + */ + public static String repeat(final String str, final int count) { + Objects.requireNonNull(str, "str"); + if (count < 0) { + throw new IllegalArgumentException("count"); + } + final StringBuilder sb = tempStr.get(); + try { + for (int index = 0; index < count; index++) { + sb.append(str); + } + return sb.toString(); + } finally { + sb.setLength(0); + } + } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Supplier.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Supplier.java index 22d0a7bcf1f..e212d805724 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Supplier.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Supplier.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.util; /** @@ -32,12 +31,5 @@ * * @since 2.4 */ -public interface Supplier { - - /** - * Gets a value. - * - * @return a value - */ - T get(); +public interface Supplier extends java.util.function.Supplier { } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java index af10fb00c44..b3d1c3ccdfd 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java @@ -16,27 +16,63 @@ */ package org.apache.logging.log4j.util; -import java.util.Map; +import java.util.Collection; +import java.util.Objects; +import java.util.Properties; /** - * PropertySource backed by the current system properties. Other than having a higher priority over normal properties, - * this follows the same rules as {@link PropertiesPropertySource}. + * PropertySource backed by the current system properties. Other than having a + * higher priority over normal properties, this follows the same rules as + * {@link PropertiesPropertySource}. * * @since 2.10.0 */ public class SystemPropertiesPropertySource implements PropertySource { + private static final int DEFAULT_PRIORITY = 0; private static final String PREFIX = "log4j2."; + /** + * Used by bootstrap code to get system properties without loading PropertiesUtil. + */ + public static String getSystemProperty(final String key, final String defaultValue) { + try { + return System.getProperty(key, defaultValue); + } catch (SecurityException e) { + // Silently ignore the exception + return defaultValue; + } + } + @Override public int getPriority() { - return 100; + return DEFAULT_PRIORITY; } @Override public void forEach(final BiConsumer action) { - for (final Map.Entry entry : System.getProperties().entrySet()) { - action.accept(((String) entry.getKey()), ((String) entry.getValue())); + final Properties properties; + try { + properties = System.getProperties(); + } catch (final SecurityException e) { + // (1) There is no status logger. + // (2) LowLevelLogUtil also consults system properties ("line.separator") to + // open a BufferedWriter, so this may fail as well. Just having a hard reference + // in this code to LowLevelLogUtil would cause a problem. + // (3) We could log to System.err (nah) or just be quiet as we do now. + return; + } + // Lock properties only long enough to get a thread-safe SAFE snapshot of its + // current keys, an array. + final Object[] keySet; + synchronized (properties) { + keySet = properties.keySet().toArray(); + } + // Then traverse for an unknown amount of time. + // Some keys may now be absent, in which case, the value is null. + for (final Object key : keySet) { + final String keyStr = Objects.toString(key, null); + action.accept(keyStr, properties.getProperty(keyStr)); } } @@ -45,4 +81,27 @@ public CharSequence getNormalForm(final Iterable tokens) return PREFIX + Util.joinAsCamelCase(tokens); } + @Override + public Collection getPropertyNames() { + try { + return System.getProperties().stringPropertyNames(); + } catch (final SecurityException e) { + return PropertySource.super.getPropertyNames(); + } + } + + @Override + public String getProperty(String key) { + try { + return System.getProperty(key); + } catch (final SecurityException e) { + return PropertySource.super.getProperty(key); + } + } + + @Override + public boolean containsProperty(String key) { + return getProperty(key) != null; + } + } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/TimeUnit.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/TimeUnit.java new file mode 100644 index 00000000000..ac2e0004cb9 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/TimeUnit.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; + +enum TimeUnit { + NANOS("ns,nano,nanos,nanosecond,nanoseconds", ChronoUnit.NANOS), + MICROS("us,micro,micros,microsecond,microseconds", ChronoUnit.MICROS), + MILLIS("ms,milli,millis,millsecond,milliseconds", ChronoUnit.MILLIS), + SECONDS("s,second,seconds", ChronoUnit.SECONDS), + MINUTES("m,minute,minutes", ChronoUnit.MINUTES), + HOURS("h,hour,hours", ChronoUnit.HOURS), + DAYS("d,day,days", ChronoUnit.DAYS); + + private final String[] descriptions; + private final ChronoUnit timeUnit; + + TimeUnit(final String descriptions, final ChronoUnit timeUnit) { + this.descriptions = descriptions.split(","); + this.timeUnit = timeUnit; + } + + ChronoUnit getTimeUnit() { + return this.timeUnit; + } + + static Duration getDuration(final String time) { + final String value = time.trim(); + TemporalUnit temporalUnit = ChronoUnit.MILLIS; + long timeVal = 0; + for (final TimeUnit timeUnit : values()) { + for (final String suffix : timeUnit.descriptions) { + if (value.endsWith(suffix)) { + temporalUnit = timeUnit.timeUnit; + timeVal = Long.parseLong(value.substring(0, value.length() - suffix.length())); + } + } + } + return Duration.of(timeVal, temporalUnit); + } +} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/Timer.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Timer.java similarity index 76% rename from log4j-api/src/test/java/org/apache/logging/log4j/Timer.java rename to log4j-api/src/main/java/org/apache/logging/log4j/util/Timer.java index 5dbe83704b2..f3c49692827 100644 --- a/log4j-api/src/test/java/org/apache/logging/log4j/Timer.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Timer.java @@ -14,29 +14,35 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.util; import java.io.Serializable; import java.text.DecimalFormat; -import org.apache.logging.log4j.util.StringBuilderFormattable; -import org.apache.logging.log4j.util.Strings; - /** - * + * Primarily used in unit tests, but can be used to track elapsed time for a request or portion of any other operation + * so long as all the timer methods are called on the same thread in which it was started. Calling start on + * multiple threads will cause the times to be aggregated. */ public class Timer implements Serializable, StringBuilderFormattable { private static final long serialVersionUID = 9175191792439630013L; private final String name; // The timer's name - private String status; // The timer's status - private long startTime; // The start time + public enum Status { + Started, Stopped, Paused + } + private Status status; // The timer's status private long elapsedTime; // The elapsed time private final int iterations; - private static long NANO_PER_SECOND = 1000000000L; - private static long NANO_PER_MINUTE = NANO_PER_SECOND * 60; - private static long NANO_PER_HOUR = NANO_PER_MINUTE * 60; + private static final long NANO_PER_SECOND = 1000000000L; + private static final long NANO_PER_MINUTE = NANO_PER_SECOND * 60; + private static final long NANO_PER_HOUR = NANO_PER_MINUTE * 60; + private final ThreadLocal startTime = new ThreadLocal() { + @Override protected Long initialValue() { + return 0L; + } + }; /** @@ -52,52 +58,62 @@ public Timer(final String name) * Constructor. * * @param name the timer name. + * @param iterations the number of iterations that will take place. */ public Timer(final String name, final int iterations) { this.name = name; - startTime = 0; - status = "Stopped"; + status = Status.Stopped; this.iterations = (iterations > 0) ? iterations : 0; } /** * Start the timer. */ - public void start() + public synchronized void start() { - startTime = System.nanoTime(); + startTime.set(System.nanoTime()); elapsedTime = 0; - status = "Start"; + status = Status.Started; + } + + public synchronized void startOrResume() { + if (status == Status.Stopped) { + start(); + } else { + resume(); + } } /** * Stop the timer. + * @return the String result of the timer completing. */ - public void stop() + public synchronized String stop() { - elapsedTime += System.nanoTime() - startTime; - startTime = 0; - status = "Stop"; + elapsedTime += System.nanoTime() - startTime.get(); + startTime.set(0L); + status = Status.Stopped; + return toString(); } /** * Pause the timer. */ - public void pause() + public synchronized void pause() { - elapsedTime += System.nanoTime() - startTime; - startTime = 0; - status = "Pause"; + elapsedTime += System.nanoTime() - startTime.get(); + startTime.set(0L); + status = Status.Paused; } /** * Resume the timer. */ - public void resume() + public synchronized void resume() { - startTime = System.nanoTime(); - status = "Resume"; + startTime.set(System.nanoTime()); + status = Status.Started; } /** @@ -134,7 +150,7 @@ public long getElapsedNanoTime() * Resume). * @return the string representing the last operation performed. */ - public String getStatus() + public Status getStatus() { return status; } @@ -154,16 +170,13 @@ public String toString() public void formatTo(final StringBuilder buffer) { buffer.append("Timer ").append(name); switch (status) { - case "Start": + case Started: buffer.append(" started"); break; - case "Pause": + case Paused: buffer.append(" paused"); break; - case "Resume": - buffer.append(" resumed"); - break; - case "Stop": + case Stopped: long nanoseconds = elapsedTime; // Get elapsed hours long hours = nanoseconds / NANO_PER_HOUR; @@ -250,11 +263,7 @@ public boolean equals(final Object o) { if (name != null ? !name.equals(timer.name) : timer.name != null) { return false; } - if (status != null ? !status.equals(timer.status) : timer.status != null) { - return false; - } - - return true; + return status != null ? status.equals(timer.status) : timer.status == null; } @Override @@ -262,7 +271,8 @@ public int hashCode() { int result; result = (name != null ? name.hashCode() : 0); result = 29 * result + (status != null ? status.hashCode() : 0); - result = 29 * result + (int) (startTime ^ (startTime >>> 32)); + final long time = startTime.get(); + result = 29 * result + (int) (time ^ (time >>> 32)); result = 29 * result + (int) (elapsedTime ^ (elapsedTime >>> 32)); return result; } diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java index bb59d468c2d..3c5907bb9ff 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.util; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.spi.LoggingSystemProperties; import org.apache.logging.log4j.status.StatusLogger; /** @@ -40,7 +41,7 @@ * have up to 32 boxed primitives in a single logger call. *

*

- * If more slots are required, set system property {@code log4j.unbox.ringbuffer.size} to the desired ring buffer size. + * If more slots are required, set system property {@value LoggingSystemProperties#UNBOX_RING_BUFFER_SIZE} to the desired ring buffer size. * Note that the specified number will be rounded up to the nearest power of 2. *

* @since 2.6 @@ -50,7 +51,7 @@ public class Unbox { private static final Logger LOGGER = StatusLogger.getLogger(); private static final int BITS_PER_INT = 32; private static final int RINGBUFFER_MIN_SIZE = 32; - private static final int RINGBUFFER_SIZE = calculateRingBufferSize("log4j.unbox.ringbuffer.size"); + private static final int RINGBUFFER_SIZE = calculateRingBufferSize(LoggingSystemProperties.UNBOX_RING_BUFFER_SIZE); private static final int MASK = RINGBUFFER_SIZE - 1; /** @@ -120,8 +121,8 @@ public boolean isBoxedPrimitive(final StringBuilder text) { return false; } } - private static ThreadLocal threadLocalState = new ThreadLocal<>(); - private static WebSafeState webSafeState = new WebSafeState(); + private static final ThreadLocal threadLocalState = new ThreadLocal<>(); + private static final WebSafeState webSafeState = new WebSafeState(); private Unbox() { // this is a utility @@ -263,7 +264,7 @@ private static State getState() { } private static StringBuilder getSB() { - return Constants.ENABLE_THREADLOCALS ? getState().getStringBuilder() : webSafeState.getStringBuilder(); + return Constants.isThreadLocalsEnabled() ? getState().getStringBuilder() : webSafeState.getStringBuilder(); } /** For testing. */ diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java index a98f9bcb7c2..f6d67ac8b7f 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java @@ -16,7 +16,6 @@ */ /** - * Internal utility classes for the Log4j 2 API. Note that the use of any classes in this package is not supported. - * There are no guarantees for binary or logical compatibility in this package. + * Utility APIs used elsewhere in Log4j API. */ package org.apache.logging.log4j.util; diff --git a/log4j-api/src/main/resources/META-INF/MANIFEST.MF b/log4j-api/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..e69de29bb2d diff --git a/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource b/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource index 39c959ca65a..eea83d3fafb 100644 --- a/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource +++ b/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource @@ -13,4 +13,4 @@ # See the license for the specific language governing permissions and # limitations under the license. org.apache.logging.log4j.util.EnvironmentPropertySource -org.apache.logging.log4j.util.SystemPropertiesPropertySource \ No newline at end of file +org.apache.logging.log4j.util.SystemPropertiesPropertySource diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java deleted file mode 100644 index f4800fecac8..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java +++ /dev/null @@ -1,1271 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.message.ParameterizedMessageFactory; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.spi.AbstractLogger; -import org.apache.logging.log4j.spi.MessageFactory2Adapter; -import org.apache.logging.log4j.util.MessageSupplier; -import org.apache.logging.log4j.util.Supplier; -import org.junit.Test; - -/** - * - */ -public class AbstractLoggerTest { - - private static final StringBuilder CHAR_SEQ = new StringBuilder("CharSeq"); - - // TODO add proper tests for ReusableMessage - - @SuppressWarnings("ThrowableInstanceNeverThrown") - private static Throwable t = new UnsupportedOperationException("Test"); - - private static Class obj = AbstractLogger.class; - private static String pattern = "{}, {}"; - private static String p1 = "Long Beach"; - - private static String p2 = "California"; - private static Message charSeq = new SimpleMessage(CHAR_SEQ); - private static Message simple = new SimpleMessage("Hello"); - private static Message object = new ObjectMessage(obj); - - private static Message param = new ParameterizedMessage(pattern, p1, p2); - - private static final Marker MARKER = MarkerManager.getMarker("TEST"); - private static final String MARKER_NAME = "TEST"; - - private static final LogEvent[] EVENTS = new LogEvent[] { - new LogEvent(null, simple, null), - new LogEvent(MARKER_NAME, simple, null), - new LogEvent(null, simple, t), - new LogEvent(MARKER_NAME, simple, t), - - new LogEvent(null, object, null), - new LogEvent(MARKER_NAME, object, null), - new LogEvent(null, object, t), - new LogEvent(MARKER_NAME, object, t), - - new LogEvent(null, param, null), - new LogEvent(MARKER_NAME, param, null), - - new LogEvent(null, simple, null), - new LogEvent(null, simple, t), - new LogEvent(MARKER_NAME, simple, null), - new LogEvent(MARKER_NAME, simple, t), - new LogEvent(MARKER_NAME, simple, null), - - new LogEvent(null, charSeq, null), - new LogEvent(null, charSeq, t), - new LogEvent(MARKER_NAME, charSeq, null), - new LogEvent(MARKER_NAME, charSeq, t), - }; - - - @Test - public void testDebug() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.DEBUG); - - logger.setCurrentEvent(EVENTS[0]); - logger.debug("Hello"); - logger.debug((Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.debug(MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.debug("Hello", t); - logger.debug((Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.debug(MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.debug(obj); - logger.setCurrentEvent(EVENTS[5]); - logger.debug(MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.debug(obj, t); - logger.debug((Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.debug(MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.debug(pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.debug(MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.debug(simple); - logger.debug((Marker) null, simple); - logger.debug((Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.debug(simple, t); - logger.debug((Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.debug(MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.debug(MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.debug(MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.debug(CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.debug(CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.debug(MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.debug(MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testError() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.ERROR); - - logger.setCurrentEvent(EVENTS[0]); - logger.error("Hello"); - logger.error((Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.error(MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.error("Hello", t); - logger.error((Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.error(MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.error(obj); - logger.setCurrentEvent(EVENTS[5]); - logger.error(MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.error(obj, t); - logger.error((Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.error(MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.error(pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.error(MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.error(simple); - logger.error((Marker) null, simple); - logger.error((Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.error(simple, t); - logger.error((Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.error(MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.error(MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.error(MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.error(CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.error(CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.error(MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.error(MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testFatal() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.FATAL); - - logger.setCurrentEvent(EVENTS[0]); - logger.fatal("Hello"); - logger.fatal((Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.fatal(MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.fatal("Hello", t); - logger.fatal((Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.fatal(MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.fatal(obj); - logger.setCurrentEvent(EVENTS[5]); - logger.fatal(MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.fatal(obj, t); - logger.fatal((Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.fatal(MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.fatal(pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.fatal(MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.fatal(simple); - logger.fatal((Marker) null, simple); - logger.fatal((Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.fatal(simple, t); - logger.fatal((Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.fatal(MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.fatal(MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.fatal(MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.fatal(CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.fatal(CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.fatal(MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.fatal(MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testInfo() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.INFO); - - logger.setCurrentEvent(EVENTS[0]); - logger.info("Hello"); - logger.info((Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.info(MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.info("Hello", t); - logger.info((Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.info(MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.info(obj); - logger.setCurrentEvent(EVENTS[5]); - logger.info(MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.info(obj, t); - logger.info((Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.info(MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.info(pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.info(MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.info(simple); - logger.info((Marker) null, simple); - logger.info((Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.info(simple, t); - logger.info((Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.info(MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.info(MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.info(MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.info(CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.info(CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.info(MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.info(MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testLogDebug() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.DEBUG); - - logger.setCurrentEvent(EVENTS[0]); - logger.log(Level.DEBUG, "Hello"); - logger.log(Level.DEBUG, (Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.log(Level.DEBUG, MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.log(Level.DEBUG, "Hello", t); - logger.log(Level.DEBUG, (Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.log(Level.DEBUG, MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.log(Level.DEBUG, obj); - logger.setCurrentEvent(EVENTS[5]); - logger.log(Level.DEBUG, MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.log(Level.DEBUG, obj, t); - logger.log(Level.DEBUG, (Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.log(Level.DEBUG, MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.log(Level.DEBUG, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.log(Level.DEBUG, MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.log(Level.DEBUG, simple); - logger.log(Level.DEBUG, (Marker) null, simple); - logger.log(Level.DEBUG, (Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.log(Level.DEBUG, simple, t); - logger.log(Level.DEBUG, (Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.log(Level.DEBUG, MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.log(Level.DEBUG, MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.log(Level.DEBUG, MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.log(Level.DEBUG, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.log(Level.DEBUG, CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.log(Level.DEBUG, MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.log(Level.DEBUG, MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testLogError() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.ERROR); - - logger.setCurrentEvent(EVENTS[0]); - logger.log(Level.ERROR, "Hello"); - logger.log(Level.ERROR, (Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.log(Level.ERROR, MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.log(Level.ERROR, "Hello", t); - logger.log(Level.ERROR, (Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.log(Level.ERROR, MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.log(Level.ERROR, obj); - logger.setCurrentEvent(EVENTS[5]); - logger.log(Level.ERROR, MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.log(Level.ERROR, obj, t); - logger.log(Level.ERROR, (Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.log(Level.ERROR, MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.log(Level.ERROR, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.log(Level.ERROR, MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.log(Level.ERROR, simple); - logger.log(Level.ERROR, (Marker) null, simple); - logger.log(Level.ERROR, (Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.log(Level.ERROR, simple, t); - logger.log(Level.ERROR, (Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.log(Level.ERROR, MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.log(Level.ERROR, MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.log(Level.ERROR, MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.log(Level.ERROR, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.log(Level.ERROR, CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.log(Level.ERROR, MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.log(Level.ERROR, MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testLogFatal() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.FATAL); - - logger.setCurrentEvent(EVENTS[0]); - logger.log(Level.FATAL, "Hello"); - logger.log(Level.FATAL, (Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.log(Level.FATAL, MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.log(Level.FATAL, "Hello", t); - logger.log(Level.FATAL, (Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.log(Level.FATAL, MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.log(Level.FATAL, obj); - logger.setCurrentEvent(EVENTS[5]); - logger.log(Level.FATAL, MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.log(Level.FATAL, obj, t); - logger.log(Level.FATAL, (Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.log(Level.FATAL, MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.log(Level.FATAL, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.log(Level.FATAL, MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.log(Level.FATAL, simple); - logger.log(Level.FATAL, (Marker) null, simple); - logger.log(Level.FATAL, (Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.log(Level.FATAL, simple, t); - logger.log(Level.FATAL, (Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.log(Level.FATAL, MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.log(Level.FATAL, MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.log(Level.FATAL, MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.log(Level.FATAL, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.log(Level.FATAL, CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.log(Level.FATAL, MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.log(Level.FATAL, MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testLogInfo() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.INFO); - - logger.setCurrentEvent(EVENTS[0]); - logger.log(Level.INFO, "Hello"); - logger.log(Level.INFO, (Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.log(Level.INFO, MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.log(Level.INFO, "Hello", t); - logger.log(Level.INFO, (Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.log(Level.INFO, MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.log(Level.INFO, obj); - logger.setCurrentEvent(EVENTS[5]); - logger.log(Level.INFO, MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.log(Level.INFO, obj, t); - logger.log(Level.INFO, (Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.log(Level.INFO, MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.log(Level.INFO, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.log(Level.INFO, MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.log(Level.INFO, simple); - logger.log(Level.INFO, (Marker) null, simple); - logger.log(Level.INFO, (Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.log(Level.INFO, simple, t); - logger.log(Level.INFO, (Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.log(Level.INFO, MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.log(Level.INFO, MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.log(Level.INFO, MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.log(Level.INFO, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.log(Level.INFO, CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.log(Level.INFO, MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.log(Level.INFO, MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testLogTrace() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.TRACE); - - logger.setCurrentEvent(EVENTS[0]); - logger.log(Level.TRACE, "Hello"); - logger.log(Level.TRACE, (Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.log(Level.TRACE, MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.log(Level.TRACE, "Hello", t); - logger.log(Level.TRACE, (Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.log(Level.TRACE, MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.log(Level.TRACE, obj); - logger.setCurrentEvent(EVENTS[5]); - logger.log(Level.TRACE, MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.log(Level.TRACE, obj, t); - logger.log(Level.TRACE, (Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.log(Level.TRACE, MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.log(Level.TRACE, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.log(Level.TRACE, MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.log(Level.TRACE, simple); - logger.log(Level.TRACE, (Marker) null, simple); - logger.log(Level.TRACE, (Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.log(Level.TRACE, simple, t); - logger.log(Level.TRACE, (Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.log(Level.TRACE, MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.log(Level.TRACE, MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.log(Level.TRACE, MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.log(Level.TRACE, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.log(Level.TRACE, CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.log(Level.TRACE, MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.log(Level.TRACE, MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testLogWarn() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.WARN); - - logger.setCurrentEvent(EVENTS[0]); - logger.log(Level.WARN, "Hello"); - logger.log(Level.WARN, (Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.log(Level.WARN, MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.log(Level.WARN, "Hello", t); - logger.log(Level.WARN, (Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.log(Level.WARN, MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.log(Level.WARN, obj); - logger.setCurrentEvent(EVENTS[5]); - logger.log(Level.WARN, MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.log(Level.WARN, obj, t); - logger.log(Level.WARN, (Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.log(Level.WARN, MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.log(Level.WARN, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.log(Level.WARN, MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.log(Level.WARN, simple); - logger.log(Level.WARN, (Marker) null, simple); - logger.log(Level.WARN, (Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.log(Level.WARN, simple, t); - logger.log(Level.WARN, (Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.log(Level.WARN, MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.log(Level.WARN, MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.log(Level.WARN, MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.log(Level.WARN, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.log(Level.WARN, CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.log(Level.WARN, MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.log(Level.WARN, MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testTrace() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.TRACE); - - logger.setCurrentEvent(EVENTS[0]); - logger.trace("Hello"); - logger.trace((Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.trace(MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.trace("Hello", t); - logger.trace((Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.trace(MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.trace(obj); - logger.setCurrentEvent(EVENTS[5]); - logger.trace(MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.trace(obj, t); - logger.trace((Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.trace(MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.trace(pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.trace(MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.trace(simple); - logger.trace((Marker) null, simple); - logger.trace((Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.trace(simple, t); - logger.trace((Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.trace(MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.trace(MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.trace(MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.trace(CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.trace(CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.trace(MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.trace(MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testWarn() { - final CountingLogger logger = new CountingLogger(); - logger.setCurrentLevel(Level.WARN); - - logger.setCurrentEvent(EVENTS[0]); - logger.warn("Hello"); - logger.warn((Marker) null, "Hello"); - logger.setCurrentEvent(EVENTS[1]); - logger.warn(MARKER, "Hello"); - logger.setCurrentEvent(EVENTS[2]); - logger.warn("Hello", t); - logger.warn((Marker) null, "Hello", t); - logger.setCurrentEvent(EVENTS[3]); - logger.warn(MARKER, "Hello", t); - logger.setCurrentEvent(EVENTS[4]); - logger.warn(obj); - logger.setCurrentEvent(EVENTS[5]); - logger.warn(MARKER, obj); - logger.setCurrentEvent(EVENTS[6]); - logger.warn(obj, t); - logger.warn((Marker) null, obj, t); - logger.setCurrentEvent(EVENTS[7]); - logger.warn(MARKER, obj, t); - logger.setCurrentEvent(EVENTS[8]); - logger.warn(pattern, p1, p2); - logger.setCurrentEvent(EVENTS[9]); - logger.warn(MARKER, pattern, p1, p2); - logger.setCurrentEvent(EVENTS[10]); - logger.warn(simple); - logger.warn((Marker) null, simple); - logger.warn((Marker) null, simple, null); - logger.setCurrentEvent(EVENTS[11]); - logger.warn(simple, t); - logger.warn((Marker) null, simple, t); - logger.setCurrentEvent(EVENTS[12]); - logger.warn(MARKER, simple, null); - logger.setCurrentEvent(EVENTS[13]); - logger.warn(MARKER, simple, t); - logger.setCurrentEvent(EVENTS[14]); - logger.warn(MARKER, simple); - - logger.setCurrentEvent(EVENTS[15]); - logger.warn(CHAR_SEQ); - logger.setCurrentEvent(EVENTS[16]); - logger.warn(CHAR_SEQ, t); - logger.setCurrentEvent(EVENTS[17]); - logger.warn(MARKER, CHAR_SEQ); - logger.setCurrentEvent(EVENTS[18]); - logger.warn(MARKER, CHAR_SEQ, t); - - assertEquals("log(CharSeq) invocations", 4, logger.getCharSeqCount()); - assertEquals("log(Object) invocations", 5, logger.getObjectCount()); - } - - @Test - public void testMessageWithThrowable() { - final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(true); - final ThrowableMessage message = new ThrowableMessage(t); - - logger.debug(message); - logger.error(message); - logger.fatal(message); - logger.info(message); - logger.trace(message); - logger.warn(message); - logger.log(Level.INFO, message); - - logger.debug(MARKER, message); - logger.error(MARKER, message); - logger.fatal(MARKER, message); - logger.info(MARKER, message); - logger.trace(MARKER, message); - logger.warn(MARKER, message); - logger.log(Level.INFO, MARKER, message); - } - - @Test - public void testMessageWithoutThrowable() { - final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); - final ThrowableMessage message = new ThrowableMessage(null); - - logger.debug(message); - logger.error(message); - logger.fatal(message); - logger.info(message); - logger.trace(message); - logger.warn(message); - logger.log(Level.INFO, message); - - logger.debug(MARKER, message); - logger.error(MARKER, message); - logger.fatal(MARKER, message); - logger.info(MARKER, message); - logger.trace(MARKER, message); - logger.warn(MARKER, message); - logger.log(Level.INFO, MARKER, message); - } - - @Test - public void testMessageSupplierWithThrowable() { - final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(true); - final ThrowableMessage message = new ThrowableMessage(t); - final MessageSupplier supplier = new MessageSupplier() { - @Override - public Message get() { - return message; - } - }; - - logger.debug(supplier); - logger.error(supplier); - logger.fatal(supplier); - logger.info(supplier); - logger.trace(supplier); - logger.warn(supplier); - logger.log(Level.INFO, supplier); - - logger.debug(MARKER, supplier); - logger.error(MARKER, supplier); - logger.fatal(MARKER, supplier); - logger.info(MARKER, supplier); - logger.trace(MARKER, supplier); - logger.warn(MARKER, supplier); - logger.log(Level.INFO, MARKER, supplier); - } - - @Test - public void testMessageSupplierWithoutThrowable() { - final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); - final ThrowableMessage message = new ThrowableMessage(null); - final MessageSupplier supplier = new MessageSupplier() { - @Override - public Message get() { - return message; - } - }; - - logger.debug(supplier); - logger.error(supplier); - logger.fatal(supplier); - logger.info(supplier); - logger.trace(supplier); - logger.warn(supplier); - logger.log(Level.INFO, supplier); - - logger.debug(MARKER, supplier); - logger.error(MARKER, supplier); - logger.fatal(MARKER, supplier); - logger.info(MARKER, supplier); - logger.trace(MARKER, supplier); - logger.warn(MARKER, supplier); - logger.log(Level.INFO, MARKER, supplier); - } - - @Test - public void testSupplierWithThrowable() { - final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(true); - final ThrowableMessage message = new ThrowableMessage(t); - final Supplier supplier = new Supplier() { - @Override - public Message get() { - return message; - } - }; - - logger.debug(supplier); - logger.error(supplier); - logger.fatal(supplier); - logger.info(supplier); - logger.trace(supplier); - logger.warn(supplier); - logger.log(Level.INFO, supplier); - - logger.debug(MARKER, supplier); - logger.error(MARKER, supplier); - logger.fatal(MARKER, supplier); - logger.info(MARKER, supplier); - logger.trace(MARKER, supplier); - logger.warn(MARKER, supplier); - logger.log(Level.INFO, MARKER, supplier); - } - - @Test - public void testSupplierWithoutThrowable() { - final ThrowableExpectingLogger logger = new ThrowableExpectingLogger(false); - final ThrowableMessage message = new ThrowableMessage(null); - final Supplier supplier = new Supplier() { - @Override - public Message get() { - return message; - } - }; - - logger.debug(supplier); - logger.error(supplier); - logger.fatal(supplier); - logger.info(supplier); - logger.trace(supplier); - logger.warn(supplier); - logger.log(Level.INFO, supplier); - - logger.debug(MARKER, supplier); - logger.error(MARKER, supplier); - logger.fatal(MARKER, supplier); - logger.info(MARKER, supplier); - logger.trace(MARKER, supplier); - logger.warn(MARKER, supplier); - logger.log(Level.INFO, MARKER, supplier); - } - - private static class CountingLogger extends AbstractLogger { - private static final long serialVersionUID = -3171452617952475480L; - - private Level currentLevel; - private LogEvent currentEvent; - private int charSeqCount; - private int objectCount; - - CountingLogger() { - super("CountingLogger", new MessageFactory2Adapter(ParameterizedMessageFactory.INSTANCE)); - } - - void setCurrentLevel(final Level currentLevel) { - this.currentLevel = currentLevel; - } - - void setCurrentEvent(final LogEvent currentEvent) { - this.currentEvent = currentEvent; - } - - int getCharSeqCount() { - return charSeqCount; - } - - int getObjectCount() { - return objectCount; - } - - @Override - public Level getLevel() { - return currentLevel; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) { - assertTrue("Incorrect Level. Expected " + currentLevel + ", actual " + level, level.equals(currentLevel)); - if (marker == null) { - if (currentEvent.markerName != null) { - fail("Incorrect marker. Expected " + currentEvent.markerName + ", actual is null"); - } - } else { - if (currentEvent.markerName == null) { - fail("Incorrect marker. Expected null. Actual is " + marker.getName()); - } else { - assertTrue("Incorrect marker. Expected " + currentEvent.markerName + ", actual " + - marker.getName(), currentEvent.markerName.equals(marker.getName())); - } - } - if (data == null) { - if (currentEvent.data != null) { - fail("Incorrect message. Expected " + currentEvent.data + ", actual is null"); - } - } else { - if (currentEvent.data == null) { - fail("Incorrect message. Expected null. Actual is " + data.getFormattedMessage()); - } else { - assertTrue("Incorrect message type. Expected " + currentEvent.data + ", actual " + data, - data.getClass().isAssignableFrom(currentEvent.data.getClass())); - assertTrue("Incorrect message. Expected " + currentEvent.data.getFormattedMessage() + ", actual " + - data.getFormattedMessage(), - currentEvent.data.getFormattedMessage().equals(data.getFormattedMessage())); - } - } - if (t == null) { - if (currentEvent.t != null) { - fail("Incorrect Throwable. Expected " + currentEvent.t + ", actual is null"); - } - } else { - if (currentEvent.t == null) { - fail("Incorrect Throwable. Expected null. Actual is " + t); - } else { - assertTrue("Incorrect Throwable. Expected " + currentEvent.t + ", actual " + t, - currentEvent.t.equals(t)); - } - } - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final CharSequence data, final Throwable t) { - charSeqCount++; - return isEnabled(level, marker, (Message) new SimpleMessage(data), t); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) { - objectCount++; - return isEnabled(level, marker, new ObjectMessage(data), t); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String data) { - return isEnabled(level, marker, (Message) new SimpleMessage(data), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) { - return isEnabled(level, marker, new ParameterizedMessage(data, p1), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, - final Object p4) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, - final Object p7) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, - final Object p7, final Object p8) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8), null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, - final Object p1, final Object p2, final Object p3, - final Object p4, final Object p5, final Object p6, - final Object p7, final Object p8, final Object p9) { - return isEnabled(level, marker, new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9), - null); - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) { - return isEnabled(level, marker, (Message) new SimpleMessage(data), t); - } - - @Override - public void logMessage(final String fqcn, final Level level, final Marker marker, final Message data, final Throwable t) { - assertTrue("Incorrect Level. Expected " + currentLevel + ", actual " + level, level.equals(currentLevel)); - if (marker == null) { - if (currentEvent.markerName != null) { - fail("Incorrect marker. Expected " + currentEvent.markerName + ", actual is null"); - } - } else { - if (currentEvent.markerName == null) { - fail("Incorrect marker. Expected null. Actual is " + marker.getName()); - } else { - assertTrue("Incorrect marker. Expected " + currentEvent.markerName + ", actual " + - marker.getName(), currentEvent.markerName.equals(marker.getName())); - } - } - if (data == null) { - if (currentEvent.data != null) { - fail("Incorrect message. Expected " + currentEvent.data + ", actual is null"); - } - } else { - if (currentEvent.data == null) { - fail("Incorrect message. Expected null. Actual is " + data.getFormattedMessage()); - } else { - assertTrue("Incorrect message type. Expected " + currentEvent.data + ", actual " + data, - data.getClass().isAssignableFrom(currentEvent.data.getClass())); - assertTrue("Incorrect message. Expected " + currentEvent.data.getFormattedMessage() + ", actual " + - data.getFormattedMessage(), - currentEvent.data.getFormattedMessage().equals(data.getFormattedMessage())); - } - } - if (t == null) { - if (currentEvent.t != null) { - fail("Incorrect Throwable. Expected " + currentEvent.t + ", actual is null"); - } - } else { - if (currentEvent.t == null) { - fail("Incorrect Throwable. Expected null. Actual is " + t); - } else { - assertTrue("Incorrect Throwable. Expected " + currentEvent.t + ", actual " + t, - currentEvent.t.equals(t)); - } - } - } - } - - private static class LogEvent { - String markerName; - Message data; - Throwable t; - - public LogEvent(final String markerName, final Message data, final Throwable t) { - this.markerName = markerName; - this.data = data; - this.t = t; - } - } - - private static class ThrowableExpectingLogger extends AbstractLogger { - private static final long serialVersionUID = -7218195998038685039L; - private final boolean expectingThrowables; - - ThrowableExpectingLogger(final boolean expectingThrowables) { - super("ThrowableExpectingLogger", new MessageFactory2Adapter(ParameterizedMessageFactory.INSTANCE)); - this.expectingThrowables = expectingThrowables; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final CharSequence message, final Throwable t) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) { - return true; - } - - @Override - public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) { - return true; - } - - @Override - public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { - if(expectingThrowables) { - assertNotNull("Expected a Throwable but received null!", t); - } else { - assertNull("Expected null but received a Throwable! "+t, t); - } - } - - @Override - public Level getLevel() { - return Level.INFO; - } - } - - private static class ThrowableMessage implements Message { - /** - * - */ - private static final long serialVersionUID = 1L; - private final Throwable throwable; - - public ThrowableMessage(final Throwable throwable) { - this.throwable = throwable; - } - - @Override - public String getFormattedMessage() { - return null; - } - - @Override - public String getFormat() { - return null; - } - - @Override - public Object[] getParameters() { - return new Object[0]; - } - - @Override - public Throwable getThrowable() { - return throwable; - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/AbstractSerializationTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/AbstractSerializationTest.java deleted file mode 100644 index ea385011b20..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/AbstractSerializationTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import java.io.Serializable; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import static org.apache.logging.log4j.SerializableMatchers.serializesRoundTrip; -import static org.junit.Assert.assertThat; - -/** - * Subclasses tests {@link Serializable} objects. - */ -@RunWith(Parameterized.class) -public abstract class AbstractSerializationTest { - - private final Serializable serializable; - - public AbstractSerializationTest(final Serializable serializable) { - super(); - this.serializable = serializable; - } - - @Test - public void testSerializationRoundtripEquals() { - assertThat(serializable, serializesRoundTrip(serializable)); - } - - @Test - public void testSerializationRoundtripNoException() { - assertThat(serializable, serializesRoundTrip()); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java deleted file mode 100644 index c5e84e09ddf..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/CloseableThreadContextTest.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import org.apache.logging.log4j.junit.ThreadContextRule; -import org.junit.Rule; -import org.junit.Test; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -/** - * Tests {@link CloseableThreadContext}. - * - * @since 2.6 - */ -public class CloseableThreadContextTest { - - private final String key = "key"; - private final String value = "value"; - - @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); - - @Test - public void shouldAddAnEntryToTheMap() throws Exception { - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { - assertThat(ThreadContext.get(key), is(value)); - } - } - - @Test - public void shouldAddTwoEntriesToTheMap() throws Exception { - final String key2 = "key2"; - final String value2 = "value2"; - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value).put(key2, value2)) { - assertThat(ThreadContext.get(key), is(value)); - assertThat(ThreadContext.get(key2), is(value2)); - } - } - - @Test - public void shouldNestEntries() throws Exception { - final String oldValue = "oldValue"; - final String innerValue = "innerValue"; - ThreadContext.put(key, oldValue); - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { - assertThat(ThreadContext.get(key), is(value)); - try (final CloseableThreadContext.Instance ignored2 = CloseableThreadContext.put(key, innerValue)) { - assertThat(ThreadContext.get(key), is(innerValue)); - } - assertThat(ThreadContext.get(key), is(value)); - } - assertThat(ThreadContext.get(key), is(oldValue)); - } - - @Test - public void shouldPreserveOldEntriesFromTheMapWhenAutoClosed() throws Exception { - final String oldValue = "oldValue"; - ThreadContext.put(key, oldValue); - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { - assertThat(ThreadContext.get(key), is(value)); - } - assertThat(ThreadContext.get(key), is(oldValue)); - } - - @Test - public void ifTheSameKeyIsAddedTwiceTheOriginalShouldBeUsed() throws Exception { - final String oldValue = "oldValue"; - final String secondValue = "innerValue"; - ThreadContext.put(key, oldValue); - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value).put(key, secondValue)) { - assertThat(ThreadContext.get(key), is(secondValue)); - } - assertThat(ThreadContext.get(key), is(oldValue)); - } - - @Test - public void shouldPushAndPopAnEntryToTheStack() throws Exception { - final String message = "message"; - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.push(message)) { - assertThat(ThreadContext.peek(), is(message)); - } - assertThat(ThreadContext.peek(), is("")); - } - - @Test - public void shouldPushAndPopTwoEntriesToTheStack() throws Exception { - final String message1 = "message1"; - final String message2 = "message2"; - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.push(message1).push(message2)) { - assertThat(ThreadContext.peek(), is(message2)); - } - assertThat(ThreadContext.peek(), is("")); - } - - @Test - public void shouldPushAndPopAParameterizedEntryToTheStack() throws Exception { - final String parameterizedMessage = "message {}"; - final String parameterizedMessageParameter = "param"; - final String formattedMessage = parameterizedMessage.replace("{}", parameterizedMessageParameter); - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.push(parameterizedMessage, - parameterizedMessageParameter)) { - assertThat(ThreadContext.peek(), is(formattedMessage)); - } - assertThat(ThreadContext.peek(), is("")); - } - - @Test - public void shouldRemoveAnEntryFromTheMapWhenAutoClosed() throws Exception { - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value)) { - assertThat(ThreadContext.get(key), is(value)); - } - assertThat(ThreadContext.containsKey(key), is(false)); - } - - @Test - public void shouldAddEntriesToBothStackAndMap() throws Exception { - final String stackValue = "something"; - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.put(key, value).push(stackValue)) { - assertThat(ThreadContext.get(key), is(value)); - assertThat(ThreadContext.peek(), is(stackValue)); - } - assertThat(ThreadContext.containsKey(key), is(false)); - assertThat(ThreadContext.peek(), is("")); - } - - @Test - public void canReuseCloseableThreadContext() throws Exception { - final String stackValue = "something"; - // Create a ctc and close it - final CloseableThreadContext.Instance ctc = CloseableThreadContext.push(stackValue).put(key, value); - assertThat(ThreadContext.get(key), is(value)); - assertThat(ThreadContext.peek(), is(stackValue)); - ctc.close(); - - assertThat(ThreadContext.containsKey(key), is(false)); - assertThat(ThreadContext.peek(), is("")); - - final String anotherKey = "key2"; - final String anotherValue = "value2"; - final String anotherStackValue = "something else"; - // Use it again - ctc.push(anotherStackValue).put(anotherKey, anotherValue); - assertThat(ThreadContext.get(anotherKey), is(anotherValue)); - assertThat(ThreadContext.peek(), is(anotherStackValue)); - ctc.close(); - - assertThat(ThreadContext.containsKey(anotherKey), is(false)); - assertThat(ThreadContext.peek(), is("")); - } - - @Test - public void closeIsIdempotent() throws Exception { - - final String originalMapValue = "map to keep"; - final String originalStackValue = "stack to keep"; - ThreadContext.put(key, originalMapValue); - ThreadContext.push(originalStackValue); - - final String newMapValue = "temp map value"; - final String newStackValue = "temp stack to keep"; - final CloseableThreadContext.Instance ctc = CloseableThreadContext.push(newStackValue).put(key, newMapValue); - - ctc.close(); - assertThat(ThreadContext.get(key), is(originalMapValue)); - assertThat(ThreadContext.peek(), is(originalStackValue)); - - ctc.close(); - assertThat(ThreadContext.get(key), is(originalMapValue)); - assertThat(ThreadContext.peek(), is(originalStackValue)); - } - - @Test - public void putAllWillPutAllValues() throws Exception { - - final String oldValue = "oldValue"; - ThreadContext.put(key, oldValue); - - final Map valuesToPut = new HashMap<>(); - valuesToPut.put(key, value); - - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.putAll(valuesToPut)) { - assertThat(ThreadContext.get(key), is(value)); - } - assertThat(ThreadContext.get(key), is(oldValue)); - - } - - @Test - public void pushAllWillPushAllValues() throws Exception { - - ThreadContext.push(key); - final List messages = ThreadContext.getImmutableStack().asList(); - ThreadContext.pop(); - - try (final CloseableThreadContext.Instance ignored = CloseableThreadContext.pushAll(messages)) { - assertThat(ThreadContext.peek(), is(key)); - } - assertThat(ThreadContext.peek(), is("")); - - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/LevelTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/LevelTest.java deleted file mode 100644 index 5691e0e54e3..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/LevelTest.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -/** - * - */ -public class LevelTest { - - @Test - public void testDefault() { - final Level level = Level.toLevel("Information", Level.ERROR); - assertNotNull(level); - assertEquals(Level.ERROR, level); - } - - @Test - public void testForNameEquals() { - final String name = "Foo"; - final int intValue = 1; - final Level level = Level.forName(name, intValue); - assertNotNull(level); - assertEquals(level, Level.forName(name, intValue)); - assertEquals(level, Level.getLevel(name)); - assertEquals(intValue, Level.getLevel(name).intLevel()); - } - - @Test - public void testGoodLevels() { - final Level level = Level.toLevel("INFO"); - assertNotNull(level); - assertEquals(Level.INFO, level); - } - - @Test - public void testIsInRangeErrorToDebug() { - assertFalse(Level.OFF.isInRange(Level.ERROR, Level.DEBUG)); - assertFalse(Level.FATAL.isInRange(Level.ERROR, Level.DEBUG)); - assertTrue(Level.ERROR.isInRange(Level.ERROR, Level.DEBUG)); - assertTrue(Level.WARN.isInRange(Level.ERROR, Level.DEBUG)); - assertTrue(Level.INFO.isInRange(Level.ERROR, Level.DEBUG)); - assertTrue(Level.DEBUG.isInRange(Level.ERROR, Level.DEBUG)); - assertFalse(Level.TRACE.isInRange(Level.ERROR, Level.DEBUG)); - assertFalse(Level.ALL.isInRange(Level.ERROR, Level.DEBUG)); - } - - @Test - public void testIsInRangeFatalToTrace() { - assertFalse(Level.OFF.isInRange(Level.FATAL, Level.TRACE)); - assertTrue(Level.FATAL.isInRange(Level.FATAL, Level.TRACE)); - assertTrue(Level.ERROR.isInRange(Level.FATAL, Level.TRACE)); - assertTrue(Level.WARN.isInRange(Level.FATAL, Level.TRACE)); - assertTrue(Level.INFO.isInRange(Level.FATAL, Level.TRACE)); - assertTrue(Level.DEBUG.isInRange(Level.FATAL, Level.TRACE)); - assertTrue(Level.TRACE.isInRange(Level.FATAL, Level.TRACE)); - assertFalse(Level.ALL.isInRange(Level.FATAL, Level.TRACE)); - } - - @Test - public void testIsInRangeOffToAll() { - assertTrue(Level.OFF.isInRange(Level.OFF, Level.ALL)); - assertTrue(Level.FATAL.isInRange(Level.OFF, Level.ALL)); - assertTrue(Level.ERROR.isInRange(Level.OFF, Level.ALL)); - assertTrue(Level.WARN.isInRange(Level.OFF, Level.ALL)); - assertTrue(Level.INFO.isInRange(Level.OFF, Level.ALL)); - assertTrue(Level.DEBUG.isInRange(Level.OFF, Level.ALL)); - assertTrue(Level.TRACE.isInRange(Level.OFF, Level.ALL)); - assertTrue(Level.ALL.isInRange(Level.OFF, Level.ALL)); - } - - @Test - public void testIsInRangeSameLevels() { - // Level.OFF - assertTrue(Level.OFF.isInRange(Level.OFF, Level.OFF)); - assertFalse(Level.OFF.isInRange(Level.FATAL, Level.FATAL)); - assertFalse(Level.OFF.isInRange(Level.ERROR, Level.ERROR)); - assertFalse(Level.OFF.isInRange(Level.WARN, Level.WARN)); - assertFalse(Level.OFF.isInRange(Level.INFO, Level.INFO)); - assertFalse(Level.OFF.isInRange(Level.DEBUG, Level.DEBUG)); - assertFalse(Level.OFF.isInRange(Level.TRACE, Level.TRACE)); - assertFalse(Level.OFF.isInRange(Level.ALL, Level.ALL)); - // Level.FATAL - assertFalse(Level.FATAL.isInRange(Level.OFF, Level.OFF)); - assertTrue(Level.FATAL.isInRange(Level.FATAL, Level.FATAL)); - assertFalse(Level.FATAL.isInRange(Level.ERROR, Level.ERROR)); - assertFalse(Level.FATAL.isInRange(Level.WARN, Level.WARN)); - assertFalse(Level.FATAL.isInRange(Level.INFO, Level.INFO)); - assertFalse(Level.FATAL.isInRange(Level.DEBUG, Level.DEBUG)); - assertFalse(Level.FATAL.isInRange(Level.TRACE, Level.TRACE)); - assertFalse(Level.FATAL.isInRange(Level.ALL, Level.ALL)); - // Level.ERROR - assertFalse(Level.ERROR.isInRange(Level.OFF, Level.OFF)); - assertFalse(Level.ERROR.isInRange(Level.FATAL, Level.FATAL)); - assertTrue(Level.ERROR.isInRange(Level.ERROR, Level.ERROR)); - assertFalse(Level.ERROR.isInRange(Level.WARN, Level.WARN)); - assertFalse(Level.ERROR.isInRange(Level.INFO, Level.INFO)); - assertFalse(Level.ERROR.isInRange(Level.DEBUG, Level.DEBUG)); - assertFalse(Level.ERROR.isInRange(Level.TRACE, Level.TRACE)); - assertFalse(Level.ERROR.isInRange(Level.ALL, Level.ALL)); - // Level.WARN - assertFalse(Level.WARN.isInRange(Level.OFF, Level.OFF)); - assertFalse(Level.WARN.isInRange(Level.FATAL, Level.FATAL)); - assertFalse(Level.WARN.isInRange(Level.ERROR, Level.ERROR)); - assertTrue(Level.WARN.isInRange(Level.WARN, Level.WARN)); - assertFalse(Level.WARN.isInRange(Level.INFO, Level.INFO)); - assertFalse(Level.WARN.isInRange(Level.DEBUG, Level.DEBUG)); - assertFalse(Level.WARN.isInRange(Level.TRACE, Level.TRACE)); - assertFalse(Level.WARN.isInRange(Level.ALL, Level.ALL)); - // Level.INFO - assertFalse(Level.INFO.isInRange(Level.OFF, Level.OFF)); - assertFalse(Level.INFO.isInRange(Level.FATAL, Level.FATAL)); - assertFalse(Level.INFO.isInRange(Level.ERROR, Level.ERROR)); - assertFalse(Level.INFO.isInRange(Level.WARN, Level.WARN)); - assertTrue(Level.INFO.isInRange(Level.INFO, Level.INFO)); - assertFalse(Level.INFO.isInRange(Level.DEBUG, Level.DEBUG)); - assertFalse(Level.INFO.isInRange(Level.TRACE, Level.TRACE)); - assertFalse(Level.INFO.isInRange(Level.ALL, Level.ALL)); - // Level.DEBUG - assertFalse(Level.DEBUG.isInRange(Level.OFF, Level.OFF)); - assertFalse(Level.DEBUG.isInRange(Level.FATAL, Level.FATAL)); - assertFalse(Level.DEBUG.isInRange(Level.ERROR, Level.ERROR)); - assertFalse(Level.DEBUG.isInRange(Level.WARN, Level.WARN)); - assertFalse(Level.DEBUG.isInRange(Level.INFO, Level.INFO)); - assertTrue(Level.DEBUG.isInRange(Level.DEBUG, Level.DEBUG)); - assertFalse(Level.DEBUG.isInRange(Level.TRACE, Level.TRACE)); - assertFalse(Level.DEBUG.isInRange(Level.ALL, Level.ALL)); - // Level.TRACE - assertFalse(Level.TRACE.isInRange(Level.OFF, Level.OFF)); - assertFalse(Level.TRACE.isInRange(Level.FATAL, Level.FATAL)); - assertFalse(Level.TRACE.isInRange(Level.ERROR, Level.ERROR)); - assertFalse(Level.TRACE.isInRange(Level.WARN, Level.WARN)); - assertFalse(Level.TRACE.isInRange(Level.INFO, Level.INFO)); - assertFalse(Level.TRACE.isInRange(Level.DEBUG, Level.DEBUG)); - assertTrue(Level.TRACE.isInRange(Level.TRACE, Level.TRACE)); - assertFalse(Level.TRACE.isInRange(Level.ALL, Level.ALL)); - // Level.ALL - assertFalse(Level.ALL.isInRange(Level.OFF, Level.OFF)); - assertFalse(Level.ALL.isInRange(Level.FATAL, Level.FATAL)); - assertFalse(Level.ALL.isInRange(Level.ERROR, Level.ERROR)); - assertFalse(Level.ALL.isInRange(Level.WARN, Level.WARN)); - assertFalse(Level.ALL.isInRange(Level.INFO, Level.INFO)); - assertFalse(Level.ALL.isInRange(Level.DEBUG, Level.DEBUG)); - assertFalse(Level.ALL.isInRange(Level.TRACE, Level.TRACE)); - assertTrue(Level.ALL.isInRange(Level.ALL, Level.ALL)); - } - - @Test - public void testIsInRangeWarnToInfo() { - assertFalse(Level.OFF.isInRange(Level.WARN, Level.INFO)); - assertFalse(Level.FATAL.isInRange(Level.WARN, Level.INFO)); - assertFalse(Level.ERROR.isInRange(Level.WARN, Level.INFO)); - assertTrue(Level.WARN.isInRange(Level.WARN, Level.INFO)); - assertTrue(Level.INFO.isInRange(Level.WARN, Level.INFO)); - assertFalse(Level.DEBUG.isInRange(Level.WARN, Level.INFO)); - assertFalse(Level.TRACE.isInRange(Level.WARN, Level.INFO)); - assertFalse(Level.ALL.isInRange(Level.WARN, Level.INFO)); - } - - @Test - public void testIsLessSpecificThan() { - // Level.OFF - assertTrue(Level.OFF.isLessSpecificThan(Level.OFF)); - assertFalse(Level.OFF.isLessSpecificThan(Level.FATAL)); - assertFalse(Level.OFF.isLessSpecificThan(Level.ERROR)); - assertFalse(Level.OFF.isLessSpecificThan(Level.WARN)); - assertFalse(Level.OFF.isLessSpecificThan(Level.INFO)); - assertFalse(Level.OFF.isLessSpecificThan(Level.DEBUG)); - assertFalse(Level.OFF.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.OFF.isLessSpecificThan(Level.ALL)); - // Level.FATAL - assertTrue(Level.FATAL.isLessSpecificThan(Level.OFF)); - assertTrue(Level.FATAL.isLessSpecificThan(Level.FATAL)); - assertFalse(Level.FATAL.isLessSpecificThan(Level.ERROR)); - assertFalse(Level.FATAL.isLessSpecificThan(Level.WARN)); - assertFalse(Level.FATAL.isLessSpecificThan(Level.INFO)); - assertFalse(Level.FATAL.isLessSpecificThan(Level.DEBUG)); - assertFalse(Level.FATAL.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.FATAL.isLessSpecificThan(Level.ALL)); - // Level.ERROR - assertTrue(Level.ERROR.isLessSpecificThan(Level.OFF)); - assertTrue(Level.ERROR.isLessSpecificThan(Level.FATAL)); - assertTrue(Level.ERROR.isLessSpecificThan(Level.ERROR)); - assertFalse(Level.ERROR.isLessSpecificThan(Level.WARN)); - assertFalse(Level.ERROR.isLessSpecificThan(Level.INFO)); - assertFalse(Level.ERROR.isLessSpecificThan(Level.DEBUG)); - assertFalse(Level.ERROR.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.ERROR.isLessSpecificThan(Level.ALL)); - // Level.ERROR - assertTrue(Level.WARN.isLessSpecificThan(Level.OFF)); - assertTrue(Level.WARN.isLessSpecificThan(Level.FATAL)); - assertTrue(Level.WARN.isLessSpecificThan(Level.ERROR)); - assertTrue(Level.WARN.isLessSpecificThan(Level.WARN)); - assertFalse(Level.WARN.isLessSpecificThan(Level.INFO)); - assertFalse(Level.WARN.isLessSpecificThan(Level.DEBUG)); - assertFalse(Level.WARN.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.WARN.isLessSpecificThan(Level.ALL)); - // Level.WARN - assertTrue(Level.WARN.isLessSpecificThan(Level.OFF)); - assertTrue(Level.WARN.isLessSpecificThan(Level.FATAL)); - assertTrue(Level.WARN.isLessSpecificThan(Level.ERROR)); - assertTrue(Level.WARN.isLessSpecificThan(Level.WARN)); - assertFalse(Level.WARN.isLessSpecificThan(Level.INFO)); - assertFalse(Level.WARN.isLessSpecificThan(Level.DEBUG)); - assertFalse(Level.WARN.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.WARN.isLessSpecificThan(Level.ALL)); - // Level.INFO - assertTrue(Level.INFO.isLessSpecificThan(Level.OFF)); - assertTrue(Level.INFO.isLessSpecificThan(Level.FATAL)); - assertTrue(Level.INFO.isLessSpecificThan(Level.ERROR)); - assertTrue(Level.INFO.isLessSpecificThan(Level.WARN)); - assertTrue(Level.INFO.isLessSpecificThan(Level.INFO)); - assertFalse(Level.INFO.isLessSpecificThan(Level.DEBUG)); - assertFalse(Level.INFO.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.INFO.isLessSpecificThan(Level.ALL)); - // Level.DEBUG - assertTrue(Level.DEBUG.isLessSpecificThan(Level.OFF)); - assertTrue(Level.DEBUG.isLessSpecificThan(Level.FATAL)); - assertTrue(Level.DEBUG.isLessSpecificThan(Level.ERROR)); - assertTrue(Level.DEBUG.isLessSpecificThan(Level.WARN)); - assertTrue(Level.DEBUG.isLessSpecificThan(Level.INFO)); - assertTrue(Level.DEBUG.isLessSpecificThan(Level.DEBUG)); - assertFalse(Level.DEBUG.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.DEBUG.isLessSpecificThan(Level.ALL)); - // Level.TRACE - assertTrue(Level.TRACE.isLessSpecificThan(Level.OFF)); - assertTrue(Level.TRACE.isLessSpecificThan(Level.FATAL)); - assertTrue(Level.TRACE.isLessSpecificThan(Level.ERROR)); - assertTrue(Level.TRACE.isLessSpecificThan(Level.WARN)); - assertTrue(Level.TRACE.isLessSpecificThan(Level.INFO)); - assertTrue(Level.TRACE.isLessSpecificThan(Level.DEBUG)); - assertTrue(Level.TRACE.isLessSpecificThan(Level.TRACE)); - assertFalse(Level.TRACE.isLessSpecificThan(Level.ALL)); - // Level.ALL - assertTrue(Level.ALL.isLessSpecificThan(Level.OFF)); - assertTrue(Level.ALL.isLessSpecificThan(Level.FATAL)); - assertTrue(Level.ALL.isLessSpecificThan(Level.ERROR)); - assertTrue(Level.ALL.isLessSpecificThan(Level.WARN)); - assertTrue(Level.ALL.isLessSpecificThan(Level.INFO)); - assertTrue(Level.ALL.isLessSpecificThan(Level.DEBUG)); - assertTrue(Level.ALL.isLessSpecificThan(Level.TRACE)); - assertTrue(Level.ALL.isLessSpecificThan(Level.ALL)); - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/LogManagerTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/LogManagerTest.java deleted file mode 100644 index a3164d1583d..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/LogManagerTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.Closeable; -import java.io.IOException; - -import org.apache.logging.log4j.message.ParameterizedMessageFactory; -import org.apache.logging.log4j.spi.LoggerContext; -import org.junit.Assert; -import org.junit.Test; - -/** - * - */ -public class LogManagerTest { - - class Inner { - final Logger LOGGER = LogManager.getLogger(); - } - - class InnerByClass { - final Logger LOGGER = LogManager.getLogger(InnerByClass.class); - } - - static class StaticInner { - final static Logger LOGGER = LogManager.getLogger(); - } - - static class StaticInnerByClass { - final static Logger LOGGER = LogManager.getLogger(StaticInnerByClass.class); - } - - @Test - public void testGetLogger() { - Logger logger = LogManager.getLogger(); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - logger = LogManager.getLogger(ParameterizedMessageFactory.INSTANCE); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - logger = LogManager.getLogger((Class) null); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - logger = LogManager.getLogger((Class) null, ParameterizedMessageFactory.INSTANCE); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - logger = LogManager.getLogger((String) null); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - logger = LogManager.getLogger((String) null, ParameterizedMessageFactory.INSTANCE); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - logger = LogManager.getLogger((Object) null); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - logger = LogManager.getLogger((Object) null, ParameterizedMessageFactory.INSTANCE); - assertNotNull("No Logger returned", logger); - assertTrue("Incorrect Logger name: " + logger.getName(),LogManagerTest.class.getName().equals(logger.getName())); - } - - @Test - public void testGetLoggerForAnonymousInnerClass1() throws IOException { - final Closeable closeable = new Closeable() { - - Logger LOGGER = LogManager.getLogger(); - - @Override - public void close() throws IOException { - Assert.assertEquals("org.apache.logging.log4j.LogManagerTest$1", LOGGER.getName()); - } - }; - closeable.close(); - } - - @Test - public void testGetLoggerForAnonymousInnerClass2() throws IOException { - final Closeable closeable = new Closeable() { - - Logger LOGGER = LogManager.getLogger(getClass()); - - @Override - public void close() throws IOException { - Assert.assertEquals("org.apache.logging.log4j.LogManagerTest$2", LOGGER.getName()); - } - }; - closeable.close(); - } - - @Test - public void testGetLoggerForInner() { - Assert.assertEquals("org.apache.logging.log4j.LogManagerTest.Inner", new Inner().LOGGER.getName()); - } - - @Test - public void testGetLoggerForInnerByClass() { - Assert.assertEquals("org.apache.logging.log4j.LogManagerTest.InnerByClass", new InnerByClass().LOGGER.getName()); - } - - @Test - public void testGetLoggerForStaticInner() { - Assert.assertEquals("org.apache.logging.log4j.LogManagerTest.StaticInner", StaticInner.LOGGER.getName()); - } - - @Test - public void testGetLoggerForStaticInnerByClass() { - Assert.assertEquals("org.apache.logging.log4j.LogManagerTest.StaticInnerByClass", StaticInnerByClass.LOGGER.getName()); - } - - @Test - public void testShutdown() { - final LoggerContext loggerContext = LogManager.getContext(false); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/LoggerSupplierTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/LoggerSupplierTest.java deleted file mode 100644 index 7b78fe16f0c..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/LoggerSupplierTest.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.util.List; -import java.util.Locale; - -import org.apache.logging.log4j.message.FormattedMessage; -import org.apache.logging.log4j.message.JsonMessage; -import org.apache.logging.log4j.message.LocalizedMessage; -import org.apache.logging.log4j.message.MessageFormatMessage; -import org.apache.logging.log4j.message.ObjectArrayMessage; -import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.message.StringFormattedMessage; -import org.apache.logging.log4j.message.ThreadDumpMessage; -import org.apache.logging.log4j.util.Supplier; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** - * Tests Logger APIs with {@link Supplier}. - */ -public class LoggerSupplierTest { - - private final TestLogger logger = (TestLogger) LogManager.getLogger("LoggerTest"); - - private final List results = logger.getEntries(); - - Locale defaultLocale; - - @Test - public void flowTracing_SupplierOfFormattedMessage() { - logger.traceEntry(new Supplier() { - @Override - public FormattedMessage get() { - return new FormattedMessage("int foo={}", 1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(int foo=1234567890)")); - assertThat("Bad toString()", results.get(0), not(containsString("FormattedMessage"))); - } - - @Test - public void flowTracing_SupplierOfJsonMessage() { - logger.traceEntry(new Supplier() { - @Override - public JsonMessage get() { - return new JsonMessage(System.getProperties()); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("\"java.runtime.name\":")); - assertThat("Bad toString()", results.get(0), not(containsString("JsonMessage"))); - } - - @Test - public void flowTracing_SupplierOfLocalizedMessage() { - logger.traceEntry(new Supplier() { - @Override - public LocalizedMessage get() { - return new LocalizedMessage("int foo={}", 1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(int foo=1234567890)")); - assertThat("Bad toString()", results.get(0), not(containsString("LocalizedMessage"))); - } - - @Test - public void flowTracing_SupplierOfLong() { - logger.traceEntry(new Supplier() { - @Override - public Long get() { - return Long.valueOf(1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(1234567890)")); - assertThat("Bad toString()", results.get(0), not(containsString("SimpleMessage"))); - } - - @Test - public void flowTracing_SupplierOfMessageFormatMessage() { - logger.traceEntry(new Supplier() { - @Override - public MessageFormatMessage get() { - return new MessageFormatMessage("int foo={0}", 1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(int foo=1,234,567,890)")); - assertThat("Bad toString()", results.get(0), not(containsString("MessageFormatMessage"))); - } - - @Test - public void flowTracing_SupplierOfObjectArrayMessage() { - logger.traceEntry(new Supplier() { - @Override - public ObjectArrayMessage get() { - return new ObjectArrayMessage(1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing Enter data", results.get(0), containsString("([1234567890])")); - assertThat("Bad toString()", results.get(0), not(containsString("ObjectArrayMessage"))); - } - - @Test - public void flowTracing_SupplierOfObjectMessage() { - logger.traceEntry(new Supplier() { - @Override - public ObjectMessage get() { - return new ObjectMessage(1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(1234567890)")); - assertThat("Bad toString()", results.get(0), not(containsString("ObjectMessage"))); - } - - @Test - public void flowTracing_SupplierOfParameterizedMessage() { - logger.traceEntry(new Supplier() { - @Override - public ParameterizedMessage get() { - return new ParameterizedMessage("int foo={}", 1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(int foo=1234567890)")); - assertThat("Bad toString()", results.get(0), not(containsString("ParameterizedMessage"))); - } - - @Test - public void flowTracing_SupplierOfSimpleMessage() { - logger.traceEntry(new Supplier() { - @Override - public SimpleMessage get() { - return new SimpleMessage("1234567890"); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(1234567890)")); - assertThat("Bad toString()", results.get(0), not(containsString("SimpleMessage"))); - } - - @Test - public void flowTracing_SupplierOfString() { - logger.traceEntry(new Supplier() { - @Override - public String get() { - return "1234567890"; - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(1234567890)")); - assertThat("Bad toString()", results.get(0), not(containsString("SimpleMessage"))); - } - - @Test - public void flowTracing_SupplierOfStringFormattedMessage() { - logger.traceEntry(new Supplier() { - @Override - public StringFormattedMessage get() { - return new StringFormattedMessage("int foo=%,d", 1234567890); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("(int foo=1,234,567,890)")); - assertThat("Bad toString()", results.get(0), not(containsString("StringFormattedMessage"))); - } - - @Test - public void flowTracing_SupplierOfThreadDumpMessage() { - logger.traceEntry(new Supplier() { - @Override - public ThreadDumpMessage get() { - return new ThreadDumpMessage("Title of ..."); - } - }); - assertEquals(1, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("RUNNABLE")); - assertThat("Missing entry data", results.get(0), containsString("Title of ...")); - assertThat("Missing entry data", results.get(0), containsString(getClass().getName())); - } - - @Before - public void setup() { - results.clear(); - defaultLocale = Locale.getDefault(Locale.Category.FORMAT); - Locale.setDefault(Locale.Category.FORMAT, java.util.Locale.US); - } - - @After - public void tearDown() { - Locale.setDefault(Locale.Category.FORMAT, defaultLocale); - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java deleted file mode 100644 index 2321c64b0f2..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java +++ /dev/null @@ -1,619 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import java.util.Date; -import java.util.List; -import java.util.Locale; - -import org.apache.logging.log4j.message.EntryMessage; -import org.apache.logging.log4j.message.JsonMessage; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.message.ParameterizedMessageFactory; -import org.apache.logging.log4j.message.SimpleMessageFactory; -import org.apache.logging.log4j.message.StringFormatterMessageFactory; -import org.apache.logging.log4j.message.StructuredDataMessage; -import org.apache.logging.log4j.spi.MessageFactory2Adapter; -import org.apache.logging.log4j.util.Strings; -import org.apache.logging.log4j.util.Supplier; -import org.junit.Before; -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.endsWith; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.junit.Assert.*; -/** - * - */ -public class LoggerTest { - - private static class TestParameterizedMessageFactory { - // empty - } - - private static class TestStringFormatterMessageFactory { - // empty - } - - private final TestLogger logger = (TestLogger) LogManager.getLogger("LoggerTest"); - private final Marker marker = MarkerManager.getMarker("test"); - private final List results = logger.getEntries(); - - @Test - public void basicFlow() { - logger.entry(); - logger.exit(); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), equalTo("ENTER[ FLOW ] TRACE Enter")); - assertThat("incorrect Exit", results.get(1), equalTo("EXIT[ FLOW ] TRACE Exit")); - - } - - @Test - public void flowTracingMessage() { - logger.traceEntry(new JsonMessage(System.getProperties())); - final Response response = new Response(-1, "Generic error"); - logger.traceExit(new JsonMessage(response), response); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("\"java.runtime.name\":")); - assertThat("incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - assertThat("Missing exit data", results.get(1), containsString("\"message\":\"Generic error\"")); - } - - @Test - public void flowTracingString_ObjectArray1() { - logger.traceEntry("doFoo(a={}, b={})", 1, 2); - logger.traceExit("doFoo(a=1, b=2): {}", 3); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("doFoo(a=1, b=2)")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - assertThat("Missing exit data", results.get(1), containsString("doFoo(a=1, b=2): 3")); - } - - @Test - public void flowTracingExitValueOnly() { - logger.traceEntry("doFoo(a={}, b={})", 1, 2); - logger.traceExit(3); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("doFoo(a=1, b=2)")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - assertThat("Missing exit data", results.get(1), containsString("3")); - } - - @Test - public void flowTracingString_ObjectArray2() { - final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", 1, 2); - logger.traceExit(msg, 3); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("doFoo(a=1, b=2)")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - assertThat("Missing exit data", results.get(1), containsString("doFoo(a=1, b=2): 3")); - } - - @Test - public void flowTracingVoidReturn() { - final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", 1, 2); - logger.traceExit(msg); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("doFoo(a=1, b=2)")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - assertThat("Missing exit data", results.get(1), endsWith("doFoo(a=1, b=2)")); - } - - @Test - public void flowTracingNoExitArgs() { - logger.traceEntry(); - logger.traceExit(); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - } - - @Test - public void flowTracingNoArgs() { - final EntryMessage message = logger.traceEntry(); - logger.traceExit(message); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - } - - @Test - public void flowTracingString_SupplierOfObjectMessages() { - final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", new Supplier() { - @Override - public Message get() { - return new ObjectMessage(1); - } - }, new Supplier() { - @Override - public Message get() { - return new ObjectMessage(2); - } - }); - logger.traceExit(msg, 3); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("doFoo(a=1, b=2)")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - assertThat("Missing exit data", results.get(1), containsString("doFoo(a=1, b=2): 3")); - } - - @Test - public void flowTracingString_SupplierOfStrings() { - final EntryMessage msg = logger.traceEntry("doFoo(a={}, b={})", new Supplier() { - @Override - public String get() { - return "1"; - } - }, new Supplier() { - @Override - public String get() { - return "2"; - } - }); - logger.traceExit(msg, 3); - assertEquals(2, results.size()); - assertThat("Incorrect Entry", results.get(0), startsWith("ENTER[ FLOW ] TRACE Enter")); - assertThat("Missing entry data", results.get(0), containsString("doFoo(a=1, b=2)")); - assertThat("Incorrect Exit", results.get(1), startsWith("EXIT[ FLOW ] TRACE Exit")); - assertThat("Missing exit data", results.get(1), containsString("doFoo(a=1, b=2): 3")); - } - - @Test - public void catching() { - try { - throw new NullPointerException(); - } catch (final Exception e) { - logger.catching(e); - assertEquals(1, results.size()); - assertThat("Incorrect Catching", - results.get(0), startsWith("CATCHING[ EXCEPTION ] ERROR Catching java.lang.NullPointerException")); - } - } - - @Test - public void debug() { - logger.debug("Debug message"); - assertEquals(1, results.size()); - assertTrue("Incorrect message", results.get(0).startsWith(" DEBUG Debug message")); - } - - @Test - public void debugObject() { - logger.debug(new Date()); - assertEquals(1, results.size()); - assertTrue("Invalid length", results.get(0).length() > 7); - } - - @Test - public void debugWithParms() { - logger.debug("Hello, {}", "World"); - assertEquals(1, results.size()); - assertTrue("Incorrect substitution", results.get(0).startsWith(" DEBUG Hello, World")); - } - - @Test - public void debugWithParmsAndThrowable() { - logger.debug("Hello, {}", "World", new RuntimeException("Test Exception")); - assertEquals(1, results.size()); - assertTrue("Unexpected results: " + results.get(0), - results.get(0).startsWith(" DEBUG Hello, World java.lang.RuntimeException: Test Exception")); - } - - @Test - public void getFormatterLogger() { - // The TestLogger logger was already created in an instance variable for this class. - // The message factory is only used when the logger is created. - final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger(); - final TestLogger altLogger = (TestLogger) LogManager.getFormatterLogger(getClass()); - assertEquals(testLogger.getName(), altLogger.getName()); - assertNotNull(testLogger); - assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); - assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - @Test - public void getFormatterLogger_Class() { - // The TestLogger logger was already created in an instance variable for this class. - // The message factory is only used when the logger is created. - final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger(TestStringFormatterMessageFactory.class); - assertNotNull(testLogger); - assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); - assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - private static void assertMessageFactoryInstanceOf(MessageFactory factory, final Class cls) { - if (factory instanceof MessageFactory2Adapter) { - factory = ((MessageFactory2Adapter) factory).getOriginal(); - } - assertTrue(factory.getClass().isAssignableFrom(cls)); - } - - @Test - public void getFormatterLogger_Object() { - // The TestLogger logger was already created in an instance variable for this class. - // The message factory is only used when the logger is created. - final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger(new TestStringFormatterMessageFactory()); - assertNotNull(testLogger); - assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); - assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - @Test - public void getFormatterLogger_String() { - final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getFormatterLogger("getLogger_String_StringFormatterMessageFactory"); - assertNotNull(testLogger); - assertMessageFactoryInstanceOf(testLogger.getMessageFactory(), StringFormatterMessageFactory.class); - assertEqualMessageFactory(messageFactory, testLogger); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - @Test - public void getLogger_Class_ParameterizedMessageFactory() { - // The TestLogger logger was already created in an instance variable for this class. - // The message factory is only used when the logger is created. - final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getLogger(TestParameterizedMessageFactory.class, - messageFactory); - assertNotNull(testLogger); - assertEqualMessageFactory(messageFactory, testLogger); - testLogger.debug("{}", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(" DEBUG " + Integer.MAX_VALUE, testLogger.getEntries().get(0)); - } - - @Test - public void getLogger_Class_StringFormatterMessageFactory() { - // The TestLogger logger was already created in an instance variable for this class. - // The message factory is only used when the logger is created. - final TestLogger testLogger = (TestLogger) LogManager.getLogger(TestStringFormatterMessageFactory.class, - StringFormatterMessageFactory.INSTANCE); - assertNotNull(testLogger); - assertEqualMessageFactory(StringFormatterMessageFactory.INSTANCE, testLogger); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - @Test - public void getLogger_Object_ParameterizedMessageFactory() { - // The TestLogger logger was already created in an instance variable for this class. - // The message factory is only used when the logger is created. - final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getLogger(new TestParameterizedMessageFactory(), - messageFactory); - assertNotNull(testLogger); - assertEqualMessageFactory(messageFactory, testLogger); - testLogger.debug("{}", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(" DEBUG " + Integer.MAX_VALUE, testLogger.getEntries().get(0)); - } - - private void assertEqualMessageFactory(final MessageFactory messageFactory, final TestLogger testLogger) { - MessageFactory actual = testLogger.getMessageFactory(); - if (actual instanceof MessageFactory2Adapter) { - actual = ((MessageFactory2Adapter) actual).getOriginal(); - } - assertEquals(messageFactory, actual); - } - - @Test - public void getLogger_Object_StringFormatterMessageFactory() { - // The TestLogger logger was already created in an instance variable for this class. - // The message factory is only used when the logger is created. - final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getLogger(new TestStringFormatterMessageFactory(), - messageFactory); - assertNotNull(testLogger); - assertEqualMessageFactory(messageFactory, testLogger); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - @Test - public void getLogger_String_MessageFactoryMismatch() { - final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_MessageFactoryMismatch", - messageFactory); - assertNotNull(testLogger); - assertEqualMessageFactory(messageFactory, testLogger); - final TestLogger testLogger2 = (TestLogger) LogManager.getLogger("getLogger_String_MessageFactoryMismatch", - ParameterizedMessageFactory.INSTANCE); - assertNotNull(testLogger2); - //TODO: How to test? - //This test context always creates new loggers, other test context impls I tried fail other tests. - //assertEquals(messageFactory, testLogger2.getMessageFactory()); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - @Test - public void getLogger_String_ParameterizedMessageFactory() { - final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_ParameterizedMessageFactory", - messageFactory); - assertNotNull(testLogger); - assertEqualMessageFactory(messageFactory, testLogger); - testLogger.debug("{}", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(" DEBUG " + Integer.MAX_VALUE, testLogger.getEntries().get(0)); - } - - @Test - public void getLogger_String_SimpleMessageFactory() { - final SimpleMessageFactory messageFactory = SimpleMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_StringFormatterMessageFactory", - messageFactory); - assertNotNull(testLogger); - assertEqualMessageFactory(messageFactory, testLogger); - testLogger.debug("{} %,d {foo}", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(" DEBUG {} %,d {foo}", testLogger.getEntries().get(0)); - } - - @Test - public void getLogger_String_StringFormatterMessageFactory() { - final StringFormatterMessageFactory messageFactory = StringFormatterMessageFactory.INSTANCE; - final TestLogger testLogger = (TestLogger) LogManager.getLogger("getLogger_String_StringFormatterMessageFactory", - messageFactory); - assertNotNull(testLogger); - assertEqualMessageFactory(messageFactory, testLogger); - testLogger.debug("%,d", Integer.MAX_VALUE); - assertEquals(1, testLogger.getEntries().size()); - assertEquals(String.format(" DEBUG %,d", Integer.MAX_VALUE), testLogger.getEntries().get(0)); - } - - @Test - public void getLoggerByClass() { - final Logger classLogger = LogManager.getLogger(LoggerTest.class); - assertNotNull(classLogger); - } - - @Test - public void getLoggerByNullClass() { - // Returns a SimpleLogger - assertNotNull(LogManager.getLogger((Class) null)); - } - - @Test - public void getLoggerByNullObject() { - // Returns a SimpleLogger - assertNotNull(LogManager.getLogger((Object) null)); - } - - @Test - public void getLoggerByNullString() { - // Returns a SimpleLogger - assertNotNull(LogManager.getLogger((String) null)); - } - - @Test - public void getLoggerByObject() { - final Logger classLogger = LogManager.getLogger(this); - assertNotNull(classLogger); - assertEquals(classLogger, LogManager.getLogger(LoggerTest.class)); - } - - @Test - public void getRootLogger() { - assertNotNull(LogManager.getRootLogger()); - assertNotNull(LogManager.getLogger(Strings.EMPTY)); - assertNotNull(LogManager.getLogger(LogManager.ROOT_LOGGER_NAME)); - assertEquals(LogManager.getRootLogger(), LogManager.getLogger(Strings.EMPTY)); - assertEquals(LogManager.getRootLogger(), LogManager.getLogger(LogManager.ROOT_LOGGER_NAME)); - } - - @Test - public void isAllEnabled() { - assertTrue("Incorrect level", logger.isEnabled(Level.ALL)); - } - - @Test - public void isDebugEnabled() { - assertTrue("Incorrect level", logger.isDebugEnabled()); - assertTrue("Incorrect level", logger.isEnabled(Level.DEBUG)); - } - - @Test - public void isErrorEnabled() { - assertTrue("Incorrect level", logger.isErrorEnabled()); - assertTrue("Incorrect level", logger.isEnabled(Level.ERROR)); - } - - @Test - public void isFatalEnabled() { - assertTrue("Incorrect level", logger.isFatalEnabled()); - assertTrue("Incorrect level", logger.isEnabled(Level.FATAL)); - } - - @Test - public void isInfoEnabled() { - assertTrue("Incorrect level", logger.isInfoEnabled()); - assertTrue("Incorrect level", logger.isEnabled(Level.INFO)); - } - - @Test - public void isOffEnabled() { - assertTrue("Incorrect level", logger.isEnabled(Level.OFF)); - } - - @Test - public void isTraceEnabled() { - assertTrue("Incorrect level", logger.isTraceEnabled()); - assertTrue("Incorrect level", logger.isEnabled(Level.TRACE)); - } - - @Test - public void isWarnEnabled() { - assertTrue("Incorrect level", logger.isWarnEnabled()); - assertTrue("Incorrect level", logger.isEnabled(Level.WARN)); - } - - @Test - public void isAllEnabledWithMarker() { - assertTrue("Incorrect level", logger.isEnabled(Level.ALL, marker)); - } - - @Test - public void isDebugEnabledWithMarker() { - assertTrue("Incorrect level", logger.isDebugEnabled(marker)); - assertTrue("Incorrect level", logger.isEnabled(Level.DEBUG, marker)); - } - - @Test - public void isErrorEnabledWithMarker() { - assertTrue("Incorrect level", logger.isErrorEnabled(marker)); - assertTrue("Incorrect level", logger.isEnabled(Level.ERROR, marker)); - } - - @Test - public void isFatalEnabledWithMarker() { - assertTrue("Incorrect level", logger.isFatalEnabled(marker)); - assertTrue("Incorrect level", logger.isEnabled(Level.FATAL, marker)); - } - - @Test - public void isInfoEnabledWithMarker() { - assertTrue("Incorrect level", logger.isInfoEnabled(marker)); - assertTrue("Incorrect level", logger.isEnabled(Level.INFO, marker)); - } - - @Test - public void isOffEnabledWithMarker() { - assertTrue("Incorrect level", logger.isEnabled(Level.OFF, marker)); - } - - @Test - public void isTraceEnabledWithMarker() { - assertTrue("Incorrect level", logger.isTraceEnabled(marker)); - assertTrue("Incorrect level", logger.isEnabled(Level.TRACE, marker)); - } - - @Test - public void isWarnEnabledWithMarker() { - assertTrue("Incorrect level", logger.isWarnEnabled(marker)); - assertTrue("Incorrect level", logger.isEnabled(Level.WARN, marker)); - } - - @Test - public void mdc() { - - ThreadContext.put("TestYear", Integer.valueOf(2010).toString()); - logger.debug("Debug message"); - String testYear = ThreadContext.get("TestYear"); - assertNotNull("Test Year is null", testYear); - assertEquals("Incorrect test year: " + testYear, "2010", testYear); - ThreadContext.clearMap(); - logger.debug("Debug message"); - assertEquals(2, results.size()); - System.out.println("Log line 1: " + results.get(0)); - System.out.println("log line 2: " + results.get(1)); - assertTrue("Incorrect MDC: " + results.get(0), - results.get(0).startsWith(" DEBUG Debug message {TestYear=2010}")); - assertTrue("MDC not cleared?: " + results.get(1), - results.get(1).startsWith(" DEBUG Debug message")); - } - - @Test - public void printf() { - logger.printf(Level.DEBUG, "Debug message %d", 1); - logger.printf(Level.DEBUG, MarkerManager.getMarker("Test"), "Debug message %d", 2); - assertEquals(2, results.size()); - assertThat("Incorrect message", results.get(0), startsWith(" DEBUG Debug message 1")); - assertThat("Incorrect message", results.get(1), startsWith("Test DEBUG Debug message 2")); - } - - @Before - public void setup() { - results.clear(); - } - - @Test - public void structuredData() { - ThreadContext.put("loginId", "JohnDoe"); - ThreadContext.put("ipAddress", "192.168.0.120"); - ThreadContext.put("locale", Locale.US.getDisplayName()); - final StructuredDataMessage msg = new StructuredDataMessage("Audit@18060", "Transfer Complete", "Transfer"); - msg.put("ToAccount", "123456"); - msg.put("FromAccount", "123457"); - msg.put("Amount", "200.00"); - logger.info(MarkerManager.getMarker("EVENT"), msg); - ThreadContext.clearMap(); - assertEquals(1, results.size()); - assertThat("Incorrect structured data: ", results.get(0), startsWith( - "EVENT INFO Transfer [Audit@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"] Transfer Complete")); - } - - @Test - public void throwing() { - logger.throwing(new IllegalArgumentException("Test Exception")); - assertEquals(1, results.size()); - assertThat("Incorrect Throwing", - results.get(0), startsWith("THROWING[ EXCEPTION ] ERROR Throwing java.lang.IllegalArgumentException: Test Exception")); - } - - - private class Response { - int status; - String message; - - public Response(final int status, final String message) { - this.status = status; - this.message = message; - } - - public int getStatus() { - return status; - } - - public void setStatus(final int status) { - this.status = status; - } - - public String getMessage() { - return message; - } - - public void setMessage(final String message) { - this.message = message; - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/MarkerTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/MarkerTest.java deleted file mode 100644 index 28740163a99..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/MarkerTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class MarkerTest { - - @Before - public void setUp() { - MarkerManager.clear(); - } - - @Test - public void testGetMarker() { - final Marker expected = MarkerManager.getMarker("A"); - assertNull(expected.getParents()); - } - - @Test - public void testGetMarkerWithParents() { - final Marker expected = MarkerManager.getMarker("A"); - final Marker p1 = MarkerManager.getMarker("P1"); - p1.addParents(MarkerManager.getMarker("PP1")); - final Marker p2 = MarkerManager.getMarker("P2"); - expected.addParents(p1); - expected.addParents(p2); - assertEquals(2, expected.getParents().length); - } - - @Test - public void testHasParents() { - final Marker parent = MarkerManager.getMarker("PARENT"); - final Marker existing = MarkerManager.getMarker("EXISTING"); - assertFalse(existing.hasParents()); - existing.setParents(parent); - assertTrue(existing.hasParents()); - } - - @Test - public void testMarker() { - final Marker parent = MarkerManager.getMarker("PARENT"); - final Marker test1 = MarkerManager.getMarker("TEST1").setParents(parent); - final Marker test2 = MarkerManager.getMarker("TEST2").addParents(parent); - assertTrue("TEST1 is not an instance of PARENT", test1.isInstanceOf(parent)); - assertTrue("TEST2 is not an instance of PARENT", test2.isInstanceOf(parent)); - } - - @Test - public void testMultipleParents() { - final Marker parent1 = MarkerManager.getMarker("PARENT1"); - final Marker parent2 = MarkerManager.getMarker("PARENT2"); - final Marker test1 = MarkerManager.getMarker("TEST1").setParents(parent1, parent2); - final Marker test2 = MarkerManager.getMarker("TEST2").addParents(parent1, parent2); - assertTrue("TEST1 is not an instance of PARENT1", test1.isInstanceOf(parent1)); - assertTrue("TEST1 is not an instance of PARENT2", test1.isInstanceOf(parent2)); - assertTrue("TEST2 is not an instance of PARENT1", test2.isInstanceOf(parent1)); - assertTrue("TEST2 is not an instance of PARENT2", test2.isInstanceOf(parent2)); - } - - @Test - public void testAddToExistingParents() { - final Marker parent = MarkerManager.getMarker("PARENT"); - final Marker existing = MarkerManager.getMarker("EXISTING"); - final Marker test1 = MarkerManager.getMarker("TEST1").setParents(existing); - test1.addParents(parent); - assertTrue("TEST1 is not an instance of PARENT", test1.isInstanceOf(parent)); - assertTrue("TEST1 is not an instance of EXISTING", test1.isInstanceOf(existing)); - } - - - @Test - public void testDuplicateParents() { - final Marker parent = MarkerManager.getMarker("PARENT"); - final Marker existing = MarkerManager.getMarker("EXISTING"); - final Marker test1 = MarkerManager.getMarker("TEST1").setParents(existing); - test1.addParents(parent); - final Marker[] parents = test1.getParents(); - test1.addParents(existing); - assertTrue("duplicate add allowed", parents.length == test1.getParents().length); - test1.addParents(existing, MarkerManager.getMarker("EXTRA")); - assertTrue("incorrect add", parents.length + 1 == test1.getParents().length); - assertTrue("TEST1 is not an instance of PARENT", test1.isInstanceOf(parent)); - assertTrue("TEST1 is not an instance of EXISTING", test1.isInstanceOf(existing)); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java deleted file mode 100644 index f465224bf20..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/NoopThreadContextTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests {@link ThreadContext}. - */ -public class NoopThreadContextTest { - - private static final String TRUE = "true"; - private static final String PROPERY_KEY_ALL = "disableThreadContext"; - private static final String PROPERY_KEY_MAP = "disableThreadContextMap"; - - @BeforeClass - public static void before() { - System.setProperty(PROPERY_KEY_ALL, TRUE); - System.setProperty(PROPERY_KEY_MAP, TRUE); - ThreadContext.init(); - } - - @AfterClass - public static void after() { - System.clearProperty(PROPERY_KEY_ALL); - System.clearProperty(PROPERY_KEY_MAP); - ThreadContext.init(); - } - - @Test - public void testNoop() { - ThreadContext.put("Test", "Test"); - final String value = ThreadContext.get("Test"); - assertNull("value was saved", value); - } - - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java deleted file mode 100644 index a2e5fce2640..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextInheritanceTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import org.apache.logging.log4j.spi.DefaultThreadContextMap; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -/** - * Tests {@link ThreadContext}. - */ -public class ThreadContextInheritanceTest { - - @BeforeClass - public static void setupClass() { - System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true"); - ThreadContext.init(); - } - - @AfterClass - public static void tearDownClass() { - System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP); - ThreadContext.init(); - } - - @Test - public void testPush() { - ThreadContext.push("Hello"); - ThreadContext.push("{} is {}", ThreadContextInheritanceTest.class.getSimpleName(), - "running"); - assertEquals("Incorrect parameterized stack value", - ThreadContext.pop(), "ThreadContextInheritanceTest is running"); - assertEquals("Incorrect simple stack value", ThreadContext.pop(), - "Hello"); - } - - @Test - public void testInheritanceSwitchedOn() throws Exception { - System.setProperty(DefaultThreadContextMap.INHERITABLE_MAP, "true"); - ThreadContext.init(); - try { - ThreadContext.clearMap(); - ThreadContext.put("Greeting", "Hello"); - StringBuilder sb = new StringBuilder(); - TestThread thread = new TestThread(sb); - thread.start(); - thread.join(); - String str = sb.toString(); - assertTrue("Unexpected ThreadContext value. Expected Hello. Actual " - + str, "Hello".equals(str)); - sb = new StringBuilder(); - thread = new TestThread(sb); - thread.start(); - thread.join(); - str = sb.toString(); - assertTrue("Unexpected ThreadContext value. Expected Hello. Actual " - + str, "Hello".equals(str)); - } finally { - System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP); - } - } - - @Test - public void perfTest() throws Exception { - ThreadContextUtilityClass.perfTest(); - } - - @Test - public void testGetContextReturnsEmptyMapIfEmpty() { - ThreadContextUtilityClass.testGetContextReturnsEmptyMapIfEmpty(); - } - - @Test - public void testGetContextReturnsMutableCopy() { - ThreadContextUtilityClass.testGetContextReturnsMutableCopy(); - } - - @Test - public void testGetImmutableContextReturnsEmptyMapIfEmpty() { - ThreadContextUtilityClass.testGetImmutableContextReturnsEmptyMapIfEmpty(); - } - - @Test(expected = UnsupportedOperationException.class) - public void testGetImmutableContextReturnsImmutableMapIfNonEmpty() { - ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfNonEmpty(); - } - - @Test(expected = UnsupportedOperationException.class) - public void testGetImmutableContextReturnsImmutableMapIfEmpty() { - ThreadContextUtilityClass.testGetImmutableContextReturnsImmutableMapIfEmpty(); - } - - @Test - public void testGetImmutableStackReturnsEmptyStackIfEmpty() { - ThreadContextUtilityClass.testGetImmutableStackReturnsEmptyStackIfEmpty(); - } - - @Test - public void testPut() { - ThreadContextUtilityClass.testPut(); - } - - @Test - public void testRemove() { - ThreadContext.clearMap(); - assertNull(ThreadContext.get("testKey")); - ThreadContext.put("testKey", "testValue"); - assertEquals("testValue", ThreadContext.get("testKey")); - - ThreadContext.remove("testKey"); - assertNull(ThreadContext.get("testKey")); - assertTrue(ThreadContext.isEmpty()); - } - - @Test - public void testContainsKey() { - ThreadContext.clearMap(); - assertFalse(ThreadContext.containsKey("testKey")); - ThreadContext.put("testKey", "testValue"); - assertTrue(ThreadContext.containsKey("testKey")); - - ThreadContext.remove("testKey"); - assertFalse(ThreadContext.containsKey("testKey")); - } - - private class TestThread extends Thread { - - private final StringBuilder sb; - - public TestThread(final StringBuilder sb) { - this.sb = sb; - } - - @Override - public void run() { - final String greeting = ThreadContext.get("Greeting"); - if (greeting == null) { - sb.append("null"); - } else { - sb.append(greeting); - } - ThreadContext.clearMap(); - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/BundleTestInfo.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/BundleTestInfo.java deleted file mode 100644 index 7cdf46831da..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/junit/BundleTestInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.junit; - -import java.io.FileReader; -import java.io.IOException; - -import org.apache.maven.model.Model; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.util.xml.pull.XmlPullParserException; - -/** - * Provides tests with bundle information. Reads the {@code pom.xml} in the current directory to get project settings. - */ -public class BundleTestInfo { - - private final MavenProject project; - - /** - * Constructs a new helper objects and initializes itself. - */ - public BundleTestInfo() { - try (final FileReader reader = new FileReader("pom.xml")) { - // get a raw POM view, not a fully realized POM object. - final Model model = new MavenXpp3Reader().read(reader); - this.project = new MavenProject(model); - } catch (final IOException | XmlPullParserException e) { - throw new IllegalStateException("Could not read pom.xml", e); - } - } - - /** - * Gets the Maven artifact ID. - * - * @return the Maven artifact ID. - */ - public String getArtifactId() { - return project.getArtifactId(); - } - - /** - * Gets the Maven version String. - * - * @return the Maven version String. - */ - public String getVersion() { - return project.getVersion(); - } - - @Override - public String toString() { - return "BundleTestInfo [project=" + project + "]"; - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageFactoryTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageFactoryTest.java deleted file mode 100644 index 71bdf298ac5..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/LocalizedMessageFactoryTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.message; - -import java.util.Locale; -import java.util.ResourceBundle; - -import org.junit.Assert; -import org.junit.Test; - -/** - * Tests {@link LocalizedMessageFactory}. - */ -public class LocalizedMessageFactoryTest { - - @Test - public void testNewMessage() { - final LocalizedMessageFactory localizedMessageFactory = new LocalizedMessageFactory( - ResourceBundle.getBundle("MF", Locale.US)); - final Message message = localizedMessageFactory.newMessage("hello_world"); - Assert.assertEquals("Hello world.", message.getFormattedMessage()); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java deleted file mode 100644 index 44a15aab659..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/MapMessageTest.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.message; - -import org.apache.logging.log4j.util.StringBuilderFormattable; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class MapMessageTest { - - @Test - public void testMap() { - final String testMsg = "Test message {}"; - final StringMapMessage msg = new StringMapMessage(); - msg.put("message", testMsg); - msg.put("project", "Log4j"); - final String result = msg.getFormattedMessage(); - final String expected = "message=\"Test message {}\" project=\"Log4j\""; - assertEquals(expected, result); - } - - @Test - public void testBuilder() { - final String testMsg = "Test message {}"; - final StringMapMessage msg = new StringMapMessage() - .with("message", testMsg) - .with("project", "Log4j"); - final String result = msg.getFormattedMessage(); - final String expected = "message=\"Test message {}\" project=\"Log4j\""; - assertEquals(expected, result); - } - - @Test - public void testXML() { - final String testMsg = "Test message {}"; - final StringMapMessage msg = new StringMapMessage(); - msg.put("message", testMsg); - msg.put("project", "Log4j"); - final String result = msg.getFormattedMessage(new String[]{"XML"}); - final String expected = "\n Test message {}\n" + - " Log4j\n" + - ""; - assertEquals(expected, result); - } - - @Test - public void testXMLEscape() { - final String testMsg = "Test message "; - final StringMapMessage msg = new StringMapMessage(); - msg.put("message", testMsg); - final String result = msg.getFormattedMessage(new String[]{"XML"}); - final String expected = "\n Test message <foo>\n" + - ""; - assertEquals(expected, result); - } - - @Test - public void testJSON() { - final String testMsg = "Test message {}"; - final StringMapMessage msg = new StringMapMessage(); - msg.put("message", testMsg); - msg.put("project", "Log4j"); - final String result = msg.getFormattedMessage(new String[]{"JSON"}); - final String expected = "{\"message\":\"Test message {}\", \"project\":\"Log4j\"}"; - assertEquals(expected, result); - } - - @Test - public void testJSONEscape() { - final String testMsg = "Test message \"Hello, World!\""; - final StringMapMessage msg = new StringMapMessage(); - msg.put("message", testMsg); - final String result = msg.getFormattedMessage(new String[]{"JSON"}); - final String expected = "{\"message\":\"Test message \\\"Hello, World!\\\"\"}"; - assertEquals(expected, result); - } - - @Test - public void testJSONEscapeNewlineAndOtherControlCharacters() { - final String testMsg = "hello\tworld\r\nhh\bere is it\f"; - final StringMapMessage msg = new StringMapMessage(); - msg.put("one\ntwo", testMsg); - final String result = msg.getFormattedMessage(new String[]{"JSON"}); - final String expected = - "{\"one\\ntwo\":\"hello\\tworld\\r\\nhh\\bere is it\\f\"}"; - assertEquals(expected, result); - } - - @Test - public void testJava() { - final String testMsg = "Test message {}"; - final StringMapMessage msg = new StringMapMessage(); - msg.put("message", testMsg); - msg.put("project", "Log4j"); - final String result = msg.getFormattedMessage(new String[]{"Java"}); - final String expected = "{message=\"Test message {}\", project=\"Log4j\"}"; - assertEquals(expected, result); - } - - @Test - public void testMutableByDesign() { // LOG4J2-763 - final StringMapMessage msg = new StringMapMessage(); - - // modify parameter before calling msg.getFormattedMessage - msg.put("key1", "value1"); - msg.put("key2", "value2"); - final String result = msg.getFormattedMessage(new String[]{"Java"}); - final String expected = "{key1=\"value1\", key2=\"value2\"}"; - assertEquals(expected, result); - - // modify parameter after calling msg.getFormattedMessage - msg.put("key3", "value3"); - final String result2 = msg.getFormattedMessage(new String[]{"Java"}); - final String expected2 = "{key1=\"value1\", key2=\"value2\", key3=\"value3\"}"; - assertEquals(expected2, result2); - } - - @Test - public void testGetNonStringValue() { - final String key = "Key"; - final ObjectMapMessage msg = new ObjectMapMessage() - .with(key, 1L); - assertEquals("1", msg.get(key)); - } - - @Test - public void testRemoveNonStringValue() { - final String key = "Key"; - final ObjectMapMessage msg = new ObjectMapMessage() - .with(key, 1L); - assertEquals("1", msg.remove(key)); - } - - @Test - public void testJSONFormatNonStringValue() { - final ObjectMapMessage msg = new ObjectMapMessage() - .with("key", 1L); - final String result = msg.getFormattedMessage(new String[]{"JSON"}); - final String expected = "{\"key\":\"1\"}"; - assertEquals(expected, result); - } - - @Test - public void testXMLFormatNonStringValue() { - final ObjectMapMessage msg = new ObjectMapMessage() - .with("key", 1L); - final String result = msg.getFormattedMessage(new String[]{"XML"}); - final String expected = "\n 1\n"; - assertEquals(expected, result); - } - - @Test - public void testFormatToUsedInOutputXml() { - final ObjectMapMessage msg = new ObjectMapMessage() - .with("key", new FormattableTestType()); - final String result = msg.getFormattedMessage(new String[]{"XML"}); - final String expected = "\n formatTo\n"; - assertEquals(expected, result); - } - - @Test - public void testFormatToUsedInOutputJson() { - final ObjectMapMessage msg = new ObjectMapMessage() - .with("key", new FormattableTestType()); - final String result = msg.getFormattedMessage(new String[]{"JSON"}); - final String expected = "{\"key\":\"formatTo\"}"; - assertEquals(expected, result); - } - - @Test - public void testFormatToUsedInOutputJava() { - final ObjectMapMessage msg = new ObjectMapMessage() - .with("key", new FormattableTestType()); - final String result = msg.getFormattedMessage(new String[]{"JAVA"}); - final String expected = "{key=\"formatTo\"}"; - assertEquals(expected, result); - } - - @Test - public void testFormatToUsedInOutputDefault() { - final ObjectMapMessage msg = new ObjectMapMessage() - .with("key", new FormattableTestType()); - final String result = msg.getFormattedMessage(null); - final String expected = "key=\"formatTo\""; - assertEquals(expected, result); - } - - @Test - public void testGetUsesDeepToString() { - final String key = "key"; - final ObjectMapMessage msg = new ObjectMapMessage() - .with(key, new FormattableTestType()); - final String result = msg.get(key); - final String expected = "formatTo"; - assertEquals(expected, result); - } - - @Test - public void testRemoveUsesDeepToString() { - final String key = "key"; - final ObjectMapMessage msg = new ObjectMapMessage() - .with(key, new FormattableTestType()); - final String result = msg.remove(key); - final String expected = "formatTo"; - assertEquals(expected, result); - } - - private static final class FormattableTestType implements StringBuilderFormattable { - - @Override - public String toString() { - return "toString"; - } - - @Override - public void formatTo(StringBuilder buffer) { - buffer.append("formatTo"); - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageSerializationTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageSerializationTest.java deleted file mode 100644 index d158cd22685..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/MessageFormatMessageSerializationTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.message; - -import java.util.Arrays; -import java.util.Collection; - -import org.apache.logging.log4j.AbstractSerializationTest; -import org.junit.runners.Parameterized; - -public class MessageFormatMessageSerializationTest extends AbstractSerializationTest { - - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList(new Object[][]{ - {new MessageFormatMessage("Test")}, - {new MessageFormatMessage("Test {0} {1}", "message", "test")}, - {new MessageFormatMessage("{0}{1}{2}", 3, '.', 14L)} - }); - } - - public MessageFormatMessageSerializationTest(final MessageFormatMessage message) { - super(message); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java deleted file mode 100644 index 1cda558af76..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/message/StringFormattedMessageTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.message; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Locale; - -import org.apache.logging.log4j.junit.Mutable; -import org.junit.Assert; -import org.junit.Test; - -/** - * - */ -public class StringFormattedMessageTest { - - private static final int LOOP_CNT = 500; - String[] array = new String[LOOP_CNT]; - - @Test - public void testNoArgs() { - final String testMsg = "Test message %1s"; - StringFormattedMessage msg = new StringFormattedMessage(testMsg, (Object[]) null); - String result = msg.getFormattedMessage(); - final String expected = "Test message null"; - assertEquals(expected, result); - final Object[] array = null; - msg = new StringFormattedMessage(testMsg, array, null); - result = msg.getFormattedMessage(); - assertEquals(expected, result); - } - - @Test - public void testOneStringArg() { - final String testMsg = "Test message %1s"; - final StringFormattedMessage msg = new StringFormattedMessage(testMsg, "Apache"); - final String result = msg.getFormattedMessage(); - final String expected = "Test message Apache"; - assertEquals(expected, result); - } - - @Test - public void testOneIntArgLocaleUs() { - final String testMsg = "Test e = %+10.4f"; - final StringFormattedMessage msg = new StringFormattedMessage(Locale.US, testMsg, Math.E); - final String result = msg.getFormattedMessage(); - final String expected = "Test e = +2.7183"; - assertEquals(expected, result); - } - - @Test - public void testOneArgLocaleFrance() { - final String testMsg = "Test e = %+10.4f"; - final StringFormattedMessage msg = new StringFormattedMessage(Locale.FRANCE, testMsg, Math.E); - final String result = msg.getFormattedMessage(); - final String expected = "Test e = +2,7183"; - assertEquals(expected, result); - } - - @Test - public void testException() { - final String testMsg = "Test message {0}"; - final MessageFormatMessage msg = new MessageFormatMessage(testMsg, "Apache", new NullPointerException("Null")); - final String result = msg.getFormattedMessage(); - final String expected = "Test message Apache"; - assertEquals(expected, result); - final Throwable t = msg.getThrowable(); - assertNotNull("No Throwable", t); - } - - @Test - public void testUnsafeWithMutableParams() { // LOG4J2-763 - final String testMsg = "Test message %s"; - final Mutable param = new Mutable().set("abc"); - final StringFormattedMessage msg = new StringFormattedMessage(testMsg, param); - - // modify parameter before calling msg.getFormattedMessage - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Should use initial param value", "Test message XYZ", actual); - } - - @Test - public void testSafeAfterGetFormattedMessageIsCalled() { // LOG4J2-763 - final String testMsg = "Test message %s"; - final Mutable param = new Mutable().set("abc"); - final StringFormattedMessage msg = new StringFormattedMessage(testMsg, param); - - // modify parameter after calling msg.getFormattedMessage - msg.getFormattedMessage(); - param.set("XYZ"); - final String actual = msg.getFormattedMessage(); - assertEquals("Should use initial param value", "Test message abc", actual); - } - - @Test - public void testSerialization() throws IOException, ClassNotFoundException { - final StringFormattedMessage expected = new StringFormattedMessage("Msg", "a", "b", "c"); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (final ObjectOutputStream out = new ObjectOutputStream(baos)) { - out.writeObject(expected); - } - final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - final ObjectInputStream in = new ObjectInputStream(bais); - final StringFormattedMessage actual = (StringFormattedMessage) in.readObject(); - Assert.assertEquals(expected, actual); - Assert.assertEquals(expected.getFormat(), actual.getFormat()); - Assert.assertEquals(expected.getFormattedMessage(), actual.getFormattedMessage()); - Assert.assertArrayEquals(expected.getParameters(), actual.getParameters()); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java deleted file mode 100644 index 8a56b409b3c..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.spi; - -import org.apache.logging.log4j.simple.SimpleLoggerContext; -import org.junit.Test; - -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.logging.Logger; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; - -/** - * Created by Pavel.Sivolobtchik@uxpsystems.com on 2016-10-19. - */ -public class LoggerAdapterTest { - - private class RunnableThreadTest implements Runnable { - private final AbstractLoggerAdapter adapter; - private final LoggerContext context; - private final CountDownLatch doneSignal; - private final int index; - private Map resultMap; - - private final CountDownLatch startSignal; - - public RunnableThreadTest(final int index, final TestLoggerAdapter adapter, final LoggerContext context, - final CountDownLatch startSignal, final CountDownLatch doneSignal) { - this.adapter = adapter; - this.context = context; - this.startSignal = startSignal; - this.doneSignal = doneSignal; - this.index = index; - } - - public Map getResultMap() { - return resultMap; - } - - @Override - public void run() { - try { - startSignal.await(); - resultMap = adapter.getLoggersInContext(context); - resultMap.put(String.valueOf(index), new TestLogger()); - doneSignal.countDown(); - } - catch (final Exception e) { - e.printStackTrace(); - } - } - - } - - private static class TestLogger extends Logger { - public TestLogger() { - super("test", null); - } - } - - private static class TestLoggerAdapter extends AbstractLoggerAdapter { - - @Override - protected LoggerContext getContext() { - return null; - } - - @Override - protected Logger newLogger(final String name, final LoggerContext context) { - return null; - } - } - - /** - * Testing synchronization in the getLoggersInContext() method - */ - @Test - public synchronized void testGetLoggersInContextSynch() throws Exception { - final TestLoggerAdapter adapter = new TestLoggerAdapter(); - - final int num = 500; - - final CountDownLatch startSignal = new CountDownLatch(1); - final CountDownLatch doneSignal = new CountDownLatch(num); - - final RunnableThreadTest[] instances = new RunnableThreadTest[num]; - LoggerContext lastUsedContext = null; - for (int i = 0; i < num; i++) { - if (i % 2 == 0) { - //every other time create a new context - lastUsedContext = new SimpleLoggerContext(); - } - final RunnableThreadTest runnable = new RunnableThreadTest(i, adapter, lastUsedContext, startSignal, doneSignal); - final Thread thread = new Thread(runnable); - thread.start(); - instances[i] = runnable; - } - - startSignal.countDown(); - doneSignal.await(); - - for (int i = 0; i < num; i = i + 2) { - //maps for the same context should be the same instance - final Map resultMap1 = instances[i].getResultMap(); - final Map resultMap2 = instances[i + 1].getResultMap(); - assertSame("not the same map for instances" + i + " and " + (i + 1) + ":", resultMap1, resultMap2); - assertEquals(2, resultMap1.size()); - } - } -} \ No newline at end of file diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/status/StatusLoggerSerializationTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/status/StatusLoggerSerializationTest.java deleted file mode 100644 index 373fdff01b1..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/status/StatusLoggerSerializationTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.status; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Collection; - -import org.apache.logging.log4j.AbstractSerializationTest; -import org.junit.Ignore; -import org.junit.runners.Parameterized.Parameters; - -@Ignore -public class StatusLoggerSerializationTest extends AbstractSerializationTest { - - @Parameters - public static Collection data() { - return Arrays.asList(new Object[][] { { StatusLogger.getLogger() } }); - } - - public StatusLoggerSerializationTest(final Serializable serializable) { - super(serializable); - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/CharsTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/CharsTest.java deleted file mode 100644 index d1447f202bd..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/CharsTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * - */ -public class CharsTest { - @Test - public void invalidDigitReturnsNullCharacter() throws Exception { - assertEquals('\0', Chars.getUpperCaseHex(-1)); - assertEquals('\0', Chars.getUpperCaseHex(16)); - assertEquals('\0', Chars.getUpperCaseHex(400)); - assertEquals('\0', Chars.getLowerCaseHex(-1)); - assertEquals('\0', Chars.getLowerCaseHex(16)); - assertEquals('\0', Chars.getLowerCaseHex(400)); - } - - @Test - public void validDigitReturnsProperCharacter() throws Exception { - final char[] expectedLower = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - final char[] expectedUpper = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - for (int i = 0; i < 16; i++) { - assertEquals(String.format("Expected %x", i), expectedLower[i], Chars.getLowerCaseHex(i)); - assertEquals(String.format("Expected %X", i), expectedUpper[i], Chars.getUpperCaseHex(i)); - } - } -} \ No newline at end of file diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java deleted file mode 100644 index 3beea8971f0..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import static org.junit.Assert.assertEquals; - -/** - * - */ -@RunWith(Parameterized.class) -public class EnvironmentPropertySourceTest { - - private final PropertySource source = new EnvironmentPropertySource(); - private final CharSequence expected; - private final List tokens; - - public EnvironmentPropertySourceTest(final CharSequence expected, final List tokens) { - this.expected = expected; - this.tokens = tokens; - } - - @Parameterized.Parameters(name = "{0}") - public static Object[][] data() { - return new Object[][]{ - {"LOG4J_CONFIGURATION_FILE", Arrays.asList("configuration", "file")}, - {"LOG4J_FOO_BAR_PROPERTY", Arrays.asList("foo", "bar", "property")}, - {"LOG4J_EXACT", Collections.singletonList("EXACT")}, - {"LOG4J_TEST_PROPERTY_NAME", PropertySource.Util.tokenize("Log4jTestPropertyName")}, - }; - } - - @Test - public void testNormalFormFollowsEnvironmentVariableConventions() throws Exception { - assertEquals(expected, source.getNormalForm(tokens)); - } -} \ No newline at end of file diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/LambdaUtilTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/LambdaUtilTest.java deleted file mode 100644 index 1972e14a218..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/LambdaUtilTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.util; - -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the LambdaUtil class. - */ -public class LambdaUtilTest { - - @Test - public void testGetSupplierResultOfSupplier() { - final String expected = "result"; - final Object actual = LambdaUtil.get(new Supplier() { - @Override - public String get() { - return expected; - } - }); - assertSame(expected, actual); - } - - @Test - public void testGetMessageSupplierResultOfSupplier() { - final Message expected = new SimpleMessage("hi"); - final Message actual = LambdaUtil.get(new MessageSupplier() { - @Override - public Message get() { - return expected; - } - }); - assertSame(expected, actual); - } - - @Test - public void testGetSupplierReturnsNullIfSupplierNull() { - final Object actual = LambdaUtil.get((Supplier) null); - assertNull(actual); - } - - @Test - public void testGetMessageSupplierReturnsNullIfSupplierNull() { - final Object actual = LambdaUtil.get((MessageSupplier) null); - assertNull(actual); - } - - @Test(expected = RuntimeException.class) - public void testGetSupplierExceptionIfSupplierThrowsException() { - LambdaUtil.get(new Supplier() { - @Override - public String get() { - throw new RuntimeException(); - } - }); - } - - @Test(expected = RuntimeException.class) - public void testGetMessageSupplierExceptionIfSupplierThrowsException() { - LambdaUtil.get(new MessageSupplier() { - @Override - public Message get() { - throw new RuntimeException(); - } - }); - } - - @Test - public void testGetAllReturnsResultOfSuppliers() { - final String expected1 = "result1"; - final Supplier function1 = new Supplier() { - @Override - public String get() { - return expected1; - } - }; - final String expected2 = "result2"; - final Supplier function2 = new Supplier() { - @Override - public String get() { - return expected2; - } - }; - - final Supplier[] functions = { function1, function2 }; - final Object[] actual = LambdaUtil.getAll(functions); - assertEquals(actual.length, functions.length); - assertSame(expected1, actual[0]); - assertSame(expected2, actual[1]); - } - - @Test - public void testGetAllReturnsNullArrayIfSupplierArrayNull() { - final Object[] actual = LambdaUtil.getAll((Supplier[]) null); - assertNull(actual); - } - - @Test - public void testGetAllReturnsNullElementsIfSupplierArrayContainsNulls() { - final Supplier[] functions = new Supplier[3]; - final Object[] actual = LambdaUtil.getAll(functions); - assertEquals(actual.length, functions.length); - for (final Object object : actual) { - assertNull(object); - } - } - - @Test(expected = RuntimeException.class) - public void testGetAllThrowsExceptionIfAnyOfTheSuppliersThrowsException() { - final Supplier function1 = new Supplier() { - @Override - public String get() { - return "abc"; - } - }; - final Supplier function2 = new Supplier() { - @Override - public String get() { - throw new RuntimeException(); - } - }; - - final Supplier[] functions = { function1, function2 }; - LambdaUtil.getAll(functions); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/Log4jCharsetsPropertiesTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/Log4jCharsetsPropertiesTest.java deleted file mode 100644 index ff8fd5fdc14..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/Log4jCharsetsPropertiesTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.util; - -import java.nio.charset.Charset; -import java.util.Enumeration; -import java.util.ResourceBundle; - -import org.junit.Assert; -import org.junit.Test; - -public class Log4jCharsetsPropertiesTest { - - /** - * Tests that we can load all mappings. - */ - @Test - public void testLoadAll() { - ResourceBundle resourceBundle = PropertiesUtil.getCharsetsResourceBundle(); - Enumeration keys = resourceBundle.getKeys(); - while (keys.hasMoreElements()) { - String key = keys.nextElement(); - Assert.assertFalse(String.format("The Charset %s is available and should not be mapped", key), - Charset.isSupported(key)); - String value = resourceBundle.getString(key); - Assert.assertTrue(String.format("The Charset %s is is not available and is mapped from %s", value, key), - Charset.isSupported(value)); - } - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java deleted file mode 100644 index 13aaf9c90e0..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import org.junit.Test; - -import static org.junit.Assert.*; - -public class ProcessIdUtilTest { - - @Test - public void processIdTest() throws Exception { - String processId = ProcessIdUtil.getProcessId(); - assertFalse("ProcessId is default", processId.equals(ProcessIdUtil.DEFAULT_PROCESSID)); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesUtilTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesUtilTest.java deleted file mode 100644 index 574fbc18827..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertiesUtilTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.util; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.Properties; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class PropertiesUtilTest { - - private final Properties properties = new Properties(); - - @Before - public void setUp() throws Exception { - properties.load(ClassLoader.getSystemResourceAsStream("PropertiesUtilTest.properties")); - } - - @Test - public void testExtractSubset() throws Exception { - assertHasAllProperties(PropertiesUtil.extractSubset(properties, "a")); - assertHasAllProperties(PropertiesUtil.extractSubset(properties, "b.")); - assertHasAllProperties(PropertiesUtil.extractSubset(properties, "c.1")); - assertHasAllProperties(PropertiesUtil.extractSubset(properties, "dd")); - assertEquals(0, properties.size()); - } - - @Test - public void testPartitionOnCommonPrefix() throws Exception { - final Map parts = PropertiesUtil.partitionOnCommonPrefixes(properties); - assertEquals(4, parts.size()); - assertHasAllProperties(parts.get("a")); - assertHasAllProperties(parts.get("b")); - assertHasAllProperties(PropertiesUtil.partitionOnCommonPrefixes(parts.get("c")).get("1")); - assertHasAllProperties(parts.get("dd")); - } - - private static void assertHasAllProperties(final Properties properties) { - assertNotNull(properties); - assertEquals("1", properties.getProperty("1")); - assertEquals("2", properties.getProperty("2")); - assertEquals("3", properties.getProperty("3")); - } - - - @Test - public void testGetCharsetProperty() throws Exception { - final Properties p = new Properties(); - p.setProperty("e.1", StandardCharsets.US_ASCII.name()); - p.setProperty("e.2", "wrong-charset-name"); - final PropertiesUtil pu = new PropertiesUtil(p); - - assertEquals(Charset.defaultCharset(), pu.getCharsetProperty("e.0")); - assertEquals(StandardCharsets.US_ASCII, pu.getCharsetProperty("e.1")); - assertEquals(Charset.defaultCharset(), pu.getCharsetProperty("e.2")); - } - - @Test - public void testGetMappedProperty_sun_stdout_encoding() { - final PropertiesUtil pu = new PropertiesUtil(System.getProperties()); - Charset expected = System.console() == null ? Charset.defaultCharset() : StandardCharsets.UTF_8; - assertEquals(expected, pu.getCharsetProperty("sun.stdout.encoding")); - } - - @Test - public void testGetMappedProperty_sun_stderr_encoding() { - final PropertiesUtil pu = new PropertiesUtil(System.getProperties()); - Charset expected = System.console() == null ? Charset.defaultCharset() : StandardCharsets.UTF_8; - assertEquals(expected, pu.getCharsetProperty("sun.err.encoding")); - } -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceCamelCaseTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceCamelCaseTest.java deleted file mode 100644 index a197085420c..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/PropertySourceCamelCaseTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import static org.junit.Assert.assertEquals; - -@RunWith(Parameterized.class) -public class PropertySourceCamelCaseTest { - - private final CharSequence expected; - private final List tokens; - - public PropertySourceCamelCaseTest(final CharSequence expected, final List tokens) { - this.expected = expected; - this.tokens = tokens; - } - - @Parameterized.Parameters(name = "{0}") - public static Object[][] data() { - return new Object[][]{ - {"", Collections.singletonList("")}, - {"foo", Collections.singletonList("foo")}, - {"fooBar", Arrays.asList("foo", "bar")}, - {"oneTwoThree", Arrays.asList("one", "two", "three")}, - }; - } - - @Test - public void testJoinAsCamelCase() throws Exception { - assertEquals(expected, PropertySource.Util.joinAsCamelCase(tokens)); - } -} \ No newline at end of file diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/StackLocatorUtilTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/StackLocatorUtilTest.java deleted file mode 100644 index abade291cc6..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/StackLocatorUtilTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import java.util.Stack; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.ParentRunner; -import sun.reflect.Reflection; - -import static org.junit.Assert.*; - -@RunWith(BlockJUnit4ClassRunner.class) -public class StackLocatorUtilTest { - - - @Test - public void testStackTraceEquivalence() throws Exception { - for (int i = 1; i < 15; i++) { - final Class expected = Reflection.getCallerClass(i + StackLocator.JDK_7u25_OFFSET); - final Class actual = StackLocatorUtil.getCallerClass(i); - final Class fallbackActual = Class.forName( - StackLocatorUtil.getStackTraceElement(i).getClassName()); - assertSame(expected, actual); - assertSame(expected, fallbackActual); - } - } - - @Test - public void testGetCallerClass() throws Exception { - final Class expected = StackLocatorUtilTest.class; - final Class actual = StackLocatorUtil.getCallerClass(1); - assertSame(expected, actual); - } - - @Test - public void testGetCallerClassNameViaStackTrace() throws Exception { - final Class expected = StackLocatorUtilTest.class; - final Class actual = Class.forName(new Throwable().getStackTrace()[0].getClassName()); - assertSame(expected, actual); - } - - @Test - public void testGetCurrentStackTrace() throws Exception { - final Stack> classes = StackLocatorUtil.getCurrentStackTrace(); - final Stack> reversed = new Stack<>(); - reversed.ensureCapacity(classes.size()); - while (!classes.empty()) { - reversed.push(classes.pop()); - } - while (reversed.peek() != StackLocatorUtil.class) { - reversed.pop(); - } - reversed.pop(); // ReflectionUtil - assertSame(StackLocatorUtilTest.class, reversed.pop()); - } - - @Test - public void testGetCallerClassViaName() throws Exception { - final Class expected = BlockJUnit4ClassRunner.class; - final Class actual = StackLocatorUtil.getCallerClass("org.junit.runners.ParentRunner"); - // if this test fails in the future, it's probably because of a JUnit upgrade; check the new stack trace and - // update this test accordingly - assertSame(expected, actual); - } - - @Test - public void testGetCallerClassViaAnchorClass() throws Exception { - final Class expected = BlockJUnit4ClassRunner.class; - final Class actual = StackLocatorUtil.getCallerClass(ParentRunner.class); - // if this test fails in the future, it's probably because of a JUnit upgrade; check the new stack trace and - // update this test accordingly - assertSame(expected, actual); - } - - @Test - public void testLocateClass() { - final ClassLocator locator = new ClassLocator(); - final Class clazz = locator.locateClass(); - assertNotNull("Could not locate class", clazz); - assertEquals("Incorrect class", this.getClass(), clazz); - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/StringBuildersTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/StringBuildersTest.java deleted file mode 100644 index b52e3f64483..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/StringBuildersTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the StringBuilders class. - */ -public class StringBuildersTest { - @Test - public void trimToMaxSize() throws Exception { - final StringBuilder sb = new StringBuilder(); - final char[] value = new char[4 * 1024]; - sb.append(value); - - assertTrue("needs trimming", sb.length() > Constants.MAX_REUSABLE_MESSAGE_SIZE); - StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE); - assertTrue("trimmed OK", sb.length() <= Constants.MAX_REUSABLE_MESSAGE_SIZE); - } - - @Test - public void trimToMaxSizeWithLargeCapacity() throws Exception { - final StringBuilder sb = new StringBuilder(); - final char[] value = new char[4 * 1024]; - sb.append(value); - sb.setLength(0); - - assertTrue("needs trimming", sb.capacity() > Constants.MAX_REUSABLE_MESSAGE_SIZE); - StringBuilders.trimToMaxSize(sb, Constants.MAX_REUSABLE_MESSAGE_SIZE); - assertTrue("trimmed OK", sb.capacity() <= Constants.MAX_REUSABLE_MESSAGE_SIZE); - } - - -} \ No newline at end of file diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/StringsTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/StringsTest.java deleted file mode 100644 index d5167391d21..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/StringsTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.util; - -import org.junit.Assert; -import org.junit.Test; - -public class StringsTest { - - /** - * A sanity test to make sure a typo does not mess up {@link Strings#EMPTY}. - */ - @Test - public void testEMPTY() { - Assert.assertEquals("", Strings.EMPTY); - Assert.assertEquals(0, Strings.EMPTY.length()); - } - - @Test - public void testQuote() { - Assert.assertEquals("'Q'", Strings.quote("Q")); - } - -} diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java deleted file mode 100644 index 2deda1233ca..00000000000 --- a/log4j-api/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.util; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests that the Unbox ring buffer size is configurable. - * Must be run in a separate process as the other UnboxTest or the last-run test will fail. - */ -public class Unbox2ConfigurableTest { - @Ignore - @BeforeClass - public static void beforeClass() { - System.setProperty("log4j.unbox.ringbuffer.size", "65"); - } - - @Ignore - @AfterClass - public static void afterClass() throws Exception { - System.clearProperty("log4j.unbox.ringbuffer.size"); - - // ensure subsequent tests (which assume 32 slots) pass - final Field field = Unbox.class.getDeclaredField("RINGBUFFER_SIZE"); - field.setAccessible(true); // make non-private - - final Field modifierField = Field.class.getDeclaredField("modifiers"); - modifierField.setAccessible(true); - modifierField.setInt(field, field.getModifiers() &~ Modifier.FINAL); // make non-final - - field.set(null, 32); // reset to default - - final Field threadLocalField = Unbox.class.getDeclaredField("threadLocalState"); - threadLocalField.setAccessible(true); - final ThreadLocal threadLocal = (ThreadLocal) threadLocalField.get(null); - threadLocal.remove(); - threadLocalField.set(null, new ThreadLocal<>()); - } - - @Ignore - @Test - public void testBoxConfiguredTo128Slots() throws Exception { - // next power of 2 that is 65 or more - assertEquals(128, Unbox.getRingbufferSize()); - } - - @Ignore - @Test - public void testBoxSuccessfullyConfiguredTo128Slots() throws Exception { - final int MAX = 128; - final StringBuilder[] probe = new StringBuilder[MAX * 3]; - for (int i = 0; i <= probe.length - 8; ) { - probe[i++] = Unbox.box(true); - probe[i++] = Unbox.box('c'); - probe[i++] = Unbox.box(Byte.MAX_VALUE); - probe[i++] = Unbox.box(Double.MAX_VALUE); - probe[i++] = Unbox.box(Float.MAX_VALUE); - probe[i++] = Unbox.box(Integer.MAX_VALUE); - probe[i++] = Unbox.box(Long.MAX_VALUE); - probe[i++] = Unbox.box(Short.MAX_VALUE); - } - for (int i = 0; i < probe.length - MAX; i++) { - assertSame("probe[" + i +"], probe[" + (i + MAX) +"]", probe[i], probe[i + MAX]); - for (int j = 1; j < MAX - 1; j++) { - assertNotSame("probe[" + i +"], probe[" + (i + j) +"]", probe[i], probe[i + j]); - } - } - } -} \ No newline at end of file diff --git a/log4j-appserver/pom.xml b/log4j-appserver/pom.xml index bd1351fd941..3e5bd9bec0f 100644 --- a/log4j-appserver/pom.xml +++ b/log4j-appserver/pom.xml @@ -15,14 +15,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + + 4.0.0 + log4j org.apache.logging.log4j - 2.10.1-SNAPSHOT + 3.0.0-SNAPSHOT - 4.0.0 log4j-appserver jar @@ -33,74 +34,24 @@ ${basedir}/.. Web Documentation /log4j-appserver - 8.5.20 - 8.2.0.v20160908 org.apache.logging.log4j.appserver + true - org.apache.logging.log4j - log4j-api - - - org.apache.logging.log4j - log4j-core - - - javax.servlet - javax.servlet-api - 3.0.1 + org.eclipse.jetty + jetty-util provided org.apache.tomcat - tomcat-catalina - ${tomcat.version} + tomcat-juli provided - - - org.apache.tomcat - tomcat-annotations-api - - - org.apache.tomcat - tomcat-jsp-api - - - org.apache.tomcat - tomcat-el-api - - - - org.eclipse.jetty - jetty-util - ${jetty.version} - provided - - - org.apache.logging.log4j - log4j-core - test-jar - test - - - junit - junit - test - - - org.springframework - spring-test - test - - - org.mockito - mockito-core - test + log4j-api @@ -120,12 +71,12 @@ + org.apache.maven.plugins maven-changes-plugin - ${changes.plugin.version} @@ -141,7 +92,6 @@ org.apache.maven.plugins maven-checkstyle-plugin - ${checkstyle.plugin.version} ${log4jParentDir}/checkstyle.xml @@ -154,15 +104,16 @@ org.apache.maven.plugins maven-javadoc-plugin - ${javadoc.plugin.version} - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
+ <p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, - and the Apache Log4j logo are trademarks of The Apache Software Foundation.

]]>
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>
+ ${javadoc.opts} false true + 8 http://docs.oracle.com/javaee/6/api/ @@ -176,22 +127,9 @@
- - org.codehaus.mojo - findbugs-maven-plugin - ${findbugs.plugin.version} - - true - -Duser.language=en - Normal - Default - ${log4jParentDir}/findbugs-exclude-filter.xml - - org.apache.maven.plugins maven-jxr-plugin - ${jxr.plugin.version} non-aggregate @@ -210,11 +148,15 @@ org.apache.maven.plugins maven-pmd-plugin - ${pmd.plugin.version} ${maven.compiler.target} + + com.github.spotbugs + spotbugs-maven-plugin +
+
diff --git a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/Log4j2Logger.java b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/Log4j2Logger.java index 7bcce986ffa..5b4bac2e2ec 100644 --- a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/Log4j2Logger.java +++ b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/Log4j2Logger.java @@ -25,8 +25,8 @@ import org.eclipse.jetty.util.log.Logger; /** - * Provides a native Apache Log4j 2 for Eclipse Jetty logging. - * + * Provides a native Apache Log4j 2 logger for Eclipse Jetty logging. + * *

* To direct Jetty to use this class, set the system property {{org.eclipse.jetty.util.log.class}} to this class name. *

diff --git a/log4j-bom/pom.xml b/log4j-bom/pom.xml index 281572a9acf..d5b305ac0cb 100644 --- a/log4j-bom/pom.xml +++ b/log4j-bom/pom.xml @@ -19,39 +19,83 @@ org.apache.logging logging-parent - 1 + 7 + 4.0.0 Apache Log4j BOM Apache Log4j Bill of Materials org.apache.logging.log4j log4j-bom - 2.10.1-SNAPSHOT + 3.0.0-SNAPSHOT pom + + + + 10 + true + true + true + + + + + org.apache.logging.log4j + log4j-1.2-api + ${project.version} + org.apache.logging.log4j log4j-api ${project.version} + + org.apache.logging.log4j + log4j-api-test + ${project.version} + + + + org.apache.logging.log4j + log4j-appserver + ${project.version} + + + + org.apache.logging.log4j + log4j-cassandra + ${project.version} + org.apache.logging.log4j log4j-core ${project.version} - org.apache.logging.log4j - log4j-1.2-api + log4j-core-test ${project.version} - + org.apache.logging.log4j - log4j-jcl + log4j-couchdb + ${project.version} + + + + org.apache.logging.log4j + log4j-csv + ${project.version} + + + + org.apache.logging.log4j + log4j-docker ${project.version} @@ -60,52 +104,57 @@ log4j-flume-ng ${project.version} - + org.apache.logging.log4j - log4j-taglib + log4j-iostreams ${project.version} - + org.apache.logging.log4j - log4j-jmx-gui + log4j-jcl ${project.version} - + org.apache.logging.log4j - log4j-slf4j-impl + log4j-jdbc ${project.version} - + org.apache.logging.log4j - log4j-web + log4j-jdbc-dbcp2 ${project.version} - + org.apache.logging.log4j - log4j-couchdb + log4j-jeromq ${project.version} - + org.apache.logging.log4j - log4j-mongodb2 + log4j-jms ${project.version} - + org.apache.logging.log4j - log4j-mongodb3 + log4j-jmx-gui ${project.version} - + org.apache.logging.log4j - log4j-cassandra + log4j-jndi + ${project.version} + + + org.apache.logging.log4j + log4j-jndi-test ${project.version} @@ -114,10 +163,10 @@ log4j-jpa ${project.version} - + org.apache.logging.log4j - log4j-iostreams + log4j-jpl ${project.version} @@ -126,38 +175,149 @@ log4j-jul ${project.version} - + + + org.apache.logging.log4j + log4j-kafka + ${project.version} + + + + org.apache.logging.log4j + log4j-kubernetes + ${project.version} + + + + org.apache.logging.log4j + log4j-layout-jackson + ${project.version} + + + + org.apache.logging.log4j + log4j-layout-jackson-json + ${project.version} + + + + org.apache.logging.log4j + log4j-layout-jackson-xml + ${project.version} + + + + org.apache.logging.log4j + log4j-layout-jackson-yaml + ${project.version} + + + + org.apache.logging.log4j + log4j-layout-template-json + ${project.version} + + + org.apache.logging.log4j + log4j-layout-template-json-test + ${project.version} + + org.apache.logging.log4j log4j-liquibase ${project.version} + + + org.apache.logging.log4j + log4j-mongodb3 + ${project.version} + + + + org.apache.logging.log4j + log4j-mongodb4 + ${project.version} + + + org.apache.logging.log4j + log4j-plugin-processor + ${project.version} + + + + org.apache.logging.log4j + log4j-plugins + ${project.version} + + + org.apache.logging.log4j + log4j-plugins-test + ${project.version} + + + + org.apache.logging.log4j + log4j-script + ${project.version} + + + + org.apache.logging.log4j + log4j-slf4j2-impl + ${project.version} + + + + org.apache.logging.log4j + log4j-slf4j-impl + ${project.version} + + + + org.apache.logging.log4j + log4j-smtp + ${project.version} + + + + org.apache.logging.log4j + log4j-spring-boot + ${project.version} + + + + org.apache.logging.log4j + log4j-spring-cloud-config-client + ${project.version} + + + + org.apache.logging.log4j + log4j-taglib + ${project.version} + + + + org.apache.logging.log4j + log4j-to-slf4j + ${project.version} + + + + org.apache.logging.log4j + log4j-web + ${project.version} + - - org.apache.maven.plugins - maven-site-plugin - 3.7 - - true - true - - org.apache.rat apache-rat-plugin - 0.12 - - - org.apache.maven.plugins - maven-doap-plugin - 1.2 - - true - diff --git a/log4j-cassandra/pom.xml b/log4j-cassandra/pom.xml index e3c8cb3d8ea..357b387f6a8 100644 --- a/log4j-cassandra/pom.xml +++ b/log4j-cassandra/pom.xml @@ -15,12 +15,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 @@ -34,6 +33,11 @@ Cassandra Documentation /log4j-cassandra org.apache.logging.log4j.cassandra + true + + + + 25.1-jre @@ -41,35 +45,34 @@ org.apache.logging.log4j log4j-core + - com.datastax.cassandra - cassandra-driver-core + org.apache.logging.log4j + log4j-jdbc - - junit - junit + com.datastax.cassandra + cassandra-driver-core - org.mockito - mockito-core + org.apache.logging.log4j + log4j-api-test test org.apache.logging.log4j - log4j-api - test-jar + log4j-core-test + test org.apache.logging.log4j - log4j-core - test-jar + log4j-slf4j-impl + test org.apache.cassandra cassandra-all - 2.2.8 test @@ -83,8 +86,23 @@ - org.apache.logging.log4j - log4j-slf4j-impl + org.apache.cassandra + cassandra-thrift + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.mockito + mockito-core test @@ -101,6 +119,22 @@
+ + org.apache.maven.plugins + maven-failsafe-plugin + + + + --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.nio=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.util.concurrent=ALL-UNNAMED + --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED + --add-opens java.base/sun.nio.ch=ALL-UNNAMED + + + @@ -108,7 +142,6 @@ org.apache.maven.plugins maven-changes-plugin - ${changes.plugin.version} @@ -124,7 +157,6 @@ org.apache.maven.plugins maven-checkstyle-plugin - ${checkstyle.plugin.version} ${log4jParentDir}/checkstyle.xml @@ -137,15 +169,15 @@ org.apache.maven.plugins maven-javadoc-plugin - ${javadoc.plugin.version} - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
+ <p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, - and the Apache Log4j logo are trademarks of The Apache Software Foundation.

]]>
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>
false true + 8
@@ -156,22 +188,9 @@
- - org.codehaus.mojo - findbugs-maven-plugin - ${findbugs.plugin.version} - - true - -Duser.language=en - Normal - Default - ${log4jParentDir}/findbugs-exclude-filter.xml - - org.apache.maven.plugins maven-jxr-plugin - ${jxr.plugin.version} non-aggregate @@ -190,11 +209,14 @@ org.apache.maven.plugins maven-pmd-plugin - ${pmd.plugin.version} ${maven.compiler.target} + + com.github.spotbugs + spotbugs-maven-plugin +
diff --git a/log4j-cassandra/revapi.json b/log4j-cassandra/revapi.json new file mode 100644 index 00000000000..ca1fa97c59c --- /dev/null +++ b/log4j-cassandra/revapi.json @@ -0,0 +1,12 @@ +[ + { + "extension": "revapi.ignore", + "configuration": [ + { + "code": "java.method.removed", + "old": "method void org.apache.logging.log4j.cassandra.CassandraManager::writeInternal(org.apache.logging.log4j.core.LogEvent)", + "justification": "Method not needed" + } + ] + } +] diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraAppender.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraAppender.java index 7a9ade9744b..8b316839ac9 100644 --- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraAppender.java +++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraAppender.java @@ -17,18 +17,19 @@ package org.apache.logging.log4j.cassandra; import com.datastax.driver.core.BatchStatement; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; import org.apache.logging.log4j.core.appender.db.ColumnMapping; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.net.SocketAddress; -import org.apache.logging.log4j.core.util.Clock; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; /** * Appender plugin that uses a Cassandra database. @@ -36,21 +37,22 @@ * @see SocketAddress * @see ColumnMapping */ -@Plugin(name = "Cassandra", category = Core.CATEGORY_NAME, elementType = CassandraAppender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = CassandraAppender.ELEMENT_TYPE, printObject = true) +@Plugin("Cassandra") public class CassandraAppender extends AbstractDatabaseAppender { private CassandraAppender(final String name, final Filter filter, final boolean ignoreExceptions, - final CassandraManager manager) { - super(name, filter, ignoreExceptions, manager); + Property[] properties, final CassandraManager manager) { + super(name, filter, null, ignoreExceptions, properties, manager); } - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } public static class Builder> extends AbstractAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { /** * List of Cassandra node contact points. Addresses without a port (or port set to 0) will use the default @@ -177,7 +179,7 @@ public CassandraAppender build() { final CassandraManager manager = CassandraManager.getManager(getName(), contactPoints, columns, useTls, clusterName, keyspace, table, username, password, useClockForTimestampGenerator, bufferSize, batched, batchType); - return new CassandraAppender(getName(), getFilter(), isIgnoreExceptions(), manager); + return new CassandraAppender(getName(), getFilter(), isIgnoreExceptions(), getPropertyArray(), manager); } } diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java index 65ee60eecd7..88f87d71693 100644 --- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java +++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java @@ -16,12 +16,6 @@ */ package org.apache.logging.log4j.cassandra; -import java.io.Serializable; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - import com.datastax.driver.core.BatchStatement; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.Cluster; @@ -31,14 +25,19 @@ import org.apache.logging.log4j.core.appender.ManagerFactory; import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; import org.apache.logging.log4j.core.appender.db.ColumnMapping; -import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter; -import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; import org.apache.logging.log4j.core.net.SocketAddress; +import org.apache.logging.log4j.jdbc.convert.DateTypeConverter; import org.apache.logging.log4j.spi.ThreadContextMap; import org.apache.logging.log4j.spi.ThreadContextStack; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.Strings; +import java.io.Serializable; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + /** * Manager for a Cassandra appender instance. */ @@ -87,12 +86,6 @@ protected void connectAndStart() { // a Session automatically manages connections for us } - @Deprecated - @Override - protected void writeInternal(final LogEvent event) { - writeInternal(event, null); - } - @Override protected void writeInternal(final LogEvent event, final Serializable serializable) { for (int i = 0; i < columnMappings.size(); i++) { @@ -105,8 +98,7 @@ protected void writeInternal(final LogEvent event, final Serializable serializab } else if (Date.class.isAssignableFrom(columnMapping.getType())) { values[i] = DateTypeConverter.fromMillis(event.getTimeMillis(), columnMapping.getType().asSubclass(Date.class)); } else { - values[i] = TypeConverters.convert(columnMapping.getLayout().toSerializable(event), - columnMapping.getType(), null); + values[i] = columnMapping.getTypeConverter().convert(columnMapping.getLayout().toSerializable(event), null); } } final BoundStatement boundStatement = preparedStatement.bind(values); diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/ClockTimestampGenerator.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/ClockTimestampGenerator.java index 1c7328ba793..cbc5c1fe888 100644 --- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/ClockTimestampGenerator.java +++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/ClockTimestampGenerator.java @@ -17,8 +17,8 @@ package org.apache.logging.log4j.cassandra; import com.datastax.driver.core.TimestampGenerator; -import org.apache.logging.log4j.core.util.Clock; -import org.apache.logging.log4j.core.util.ClockFactory; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.ClockFactory; /** * A {@link TimestampGenerator} implementation using the configured {@link Clock}. diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java index 0336dd0e8a1..3121a56049a 100644 --- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java +++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java @@ -20,4 +20,4 @@ * @see Cassandra Appender manual * @since 2.8 */ -package org.apache.logging.log4j.cassandra; \ No newline at end of file +package org.apache.logging.log4j.cassandra; diff --git a/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraAppenderIT.java b/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraAppenderIT.java index 9cd07941395..43924418275 100644 --- a/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraAppenderIT.java +++ b/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraAppenderIT.java @@ -16,30 +16,30 @@ */ package org.apache.logging.log4j.cassandra; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - +import com.datastax.driver.core.Cluster; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.categories.Appenders; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.rules.RuleChain; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Integration test for CassandraAppender. */ -@Category(Appenders.Cassandra.class) +@Disabled("https://issues.apache.org/jira/browse/LOG4J2-3384") public class CassandraAppenderIT { private static final String DDL = "CREATE TABLE logs (" + @@ -54,15 +54,12 @@ public class CassandraAppenderIT { "ndc list" + ")"; - private static final LoggerContextRule CTX = new LoggerContextRule("CassandraAppenderTest.xml"); - private static final CassandraRule CASSANDRA = new CassandraRule("test", DDL); - - @ClassRule - public static RuleChain rules = RuleChain.outerRule(CASSANDRA).around(CTX); - + @Disabled("Doesn't work in Java 11 at this Cassandra version") @Test - public void appendManyEvents() throws Exception { - final Logger logger = CTX.getLogger(); + @CassandraFixture(keyspace = "test", setup = DDL) + @LoggerContextSource("CassandraAppenderTest.xml") + public void appendManyEvents(final LoggerContext context, final Cluster cluster) throws Exception { + final Logger logger = context.getLogger(getClass()); ThreadContext.put("test", "mdc"); ThreadContext.push("ndc"); for (int i = 0; i < 20; i++) { @@ -73,7 +70,7 @@ public void appendManyEvents() throws Exception { TimeUnit.SECONDS.sleep(3); int i = 0; - try (final Session session = CASSANDRA.connect()) { + try (final Session session = cluster.connect("test")) { for (final Row row : session.execute("SELECT * FROM logs")) { assertNotNull(row.get("id", UUID.class)); assertNotNull(row.get("timeid", UUID.class)); diff --git a/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraExtension.java b/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraExtension.java new file mode 100644 index 00000000000..4f17e12e950 --- /dev/null +++ b/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraExtension.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.cassandra; + +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Permission; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.cassandra.service.CassandraDaemon; +import org.apache.logging.log4j.core.util.Cancellable; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.core.util.Log4jThreadFactory; +import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; +import org.opentest4j.TestAbortedException; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; + +import static org.junit.jupiter.api.Assertions.fail; + +public class CassandraExtension extends TypeBasedParameterResolver implements BeforeEachCallback, AfterEachCallback { + private static final ThreadFactory THREAD_FACTORY = Log4jThreadFactory.createThreadFactory("CassandraFixture"); + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + final var cassandraFixture = context.getRequiredTestMethod().getAnnotation(CassandraFixture.class); + if (cassandraFixture != null) { + final var latch = new CountDownLatch(1); + final var errorRef = new AtomicReference(); + final var embeddedCassandra = new EmbeddedCassandra(latch, errorRef); + final Path root = Files.createTempDirectory("cassandra"); + Files.createDirectories(root.resolve("data")); + final Path config = root.resolve("cassandra.yml"); + Files.copy(getClass().getResourceAsStream("/cassandra.yaml"), config); + System.setProperty("cassandra.config", "file:" + config.toString()); + System.setProperty("cassandra.storagedir", root.toString()); + System.setProperty("cassandra-foreground", "true"); // prevents Cassandra from closing stdout/stderr + THREAD_FACTORY.newThread(embeddedCassandra).start(); + latch.await(); + final Throwable error = errorRef.get(); + if (error instanceof NoClassDefFoundError) { + throw new TestAbortedException("Unsupported platform", error); + } else { + fail(error); + } + final var cluster = Cluster.builder().addContactPoints(InetAddress.getLoopbackAddress()).build(); + final var store = context.getStore( + ExtensionContext.Namespace.create(CassandraFixture.class, context.getRequiredTestInstance())); + store.put(Cluster.class, cluster); + store.put(EmbeddedCassandra.class, embeddedCassandra); + try (final Session session = cluster.connect()) { + session.execute("CREATE KEYSPACE " + cassandraFixture.keyspace() + " WITH REPLICATION = " + + "{ 'class': 'SimpleStrategy', 'replication_factor': 2 };"); + } + try (final Session session = cluster.connect(cassandraFixture.keyspace())) { + for (final String ddl : cassandraFixture.setup()) { + session.execute(ddl); + } + } + } + + } + + @Override + public void afterEach(final ExtensionContext context) throws Exception { + final var store = + context.getStore(ExtensionContext.Namespace.create(CassandraFixture.class, context.getRequiredTestInstance())); + final var cluster = store.get(Cluster.class, Cluster.class); + final var embeddedCassandra = store.get(EmbeddedCassandra.class, EmbeddedCassandra.class); + if (embeddedCassandra != null) { + Closer.closeSilently(cluster); + embeddedCassandra.cancel(); + } + } + + @Override + public Cluster resolveParameter( + final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final var store = extensionContext.getStore( + ExtensionContext.Namespace.create(CassandraFixture.class, extensionContext.getRequiredTestInstance())); + return store.get(Cluster.class, Cluster.class); + } + + private static class EmbeddedCassandra implements Cancellable { + + private final CassandraDaemon daemon = new CassandraDaemon(); + private final CountDownLatch latch; + private final AtomicReference errorRef; + + private EmbeddedCassandra( + final CountDownLatch latch, final AtomicReference errorRef) { + this.latch = latch; + this.errorRef = errorRef; + } + + @Override + public void cancel() { + // LOG4J2-1850 Cassandra on Windows calls System.exit in the daemon stop method + if (PropertiesUtil.getProperties().isOsWindows()) { + cancelOnWindows(); + } else { + daemon.stop(); + } + } + + private void cancelOnWindows() { + final SecurityManager currentSecurityManager = System.getSecurityManager(); + try { + final SecurityManager securityManager = new SecurityManager() { + @Override + public void checkPermission(final Permission permission) { + final String permissionName = permission.getName(); + if (permissionName != null && permissionName.startsWith("exitVM")) { + throw new SecurityException("test"); + } + } + }; + System.setSecurityManager(securityManager); + daemon.stop(); + } catch (final SecurityException ex) { + // ignore + } finally { + System.setSecurityManager(currentSecurityManager); + } + } + + @Override + public void run() { + try { + daemon.init(null); + daemon.start(); + } catch (final Exception | LinkageError e) { + errorRef.set(Throwables.getRootCause(e)); + } finally { + latch.countDown(); + } + } + } +} diff --git a/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraFixture.java b/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraFixture.java new file mode 100644 index 00000000000..1c9e893b36a --- /dev/null +++ b/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraFixture.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.cassandra; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(CassandraExtension.class) +@Tag("cassandra") +public @interface CassandraFixture { + /** + * Name of keyspace to create. + */ + String keyspace(); + + /** + * List of setup statements to execute in keyspace. + */ + String[] setup(); +} diff --git a/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraRule.java b/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraRule.java deleted file mode 100644 index 2939d07f935..00000000000 --- a/log4j-cassandra/src/test/java/org/apache/logging/log4j/cassandra/CassandraRule.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.cassandra; - -import java.io.IOException; -import java.net.InetAddress; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.Permission; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ThreadFactory; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.Session; -import org.apache.cassandra.service.CassandraDaemon; -import org.apache.logging.log4j.LoggingException; -import org.apache.logging.log4j.core.util.Cancellable; -import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.core.util.Log4jThreadFactory; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.junit.rules.ExternalResource; - -/** - * JUnit rule to set up and tear down a Cassandra database instance. - */ -public class CassandraRule extends ExternalResource { - - private static final ThreadFactory THREAD_FACTORY = Log4jThreadFactory.createThreadFactory("Cassandra"); - - private final CountDownLatch latch = new CountDownLatch(1); - private final Cancellable embeddedCassandra = new EmbeddedCassandra(latch); - private final String keyspace; - private final String tableDdl; - private Cluster cluster; - - public CassandraRule(final String keyspace, final String tableDdl) { - this.keyspace = keyspace; - this.tableDdl = tableDdl; - } - - public Cluster getCluster() { - return cluster; - } - - public Session connect() { - return cluster.connect(keyspace); - } - - @Override - protected void before() throws Throwable { - final Path root = Files.createTempDirectory("cassandra"); - Files.createDirectories(root.resolve("data")); - final Path config = root.resolve("cassandra.yml"); - Files.copy(getClass().getResourceAsStream("/cassandra.yaml"), config); - System.setProperty("cassandra.config", "file:" + config.toString()); - System.setProperty("cassandra.storagedir", root.toString()); - System.setProperty("cassandra-foreground", "true"); // prevents Cassandra from closing stdout/stderr - THREAD_FACTORY.newThread(embeddedCassandra).start(); - latch.await(); - cluster = Cluster.builder().addContactPoints(InetAddress.getLoopbackAddress()).build(); - try (final Session session = cluster.connect()) { - session.execute("CREATE KEYSPACE " + keyspace + " WITH REPLICATION = " + - "{ 'class': 'SimpleStrategy', 'replication_factor': 2 };"); - } - try (final Session session = connect()) { - session.execute(tableDdl); - } - } - - @Override - protected void after() { - Closer.closeSilently(cluster); - embeddedCassandra.cancel(); - } - - private static class EmbeddedCassandra implements Cancellable { - - private final CassandraDaemon daemon = new CassandraDaemon(); - private final CountDownLatch latch; - - private EmbeddedCassandra(final CountDownLatch latch) { - this.latch = latch; - } - - @Override - public void cancel() { - // LOG4J2-1850 Cassandra on Windows calls System.exit in the daemon stop method - if (PropertiesUtil.getProperties().isOsWindows()) { - cancelOnWindows(); - } else { - daemon.stop(); - } - } - - private void cancelOnWindows() { - final SecurityManager currentSecurityManager = System.getSecurityManager(); - try { - final SecurityManager securityManager = new SecurityManager() { - @Override - public void checkPermission(final Permission permission) { - final String permissionName = permission.getName(); - if (permissionName != null && permissionName.startsWith("exitVM")) { - throw new SecurityException("test"); - } - } - }; - System.setSecurityManager(securityManager); - daemon.stop(); - } catch (final SecurityException ex) { - // ignore - } finally { - System.setSecurityManager(currentSecurityManager); - } - } - - @Override - public void run() { - try { - daemon.init(null); - } catch (final IOException e) { - throw new LoggingException("Cannot initialize embedded Cassandra instance", e); - } - daemon.start(); - latch.countDown(); - } - } -} diff --git a/log4j-cassandra/src/test/resources/cassandra.yaml b/log4j-cassandra/src/test/resources/cassandra.yaml index 356e43dc2db..aab593b3e4f 100644 --- a/log4j-cassandra/src/test/resources/cassandra.yaml +++ b/log4j-cassandra/src/test/resources/cassandra.yaml @@ -380,7 +380,7 @@ memtable_allocation_type: heap_buffers # to the number of cores. #memtable_flush_writers: 8 -# A fixed memory pool size in MB for for SSTable index summaries. If left +# A fixed memory pool size in MB for SSTable index summaries. If left # empty, this will default to 5% of the heap size. If the memory usage of # all index summaries exceeds this limit, SSTables with low read rates will # shrink their index summaries in order to meet this limit. However, this @@ -549,7 +549,7 @@ rpc_server_type: sync # Uncomment to set socket buffer size for internode communication # Note that when setting this, the buffer size is limited by net.core.wmem_max -# and when not setting it it is defined by net.ipv4.tcp_wmem +# and when not setting it is defined by net.ipv4.tcp_wmem # See: # /proc/sys/net/core/wmem_max # /proc/sys/net/core/rmem_max diff --git a/log4j-core-its/pom.xml b/log4j-core-its/pom.xml index 9df2f43fca6..460c0731a75 100644 --- a/log4j-core-its/pom.xml +++ b/log4j-core-its/pom.xml @@ -20,8 +20,7 @@ org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + 3.0.0-SNAPSHOT log4j-core-its jar @@ -29,47 +28,33 @@ Integration Tests for the Apache Log4j Implementation ${basedir}/.. - Core Documentation - /core + Core Integration Tests Documentation + /log4j-core-its + true + true + true + true + true + true + - org.apache.logging.log4j - log4j-api - - - org.apache.logging.log4j - log4j-api - test-jar - test - - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-core - test-jar - test - - - - com.lmax - disruptor + javax.jms + javax.jms-api + provided true com.conversantmedia disruptor - jdk7 true - + - org.jctools - jctools-core + com.lmax + disruptor true @@ -84,100 +69,151 @@ jackson-databind true + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + true + com.fasterxml.jackson.dataformat jackson-dataformat-yaml true - + - com.fasterxml.jackson.dataformat - jackson-dataformat-xml + javax.activation + javax.activation-api + true + + + + javax.xml.bind + jaxb-api + true + + + + org.jctools + jctools-core true + + org.junit.jupiter + junit-jupiter-engine + + + + org.junit.vintage + junit-vintage-engine + com.fasterxml.woodstox woodstox-core - 5.0.3 true - - - - log4j - log4j - 1.2.17 + org.apache.logging.log4j + log4j-api test - - org.slf4j - slf4j-api + org.apache.logging.log4j + log4j-api-test test - org.slf4j - slf4j-ext + org.apache.logging.log4j + log4j-core test - - junit - junit + org.apache.logging.log4j + log4j-core-test test - org.hamcrest - hamcrest-all + org.apache.logging.log4j + log4j-layout-jackson-json test - - org.springframework - spring-test + org.apache.logging.log4j + log4j-layout-jackson-xml + test + + + + org.apache.activemq + activemq-broker test + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + + commons-logging commons-logging test + + org.hamcrest + hamcrest + test + + + + + log4j + log4j + test + + + ch.qos.logback + logback-classic + test + ch.qos.logback logback-core test + - ch.qos.logback - logback-classic + org.slf4j + slf4j-api test - - org.jboss.spec.javax.jms - jboss-jms-api_1.1_spec - provided - true + org.slf4j + slf4j-ext + test - + - org.apache.activemq - activemq-broker + org.springframework + spring-test + test + + + + com.github.tomakehurst + wiremock-jre8 test - - - org.apache.geronimo.specs - geronimo-jms_1.1_spec - - + + org.apache.felix + maven-bundle-plugin + maven-compiler-plugin @@ -191,8 +227,23 @@ - org.apache.felix - maven-bundle-plugin + org.apache.maven.plugins + maven-failsafe-plugin + + + ${project.basedir}/src/test/resources + + + **/*.java + + + **/ForceNoDefClassFoundError.* + + + org.apache.logging.log4j.core.test.categories.PerformanceTests, + org.apache.logging.log4j.core.test.categories.Appenders$Jms + + org.apache.maven.plugins @@ -233,40 +284,6 @@ - - maven-surefire-plugin - - true - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - ${project.basedir}/src/test/resources - - - **/*.java - - - **/ForceNoDefClassFoundError.* - - - org.apache.logging.log4j.categories.PerformanceTests, - org.apache.logging.log4j.categories.Appenders$Jms - - - - - org.apache.maven.plugins - maven-deploy-plugin - ${deploy.plugin.version} - - true - - - diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/FilterPerformanceComparison.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/FilterPerformanceComparison.java index 1de0e97ccdd..768aabd05d3 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/FilterPerformanceComparison.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/FilterPerformanceComparison.java @@ -20,7 +20,7 @@ import java.util.HashMap; import java.util.Map; -import org.apache.logging.log4j.categories.PerformanceTests; +import org.apache.logging.log4j.core.test.categories.PerformanceTests; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.junit.After; import org.junit.AfterClass; @@ -211,4 +211,4 @@ public void run() { } } -} \ No newline at end of file +} diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceComparison.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceComparison.java index 5c3aad6ae05..b858f6ba1fa 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceComparison.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceComparison.java @@ -25,11 +25,12 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import org.apache.logging.log4j.categories.PerformanceTests; +import org.apache.logging.log4j.core.test.categories.PerformanceTests; import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.util.Profiler; +import org.apache.logging.log4j.core.test.util.Profiler; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -109,7 +110,8 @@ private void doRun() { System.out.println("###############################################"); } - //@Test + @Test + @Ignore public void testRawPerformance() throws Exception { final OutputStream os = new FileOutputStream("target/testos.log", true); final long result1 = writeToStream(COUNT, os); @@ -200,4 +202,4 @@ private byte[] getBytes(final String s) { return s.getBytes(); } -} \ No newline at end of file +} diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceRun.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceRun.java index 64f0e35d865..ded98695a55 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceRun.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/PerformanceRun.java @@ -24,8 +24,8 @@ import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import org.apache.logging.log4j.categories.PerformanceTests; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.categories.PerformanceTests; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Test; diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/SimplePerfTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/SimplePerfTest.java index 97ed3951019..e153413b1fd 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/SimplePerfTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/SimplePerfTest.java @@ -16,24 +16,23 @@ */ package org.apache.logging.log4j.core; -import static org.junit.Assert.assertTrue; - -import java.util.Random; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Timer; -import org.apache.logging.log4j.categories.PerformanceTests; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.apache.logging.log4j.util.Timer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.Random; /** * */ -@Category(PerformanceTests.class) +@Tag("sleepy") +@Tag("performance") public class SimplePerfTest { private static org.apache.logging.log4j.Logger logger = LogManager.getLogger(SimplePerfTest.class.getName()); @@ -45,11 +44,11 @@ public class SimplePerfTest { private static int RAND_SIZE = 250; private static int[] values = new int[RAND_SIZE]; - @BeforeClass + @BeforeAll public static void setupClass() { final Configuration config = LoggerContext.getContext().getConfiguration(); - + if (!DefaultConfiguration.DEFAULT_NAME.equals(config.getName())) { System.out.println("Configuration was " + config.getName()); LoggerContext.getContext().start(new DefaultConfiguration()); @@ -71,43 +70,70 @@ public static void setupClass() { } @Test - public void debugDisabled() { - System.gc(); - final Timer timer = new Timer("DebugDisabled", LOOP_CNT); - timer.start(); - for (int i=0; i < LOOP_CNT; ++i) { - logger.isDebugEnabled(); + public void debugDisabled() throws Exception { + long elapsed = 0; + for (int retries = 1; retries <= 5; ++retries) { + System.out.println("Iteration " + retries); + System.gc(); + Thread.sleep(100); + final Timer timer = new Timer("DebugDisabled", LOOP_CNT); + timer.start(); + for (int i = 0; i < LOOP_CNT; ++i) { + logger.isDebugEnabled(); + } + timer.stop(); + System.out.println(timer.toString()); + elapsed = timer.getElapsedNanoTime(); + if (elapsed < maxTime) { + break; + } } - timer.stop(); - System.out.println(timer.toString()); - assertTrue("Timer exceeded max time of " + maxTime, maxTime > timer.getElapsedNanoTime()); + Assertions.assertTrue(maxTime > elapsed, "Timer exceeded max time of " + maxTime); } @Test - public void debugDisabledByLevel() { - System.gc(); - final Timer timer = new Timer("DebugDisabled", LOOP_CNT); - timer.start(); - for (int i=0; i < LOOP_CNT; ++i) { - logger.isEnabled(Level.DEBUG); + public void debugDisabledByLevel() throws Exception { + long elapsed = 0; + for (int retries = 1; retries <= 5; ++retries) { + System.out.println("Iteration " + retries); + System.gc(); + Thread.sleep(100); + final Timer timer = new Timer("IsEnabled", LOOP_CNT); + timer.start(); + for (int i = 0; i < LOOP_CNT; ++i) { + logger.isEnabled(Level.DEBUG); + } + timer.stop(); + System.out.println(timer.toString()); + elapsed = timer.getElapsedNanoTime(); + if (elapsed < maxTime) { + break; + } } - timer.stop(); - System.out.println(timer.toString()); - assertTrue("Timer exceeded max time of " + maxTime, maxTime > timer.getElapsedNanoTime()); + Assertions.assertTrue(maxTime > elapsed, "Timer exceeded max time of " + maxTime); } @Test - public void debugLogger() { - System.gc(); - final Timer timer = new Timer("DebugLogger", LOOP_CNT); - final String msg = "This is a test"; - timer.start(); - for (int i=0; i < LOOP_CNT; ++i) { - logger.debug(msg); + public void debugLogger() throws Exception { + long elapsed = 0; + for (int retries = 1; retries <= 5; ++retries) { + System.out.println("Iteration " + retries); + System.gc(); + Thread.sleep(100); + final Timer timer = new Timer("DebugLogger", LOOP_CNT); + final String msg = "This is a test"; + timer.start(); + for (int i = 0; i < LOOP_CNT; ++i) { + logger.debug(msg); + } + timer.stop(); + System.out.println(timer.toString()); + elapsed = timer.getElapsedNanoTime(); + if (elapsed < maxTime) { + break; + } } - timer.stop(); - System.out.println(timer.toString()); - assertTrue("Timer exceeded max time of " + maxTime, maxTime > timer.getElapsedNanoTime()); + Assertions.assertTrue(maxTime > elapsed, "Timer exceeded max time of " + maxTime); } /* @Test @@ -158,7 +184,7 @@ public int nextInt() { private static void bubbleSort(final int array[]) { final int length = array.length; for (int i = 0; i < length; i++) { - for (int j = 1; j > length - i; j++) { + for (int j = 1; j < length - i; j++) { if (array[j-1] > array[j]) { final int temp = array[j-1]; array[j-1] = array[j]; diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedPerfTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedPerfTest.java index 57987666855..59b8c7dbab4 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedPerfTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedPerfTest.java @@ -21,8 +21,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Timer; -import org.apache.logging.log4j.categories.PerformanceTests; +import org.apache.logging.log4j.core.test.categories.PerformanceTests; +import org.apache.logging.log4j.util.Timer; import org.junit.Test; import org.junit.experimental.categories.Category; diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedTest.java index 3df298a450f..8e6672a6a86 100644 --- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedTest.java @@ -23,8 +23,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.PerformanceTests; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.categories.PerformanceTests; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java index 25261f95d98..1cd9f78d7b1 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java @@ -35,13 +35,13 @@ import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.layout.JsonLayout; import org.apache.logging.log4j.core.lookup.JavaLookup; import org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration; import org.apache.logging.log4j.core.net.ssl.SslConfiguration; -import org.apache.logging.log4j.core.net.ssl.TestConstants; +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.jackson.json.layout.JsonLayout; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.status.StatusData; import org.apache.logging.log4j.status.StatusListener; @@ -152,10 +152,12 @@ private static Log4jLogEvent createLogEvent() { public LoggerContextRule ctx = new LoggerContextRule("HttpAppenderTest.xml"); @Rule - public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort() - .keystorePath(TestConstants.KEYSTORE_FILE) - .keystorePassword(String.valueOf(TestConstants.KEYSTORE_PWD())) - .keystoreType(TestConstants.KEYSTORE_TYPE)); + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort() + .dynamicHttpsPort() + .keystorePath(TestConstants.KEYSTORE_FILE) + .keystorePassword(String.valueOf(TestConstants.KEYSTORE_PWD())) + .keyManagerPassword(String.valueOf(TestConstants.KEYSTORE_PWD())) + .keystoreType(TestConstants.KEYSTORE_TYPE)); @Test public void testAppend() throws Exception { @@ -163,8 +165,8 @@ public void testAppend() throws Exception { .willReturn(SUCCESS_RESPONSE)); final Appender appender = HttpAppender.newBuilder() - .withName("Http") - .withLayout(JsonLayout.createDefaultLayout()) + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) .setConfiguration(ctx.getConfiguration()) .setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/")) .build(); @@ -182,13 +184,13 @@ public void testAppendHttps() throws Exception { .willReturn(SUCCESS_RESPONSE)); final Appender appender = HttpAppender.newBuilder() - .withName("Http") - .withLayout(JsonLayout.createDefaultLayout()) + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) .setConfiguration(ctx.getConfiguration()) .setUrl(new URL("https://localhost:" + wireMockRule.httpsPort() + "/test/log4j/")) .setSslConfiguration(SslConfiguration.createSSLConfiguration(null, - KeyStoreConfiguration.createKeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null), - TrustStoreConfiguration.createKeyStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), TestConstants.TRUSTSTORE_TYPE, null))) + KeyStoreConfiguration.createKeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), null, null, TestConstants.KEYSTORE_TYPE, null), + TrustStoreConfiguration.createKeyStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null ,null, TestConstants.TRUSTSTORE_TYPE, null))) .setVerifyHostname(false) .build(); appender.append(createLogEvent()); @@ -205,8 +207,8 @@ public void testAppendMethodPut() throws Exception { .willReturn(SUCCESS_RESPONSE)); final Appender appender = HttpAppender.newBuilder() - .withName("Http") - .withLayout(JsonLayout.createDefaultLayout()) + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) .setConfiguration(ctx.getConfiguration()) .setMethod("PUT") .setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/1234")) @@ -225,8 +227,8 @@ public void testAppendCustomHeader() throws Exception { .willReturn(SUCCESS_RESPONSE)); final Appender appender = HttpAppender.newBuilder() - .withName("Http") - .withLayout(JsonLayout.createDefaultLayout()) + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) .setConfiguration(ctx.getConfiguration()) .setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/")) .setHeaders(new Property[] { @@ -269,8 +271,8 @@ public void close() throws IOException { } error = null; final Appender appender = HttpAppender.newBuilder() - .withName("Http") - .withLayout(JsonLayout.createDefaultLayout()) + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) .setConfiguration(ctx.getConfiguration()) .setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/")) .build(); @@ -292,10 +294,10 @@ public void testAppendError() throws Exception { .willReturn(FAILURE_RESPONSE)); final Appender appender = HttpAppender.newBuilder() - .withName("Http") - .withLayout(JsonLayout.createDefaultLayout()) + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) .setConfiguration(ctx.getConfiguration()) - .withIgnoreExceptions(false) + .setIgnoreExceptions(false) .setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/")) .build(); appender.append(createLogEvent()); @@ -304,13 +306,13 @@ public void testAppendError() throws Exception { @Test(expected = AppenderLoggingException.class) public void testAppendConnectError() throws Exception { final Appender appender = HttpAppender.newBuilder() - .withName("Http") - .withLayout(JsonLayout.createDefaultLayout()) + .setName("Http") + .setLayout(JsonLayout.createDefaultLayout()) .setConfiguration(ctx.getConfiguration()) - .withIgnoreExceptions(false) + .setIgnoreExceptions(false) .setUrl(new URL("http://localhost:"+(wireMockRule.port()+1)+"/test/log4j/")) .build(); appender.append(createLogEvent()); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java index 323ca13a83d..3d01e38be7b 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/JsonCompleteFileAppenderTest.java @@ -16,22 +16,21 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.BufferedReader; +import static org.junit.Assert.assertTrue; + import java.io.File; -import java.io.FileReader; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.List; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.filter.TimeFilterTest; -import org.apache.logging.log4j.core.impl.Log4jLogEventTest; +import org.apache.logging.log4j.core.test.categories.Layouts; import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.core.selector.CoreContextSelectors; -import org.apache.logging.log4j.core.util.ClockFactory; -import org.apache.logging.log4j.junit.CleanFiles; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.util.FixedTimeClock; +import org.apache.logging.log4j.core.time.ClockFactory; +import org.apache.logging.log4j.test.junit.CleanFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; @@ -42,8 +41,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import static org.junit.Assert.*; - /** * Tests a "complete" JSON file. */ @@ -59,7 +56,7 @@ public JsonCompleteFileAppenderTest(final Class contextSelector @BeforeClass public static void beforeClass() { - System.setProperty(ClockFactory.PROPERTY_NAME, Log4jLogEventTest.FixedTimeClock.class.getName()); + System.setProperty(ClockFactory.PROPERTY_NAME, FixedTimeClock.class.getName()); } @AfterClass diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SecureSocketAppenderSocketOptionsTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SecureSocketAppenderSocketOptionsTest.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SecureSocketAppenderSocketOptionsTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SecureSocketAppenderSocketOptionsTest.java index abee218d53d..1a24bcbe7cf 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SecureSocketAppenderSocketOptionsTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SecureSocketAppenderSocketOptionsTest.java @@ -29,11 +29,11 @@ import org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration; import org.apache.logging.log4j.core.net.ssl.SslConfiguration; import org.apache.logging.log4j.core.net.ssl.StoreConfigurationException; -import org.apache.logging.log4j.core.net.ssl.TestConstants; +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration; import org.apache.logging.log4j.core.util.NullOutputStream; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.AvailablePortFinder; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.AvailablePortFinder; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Assume; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java index 85b6b17d478..0d1b9914367 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBufferSizeTest.java @@ -21,8 +21,8 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.SocketAppenderTest.TcpSocketTestServer; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.AvailablePortFinder; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.AvailablePortFinder; import org.junit.After; import org.junit.Before; import org.junit.Rule; diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java new file mode 100644 index 00000000000..bad65d4eeea --- /dev/null +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderSocketOptionsTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.io.IOException; +import java.net.Socket; + +import org.apache.logging.log4j.core.appender.SocketAppenderTest.TcpSocketTestServer; +import org.apache.logging.log4j.core.net.AbstractSocketManager; +import org.apache.logging.log4j.core.net.Rfc1349TrafficClass; +import org.apache.logging.log4j.core.net.SocketOptions; +import org.apache.logging.log4j.core.net.TcpSocketManager; +import org.apache.logging.log4j.core.test.junit.AllocatePorts; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.NullOutputStream; +import org.apache.logging.log4j.plugins.Named; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@AllocatePorts("SocketAppenderSocketOptionsTest.port") +public class SocketAppenderSocketOptionsTest { + + private TcpSocketTestServer server; + + @BeforeEach + void setUp() throws IOException { + int port = Integer.getInteger("SocketAppenderSocketOptionsTest.port"); + server = new TcpSocketTestServer(port); + server.start(); + } + + @AfterEach + void tearDown() { + if (server != null) { + server.shutdown(); + server = null; + } + } + + @Test + @LoggerContextSource("log4j-socket-options.xml") + public void testSocketOptions(@Named("socket") final SocketAppender appender) throws IOException { + final AbstractSocketManager abstractSocketManager = appender.getManager(); + assertThat(abstractSocketManager) + .isNotNull() + .isInstanceOf(TcpSocketManager.class); + final TcpSocketManager manager = (TcpSocketManager) abstractSocketManager; + assertThat(manager.getOutputStream()) + .isNotNull() + .isInstanceOf(NullOutputStream.class); + final SocketOptions socketOptions = manager.getSocketOptions(); + assertNotNull(socketOptions); + final Socket socket = manager.getSocket(); + assertNotNull(socket); + // Test config request + assertEquals(false, socketOptions.isKeepAlive()); + assertEquals(false, socketOptions.isOobInline()); + assertEquals(false, socketOptions.isReuseAddress()); + assertEquals(false, socketOptions.isTcpNoDelay()); + assertEquals(Rfc1349TrafficClass.IPTOS_LOWCOST.value(), socketOptions.getActualTrafficClass().intValue()); + assertEquals(10000, socketOptions.getReceiveBufferSize().intValue()); + assertEquals(8000, socketOptions.getSendBufferSize().intValue()); + assertEquals(12345, socketOptions.getSoLinger().intValue()); + assertEquals(54321, socketOptions.getSoTimeout().intValue()); + // Test live socket + assertFalse(socket.getKeepAlive()); + assertFalse(socket.getOOBInline()); + assertFalse(socket.getReuseAddress()); + assertFalse(socket.getTcpNoDelay()); + // Assert.assertEquals(10000, socket.getReceiveBufferSize()); + // This settings changes while we are running, so we cannot assert it. + // Assert.assertEquals(8000, socket.getSendBufferSize()); + assertEquals(12345, socket.getSoLinger()); + assertEquals(54321, socket.getSoTimeout()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java similarity index 75% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java index 8aee640ca23..99fd2d08d76 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java @@ -16,10 +16,28 @@ */ package org.apache.logging.log4j.core.appender; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.core.test.AvailablePortFinder; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.jackson.json.Log4jJsonObjectMapper; +import org.apache.logging.log4j.jackson.json.layout.JsonLayout; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import java.io.EOFException; import java.io.IOException; @@ -34,33 +52,14 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; -import com.fasterxml.jackson.databind.MappingIterator; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LoggingException; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; -import org.apache.logging.log4j.core.layout.JsonLayout; -import org.apache.logging.log4j.core.net.Protocol; -import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.core.util.Throwables; -import org.apache.logging.log4j.test.AvailablePortFinder; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; /** * */ +@Tag("sleepy") public class SocketAppenderTest { private static final int PORT = AvailablePortFinder.getNextAvailable(); @@ -73,7 +72,7 @@ public class SocketAppenderTest { private final LoggerContext context = LoggerContext.getContext(); private final Logger logger = context.getLogger(SocketAppenderTest.class.getName()); - @BeforeClass + @BeforeAll public static void setupClass() throws Exception { tcpServer = new TcpSocketTestServer(PORT); tcpServer.start(); @@ -83,14 +82,14 @@ public static void setupClass() throws Exception { ThreadContext.clearAll(); } - @AfterClass + @AfterAll public static void cleanupClass() { tcpServer.shutdown(); udpServer.shutdown(); ThreadContext.clearAll(); } - @After + @AfterEach public void teardown() { ThreadContext.clearAll(); removeAndStopAppenders(); @@ -117,7 +116,7 @@ public void testTcpAppender1() throws Exception { } @Test - @Ignore("WIP Bug when this method runs after testTcpAppender1()") + @Disabled("WIP Bug when this method runs after testTcpAppender1()") public void testTcpAppender2() throws Exception { testTcpAppender(tcpServer, logger, Constants.ENCODER_BYTE_BUFFER_SIZE); } @@ -126,17 +125,17 @@ static void testTcpAppender(final TcpSocketTestServer tcpTestServer, final Logge throws Exception { // @formatter:off final SocketAppender appender = SocketAppender.newBuilder() - .withHost("localhost") - .withPort(tcpTestServer.getLocalPort()) - .withReconnectDelayMillis(-1) - .withName("test") - .withImmediateFail(false) - .withBufferSize(bufferSize) - .withLayout(JsonLayout.newBuilder().setProperties(true).build()) + .setHost("localhost") + .setPort(tcpTestServer.getLocalPort()) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFail(false) + .setBufferSize(bufferSize) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) .build(); // @formatter:on appender.start(); - Assert.assertEquals(bufferSize, appender.getManager().getByteBuffer().capacity()); + assertEquals(bufferSize, appender.getManager().getByteBuffer().capacity()); // set appender on root and set level to debug logger.addAppender(appender); @@ -158,14 +157,14 @@ static void testTcpAppender(final TcpSocketTestServer tcpTestServer, final Logge } Thread.sleep(250); LogEvent event = tcpTestServer.getQueue().poll(3, TimeUnit.SECONDS); - assertNotNull("No event retrieved", event); - assertTrue("Incorrect event", event.getMessage().getFormattedMessage().equals("This is a test message")); - assertTrue("Message not delivered via TCP", tcpTestServer.getCount() > 0); + assertNotNull(event, "No event retrieved"); + assertEquals("This is a test message", event.getMessage().getFormattedMessage(), "Incorrect event"); + assertTrue(tcpTestServer.getCount() > 0, "Message not delivered via TCP"); assertEquals(expectedUuidStr, event.getContextData().getValue(tcKey)); event = tcpTestServer.getQueue().poll(3, TimeUnit.SECONDS); - assertNotNull("No event retrieved", event); - assertTrue("Incorrect event", event.getMessage().getFormattedMessage().equals("Throwing an exception")); - assertTrue("Message not delivered via TCP", tcpTestServer.getCount() > 1); + assertNotNull(event, "No event retrieved"); + assertTrue(event.getMessage().getFormattedMessage().equals("Throwing an exception"), "Incorrect event"); + assertTrue(tcpTestServer.getCount() > 1, "Message not delivered via TCP"); assertEquals(expectedUuidStr, event.getContextStack().pop()); assertNotNull(event.getThrownProxy()); assertEquals(expectedExMsg, event.getThrownProxy().getMessage()); @@ -175,11 +174,11 @@ static void testTcpAppender(final TcpSocketTestServer tcpTestServer, final Logge public void testDefaultProtocol() throws Exception { // @formatter:off final SocketAppender appender = SocketAppender.newBuilder() - .withPort(tcpServer.getLocalPort()) - .withReconnectDelayMillis(-1) - .withName("test") - .withImmediateFail(false) - .withLayout(JsonLayout.newBuilder().setProperties(true).build()) + .setPort(tcpServer.getLocalPort()) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFail(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) .build(); // @formatter:on assertNotNull(appender); @@ -196,12 +195,12 @@ public void testUdpAppender() throws Exception { // @formatter:off final SocketAppender appender = SocketAppender.newBuilder() - .withProtocol(Protocol.UDP) - .withPort(tcpServer.getLocalPort()) - .withReconnectDelayMillis(-1) - .withName("test") - .withImmediateFail(false) - .withLayout(JsonLayout.newBuilder().setProperties(true).build()) + .setProtocol(Protocol.UDP) + .setPort(tcpServer.getLocalPort()) + .setReconnectDelayMillis(-1) + .setName("test") + .setImmediateFail(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) .build(); // @formatter:on appender.start(); @@ -212,9 +211,9 @@ public void testUdpAppender() throws Exception { logger.setLevel(Level.DEBUG); logger.debug("This is a udp message"); final LogEvent event = udpServer.getQueue().poll(3, TimeUnit.SECONDS); - assertNotNull("No event retrieved", event); - assertTrue("Incorrect event", event.getMessage().getFormattedMessage().equals("This is a udp message")); - assertTrue("Message not delivered via UDP", udpServer.getCount() > 0); + assertNotNull(event, "No event retrieved"); + assertTrue(event.getMessage().getFormattedMessage().equals("This is a udp message"), "Incorrect event"); + assertTrue(udpServer.getCount() > 0, "Message not delivered via UDP"); } @Test @@ -222,12 +221,12 @@ public void testTcpAppenderDeadlock() throws Exception { // @formatter:off final SocketAppender appender = SocketAppender.newBuilder() - .withHost("localhost") - .withPort(DYN_PORT) - .withReconnectDelayMillis(100) - .withName("test") - .withImmediateFail(false) - .withLayout(JsonLayout.newBuilder().setProperties(true).build()) + .setHost("localhost") + .setPort(DYN_PORT) + .setReconnectDelayMillis(100) + .setName("test") + .setImmediateFail(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) .build(); // @formatter:on appender.start(); @@ -243,7 +242,7 @@ public void testTcpAppenderDeadlock() throws Exception { logger.debug("This message is written because a deadlock never."); final LogEvent event = tcpSocketServer.getQueue().poll(3, TimeUnit.SECONDS); - assertNotNull("No event retrieved", event); + assertNotNull(event, "No event retrieved"); } finally { tcpSocketServer.shutdown(); } @@ -253,13 +252,13 @@ public void testTcpAppenderDeadlock() throws Exception { public void testTcpAppenderNoWait() throws Exception { // @formatter:off final SocketAppender appender = SocketAppender.newBuilder() - .withHost("localhost") - .withPort(ERROR_PORT) - .withReconnectDelayMillis(100) - .withName("test") - .withImmediateFail(false) - .withIgnoreExceptions(false) - .withLayout(JsonLayout.newBuilder().setProperties(true).build()) + .setHost("localhost") + .setPort(ERROR_PORT) + .setReconnectDelayMillis(100) + .setName("test") + .setImmediateFail(false) + .setIgnoreExceptions(false) + .setLayout(JsonLayout.newBuilder().setProperties(true).build()) .build(); // @formatter:on appender.start(); @@ -268,14 +267,7 @@ public void testTcpAppenderNoWait() throws Exception { logger.setAdditive(false); logger.setLevel(Level.DEBUG); - try { - logger.debug("This message is written because a deadlock never."); - fail("No Exception was thrown"); - } catch (final Exception ex) { - // TODO: move exception to @Test(expect = Exception.class) - // Failure is expected. - // ex.printStackTrace(); - } + assertThrows(Exception.class, () -> logger.debug("This message is written because a deadlock never.")); } public static class UdpSocketTestServer extends Thread { @@ -284,7 +276,7 @@ public static class UdpSocketTestServer extends Thread { private boolean shutdown = false; private Thread thread; private final CountDownLatch latch = new CountDownLatch(1); - private volatile int count = 0; + private final AtomicInteger count = new AtomicInteger(); private final BlockingQueue queue; private final ObjectMapper objectMapper = new Log4jJsonObjectMapper(); @@ -295,7 +287,7 @@ public UdpSocketTestServer() throws IOException { public void reset() { queue.clear(); - count = 0; + count.set(0); } public void shutdown() { @@ -317,7 +309,7 @@ public void run() { while (!shutdown) { latch.countDown(); sock.receive(packet); - ++count; + count.incrementAndGet(); final LogEvent event = objectMapper.readValue(packet.getData(), Log4jLogEvent.class); queue.add(event); } @@ -330,7 +322,7 @@ public void run() { } public int getCount() { - return count; + return count.get(); } public BlockingQueue getQueue() { @@ -342,7 +334,7 @@ public static class TcpSocketTestServer extends Thread { private final ServerSocket serverSocket; private volatile boolean shutdown = false; - private volatile int count = 0; + private final AtomicInteger count = new AtomicInteger(); private final BlockingQueue queue; private final ObjectMapper objectMapper = new Log4jJsonObjectMapper(); @@ -362,7 +354,7 @@ public int getLocalPort() { public void reset() { queue.clear(); - count = 0; + count.set(0); } public void shutdown() { @@ -385,7 +377,7 @@ public void run() { final MappingIterator mappingIterator = objectMapper.readerFor(Log4jLogEvent.class).readValues(is); while (mappingIterator.hasNextValue()) { queue.add(mappingIterator.nextValue()); - ++count; + count.incrementAndGet(); } } } @@ -404,7 +396,7 @@ public BlockingQueue getQueue() { } public int getCount() { - return count; + return count.get(); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java index c59ad9d13ae..c0ac2439218 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderTest.java @@ -16,21 +16,22 @@ */ package org.apache.logging.log4j.core.appender; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.junit.Assert.*; - /** * Tests a "compact" XML file, no extra spaces or end of lines. */ diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderValidationTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderValidationTest.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderValidationTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderValidationTest.java index 9b4da7963be..a66e45eebeb 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderValidationTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAppenderValidationTest.java @@ -29,7 +29,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.core.test.categories.Layouts; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; import org.junit.After; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAsyncAppenderValidationTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAsyncAppenderValidationTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAsyncAppenderValidationTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAsyncAppenderValidationTest.java index 1e048b1158d..bfaf05445fb 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAsyncAppenderValidationTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompactFileAsyncAppenderValidationTest.java @@ -29,8 +29,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.junit.BeforeClass; import org.junit.Ignore; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java index c77b464723c..b8c888500f2 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlCompleteFileAppenderTest.java @@ -16,21 +16,24 @@ */ package org.apache.logging.log4j.core.appender; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.nio.charset.Charset; import java.nio.file.Files; -import java.nio.file.Paths; import java.util.List; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.core.selector.CoreContextSelectors; -import org.apache.logging.log4j.junit.CleanFiles; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.test.junit.CleanFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -39,8 +42,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import static org.junit.Assert.*; - /** * Tests a "complete" XML file a.k.a. a well-formed XML file. */ diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java index f9cdff382eb..3b7dc3052f7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlFileAppenderTest.java @@ -16,24 +16,22 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.BufferedReader; +import static org.junit.Assert.assertTrue; + import java.io.File; -import java.io.FileReader; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.junit.Assert.*; - /** * Tests a "complete" XML file a.k.a. a well-formed XML file. */ diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlRandomAccessFileAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlRandomAccessFileAppenderTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlRandomAccessFileAppenderTest.java rename to log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlRandomAccessFileAppenderTest.java index a907c7fe496..b4ebe406560 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/XmlRandomAccessFileAppenderTest.java +++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/XmlRandomAccessFileAppenderTest.java @@ -16,22 +16,23 @@ */ package org.apache.logging.log4j.core.appender; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.Layouts; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; -import static org.junit.Assert.*; - /** * Tests a "complete" XML file a.k.a. a well-formed XML file. */ diff --git a/log4j-core-java9/pom.xml b/log4j-core-java9/pom.xml deleted file mode 100644 index b46f8c91044..00000000000 --- a/log4j-core-java9/pom.xml +++ /dev/null @@ -1,152 +0,0 @@ - - - - 4.0.0 - - org.apache.logging.log4j - log4j - 2.10.1-SNAPSHOT - ../ - - log4j-core-java9 - pom - Apache Log4j Implementation Java 9 support - The Apache Log4j Implementation (Java 9) - - ${basedir}/.. - Log4j Implementation Documentation - /core - - - - - org.apache.logging.log4j - log4j-api - - - junit - junit - test - - - org.apache.maven - maven-core - test - - - - - - org.apache.maven.plugins - maven-toolchains-plugin - 1.1 - - - - toolchain - - - - - - - 9 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - default-compile - compile - - compile - - - - default-test-compile - test-compile - - testCompile - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - 2.13 - - - test - test - - test - - - - - - true - - 2C - true - - **/Test*.java - **/*Test.java - - - **/*FuncTest.java - - - - - maven-assembly-plugin - - - zip - package - - single - - - log4j-core-java9-${project.version} - false - - src/assembly/java9.xml - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - ${deploy.plugin.version} - - true - - - - - - diff --git a/log4j-core-java9/src/assembly/java9.xml b/log4j-core-java9/src/assembly/java9.xml deleted file mode 100644 index f8f129dc0d0..00000000000 --- a/log4j-core-java9/src/assembly/java9.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - src - - zip - - / - - - ${project.build.outputDirectory} - /classes/META-INF/versions/9 - - **/*.class - - - module-info.class - **/Dummy.class - **/util/Clock.class - **/util/Instant.class - **/util/MutableInstant.class - **/util/PreciseClock.class - - - - ${project.build.outputDirectory} - /classes - - module-info.class - - - - diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Clock.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Clock.java deleted file mode 100644 index 8c961d48e92..00000000000 --- a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Clock.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.util; - -// This class is here to allow {@link SystemClock}, {@link SystemMillisClock} -// to compile. It will not be copied into the log4j-core module. - -/** - * Provides the time stamp used in log events. - */ -public interface Clock { - /** - * Returns the time in milliseconds since the epoch. - * - * @return the time in milliseconds since the epoch - */ - long currentTimeMillis(); -} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Instant.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Instant.java deleted file mode 100644 index cd4ac9db467..00000000000 --- a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/Instant.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.util; - -// This class is here to allow {@link SystemClock}, {@link SystemMillisClock} -// to compile. It will not be copied into the log4j-core module. - -/** - * Models a point in time, suitable for event timestamps. - *

- * Provides methods for obtaining high precision time information similar to the - * Instant class introduced in Java 8, - * while also supporting the legacy millisecond precision API. - *

- * Depending on the platform, time sources ({@link Clock} implementations) may produce high precision or millisecond - * precision time values. At the same time, some time value consumers (for example timestamp formatters) may only be - * able to consume time values of millisecond precision, while some others may require a high precision time value. - *

- * This class bridges these two time APIs. - *

- * @since 2.11 - */ -public interface Instant { - /** - * Gets the number of seconds from the Java epoch of 1970-01-01T00:00:00Z. - *

- * The epoch second count is a simple incrementing count of seconds where second 0 is 1970-01-01T00:00:00Z. - * The nanosecond part of the day is returned by {@link #getNanoOfSecond()}. - *

- * @return the seconds from the epoch of 1970-01-01T00:00:00Z - */ - long getEpochSecond(); - - /** - * Gets the number of nanoseconds, later along the time-line, from the start of the second. - *

- * The nanosecond-of-second value measures the total number of nanoseconds from the second returned by {@link #getEpochSecond()}. - *

- * @return the nanoseconds within the second, always positive, never exceeds {@code 999,999,999} - */ - int getNanoOfSecond(); - - /** - * Gets the number of milliseconds from the Java epoch of 1970-01-01T00:00:00Z. - *

- * The epoch millisecond count is a simple incrementing count of milliseconds where millisecond 0 is 1970-01-01T00:00:00Z. - * The nanosecond part of the day is returned by {@link #getNanoOfMillisecond()}. - *

- * @return the milliseconds from the epoch of 1970-01-01T00:00:00Z - */ - long getEpochMillisecond(); - - /** - * Gets the number of nanoseconds, later along the time-line, from the start of the millisecond. - *

- * The nanosecond-of-millisecond value measures the total number of nanoseconds from the millisecond returned by {@link #getEpochMillisecond()}. - *

- * @return the nanoseconds within the millisecond, always positive, never exceeds {@code 999,999} - */ - int getNanoOfMillisecond(); -} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/MutableInstant.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/MutableInstant.java deleted file mode 100644 index 8960fc53f53..00000000000 --- a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/MutableInstant.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.util; - -import org.apache.logging.log4j.util.PerformanceSensitive; - -import java.io.Serializable; - -// This class is here to allow {@link SystemClock}, {@link SystemMillisClock} -// to compile. It will not be copied into the log4j-core module. - -/** - * An instantaneous point on the time line, used for high-precision log event timestamps. - * Modelled on java.time.Instant, - * except that this version is mutable to prevent allocating temporary objects that need to be garbage-collected later. - *

- * Instances of this class are not thread-safe and should not be shared between threads. - *

- * - * @since 2.11 - */ -@PerformanceSensitive("allocation") -public class MutableInstant implements Instant, Serializable { - - private static final int MILLIS_PER_SECOND = 1000; - private static final int NANOS_PER_MILLI = 1000_000; - static final int NANOS_PER_SECOND = MILLIS_PER_SECOND * NANOS_PER_MILLI; - - private long epochSecond; - private int nanoOfSecond; - - @Override - public long getEpochSecond() { - return epochSecond; - } - - @Override - public int getNanoOfSecond() { - return nanoOfSecond; - } - - @Override - public long getEpochMillisecond() { - final int millis = nanoOfSecond / NANOS_PER_MILLI; - long epochMillisecond = epochSecond * MILLIS_PER_SECOND + millis; - return epochMillisecond; - } - - @Override - public int getNanoOfMillisecond() { - final int millis = nanoOfSecond / NANOS_PER_MILLI; - int nanoOfMillisecond = nanoOfSecond - (millis * NANOS_PER_MILLI); // cheaper than nanoOfSecond % NANOS_PER_MILLI - return nanoOfMillisecond; - } - - public void initFrom(final Instant other) { - this.epochSecond = other.getEpochSecond(); - this.nanoOfSecond = other.getNanoOfSecond(); - } - - /** - * Updates the fields of this {@code MutableInstant} from the specified epoch millis. - * @param epochMilli the number of milliseconds from the Java epoch of 1970-01-01T00:00:00Z - * @param nanoOfMillisecond the number of nanoseconds, later along the time-line, from the start of the millisecond - */ - public void initFromEpochMilli(final long epochMilli, final int nanoOfMillisecond) { - validateNanoOfMillisecond(nanoOfMillisecond); - this.epochSecond = epochMilli / MILLIS_PER_SECOND; - this.nanoOfSecond = (int) (epochMilli - (epochSecond * MILLIS_PER_SECOND)) * NANOS_PER_MILLI + nanoOfMillisecond; - } - - private void validateNanoOfMillisecond(final int nanoOfMillisecond) { - if (nanoOfMillisecond < 0 || nanoOfMillisecond >= NANOS_PER_MILLI) { - throw new IllegalArgumentException("Invalid nanoOfMillisecond " + nanoOfMillisecond); - } - } - - public void initFrom(final Clock clock) { - if (clock instanceof PreciseClock) { - ((PreciseClock) clock).init(this); - } else { - initFromEpochMilli(clock.currentTimeMillis(), 0); - } - } - - /** - * Updates the fields of this {@code MutableInstant} from the specified instant components. - * @param epochSecond the number of seconds from the Java epoch of 1970-01-01T00:00:00Z - * @param nano the number of nanoseconds, later along the time-line, from the start of the second - */ - public void initFromEpochSecond(final long epochSecond, final int nano) { - validateNanoOfSecond(nano); - this.epochSecond = epochSecond; - this.nanoOfSecond = nano; - } - - private void validateNanoOfSecond(final int nano) { - if (nano < 0 || nano >= NANOS_PER_SECOND) { - throw new IllegalArgumentException("Invalid nanoOfSecond " + nano); - } - } - - /** - * Updates the elements of the specified {@code long[]} result array from the specified instant components. - * @param epochSecond (input) the number of seconds from the Java epoch of 1970-01-01T00:00:00Z - * @param nano (input) the number of nanoseconds, later along the time-line, from the start of the second - * @param result (output) a two-element array to store the result: the first element is the number of milliseconds - * from the Java epoch of 1970-01-01T00:00:00Z, - * the second element is the number of nanoseconds, later along the time-line, from the start of the millisecond - */ - public static void instantToMillisAndNanos(final long epochSecond, final int nano, final long[] result) { - int millis = nano / NANOS_PER_MILLI; - result[0] = epochSecond * MILLIS_PER_SECOND + millis; - result[1] = nano - (millis * NANOS_PER_MILLI); // cheaper than nanoOfSecond % NANOS_PER_MILLI - } - - @Override - public boolean equals(final Object object) { - if (object == this) { - return true; - } - if (!(object instanceof MutableInstant)) { - return false; - } - MutableInstant other = (MutableInstant) object; - return epochSecond == other.epochSecond && nanoOfSecond == other.nanoOfSecond; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + (int) (epochSecond ^ (epochSecond >>> 32)); - result = 31 * result + nanoOfSecond; - return result; - } - - @Override - public String toString() { - return "MutableInstant[epochSecond=" + epochSecond + ", nano=" + nanoOfSecond + "]"; - } -} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/PreciseClock.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/PreciseClock.java deleted file mode 100644 index 3d1f314ee60..00000000000 --- a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/PreciseClock.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.util; - -// This class is here to allow {@link SystemClock}, {@link SystemMillisClock} -// to compile. It will not be copied into the log4j-core module. - -/** - * Extension of the {@link Clock} interface that is able to provide more accurate time information than milliseconds - * since the epoch. {@code PreciseClock} implementations are free to return millisecond-precision time - * if that is the most accurate time information available on this platform. - * @since 2.11 - */ -public interface PreciseClock extends Clock { - - /** - * Initializes the specified instant with time information as accurate as available on this platform. - * @param mutableInstant the container to be initialized with the accurate time information - * @since 2.11 - */ - void init(final MutableInstant mutableInstant); -} diff --git a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/SystemClock.java b/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/SystemClock.java deleted file mode 100644 index a74a1fde110..00000000000 --- a/log4j-core-java9/src/main/java/org/apache/logging/log4j/core/util/SystemClock.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.util; - -import java.time.Instant; - -/** - * Implementation of the {@code Clock} interface that returns the system time. - * @since 2.11 - */ -public final class SystemClock implements Clock, PreciseClock { - - /** - * Returns the system time. - * @return the result of calling {@code System.currentTimeMillis()} - */ - @Override - public long currentTimeMillis() { - return System.currentTimeMillis(); - } - - /** - * {@inheritDoc} - */ - @Override - public void init(MutableInstant mutableInstant) { - Instant instant = java.time.Clock.systemUTC().instant(); - mutableInstant.initFromEpochSecond(instant.getEpochSecond(), instant.getNano()); - } -} diff --git a/log4j-core-java9/src/test/java/org/apache/logging/log4j/core/util/Dummy.java b/log4j-core-java9/src/test/java/org/apache/logging/log4j/core/util/Dummy.java deleted file mode 100644 index 6bffac68c33..00000000000 --- a/log4j-core-java9/src/test/java/org/apache/logging/log4j/core/util/Dummy.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.util; - -/** - * This is a dummy class and is only here to allow module-info.java to compile. It will not - * be copied into the log4j-api module. - */ -public class Dummy { -} diff --git a/log4j-core-test/README.md b/log4j-core-test/README.md new file mode 100644 index 00000000000..03f5046feb1 --- /dev/null +++ b/log4j-core-test/README.md @@ -0,0 +1,37 @@ +# Building Log4j Core + +Log4j 2 supports the Java Platform Module System. JPMS has requirements that conflict with several things +Log4j also tries to support: +1. OSGi - OSGi frameworks are not packaged as OSGi modules. Including an OSGi implementation will cause +compiler errors while resolving the JPMS module inforation. +2. Garbage Free - The Google tool Log4j uses to verify that Log4j core is garbage free violates JPMS rules. The test +compilations fail when it is included as a dependency. +3. Compiler bugs - When compiling with module-info.java included the classes in the appender, layout, and filter +directories get "duplicate class" errors. For some reason these directory names are being interpreted as starting +with upper case letters even though they are not. For some reason the compiler is showing an error +that the class cannot be found even though it is being generated. See + [JDK-8265826](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8265826). +4. Test classes that are used by other modules - Several test classes are used by other log4j modules and need +to be passed to them. This requires these classes exist in a package that is not used in log4j-core. +5. Test classes used by log4j-core must use the same package space to be able to access some methods in the classes +being tested. +6. Once Java has compiled the main module with a module-info.java all test compiles also require one. Likewise, +a test compile with a module-info.java is not allowed if the main compile doesn't have one. + +For these reasons the build will need to be processed as follows: +1. Move the Garbage Free tests to their own module. This will require copying all the test resources. +1. Compile all the main classes except module-info.java with the Plugin preprocessor. +1. Compile the main module-info.java. +1. Compile the test classes used by other modules with module-info.java and with the plugin preprocessor. +1. Package these test classes in a test jar. +1. Delete the module-info and generated source for the test classes. +1. Move the main module-info to a temp location. +1. Compile the unit test classes without module-info.java. +1. Move the main module-info back to the classes directory. +1. Compile module-info.java for unit tests. +1. Run the unit tests. +1. Create the main jar if the unit tests pass. + +Once the JDK bug is fixed this process can be simplified quite a bit since the components will all be able to be +compiled once with the module-info.java file. + \ No newline at end of file diff --git a/log4j-core-test/pom.xml b/log4j-core-test/pom.xml new file mode 100644 index 00000000000..7284206f5c1 --- /dev/null +++ b/log4j-core-test/pom.xml @@ -0,0 +1,524 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j + 3.0.0-SNAPSHOT + + log4j-core-test + jar + Apache Log4j Core Tests + The Apache Log4j Implementation Tests + + ${basedir}/.. + Core Documentation + /log4j-core-test + true + + + + + org.osgi + org.osgi.framework + provided + + + org.osgi + org.osgi.resource + provided + + + + org.apache.logging.log4j + log4j-api + + + + + org.apache.logging.log4j + log4j-api-test + + + org.apache.logging.log4j + log4j-core + + + org.apache.logging.log4j + log4j-plugins + + + + org.apache.logging.log4j + log4j-plugins-test + + + org.assertj + assertj-core + + + + org.apache.commons + commons-compress + true + + + + commons-logging + commons-logging + + + + com.conversantmedia + disruptor + true + + + + com.lmax + disruptor + true + + + org.hamcrest + hamcrest + + + + com.fasterxml.jackson.core + jackson-core + true + + + + com.fasterxml.jackson.core + jackson-databind + true + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + true + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + true + + + + org.fusesource.jansi + jansi + true + + + + javax.activation + javax.activation-api + true + + + + javax.xml.bind + jaxb-api + true + + + + org.jctools + jctools-core + true + + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.jupiter + junit-jupiter-migrationsupport + + + org.junit.jupiter + junit-jupiter-params + + + org.junit-pioneer + junit-pioneer + true + + + + org.junit.vintage + junit-vintage-engine + + + + com.fasterxml.woodstox + woodstox-core + true + + + org.awaitility + awaitility + test + + + + commons-codec + commons-codec + test + + + commons-io + commons-io + test + + + org.apache.commons + commons-lang3 + test + + + com.h2database + h2 + test + + + org.hdrhistogram + HdrHistogram + test + + + + org.hsqldb + hsqldb + test + + + + org.jmdns + jmdns + test + + + + net.javacrumbs.json-unit + json-unit + test + + + + log4j + log4j + test + + + com.github.ivandzf + log4j2-custom-layout + test + + + + com.vlkan.log4j2 + log4j2-logstash-layout + test + + + ch.qos.logback + logback-classic + test + + + + ch.qos.logback + logback-core + test + + + + org.mockito + mockito-core + test + + + org.mockito + mockito-junit-jupiter + test + + + + org.slf4j + slf4j-api + test + + + + org.springframework + spring-test + test + + + org.graalvm.truffle + truffle-api + test + + + + com.github.tomakehurst + wiremock-jre8 + test + + + org.xmlunit + xmlunit-core + test + + + org.xmlunit + xmlunit-matchers + test + + + + org.tukaani + xz + test + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + add-source + + + + ${project.basedir}/src/main/module + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.apache.logging.log4j.core + + org.apache.logging.log4j.core.* + + sun.reflect;resolution:=optional, + org.apache.logging.log4j.util, + org.apache.logging.log4j.plugins, + org.apache.logging.log4j.plugins.convert, + org.apache.logging.log4j.plugins.processor, + org.apache.logging.log4j.plugins.util, + org.apache.logging.log4j.plugins.validation, + org.apache.logging.log4j.plugins.inject, + * + + org.apache.logging.log4j.core.osgi.Activator + + + + + + org.apache.maven.plugins + maven-clean-plugin + + + remove-module-info + process-classes + + clean + + + true + + + ${project.build.outputDirectory} + + module-info.class + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + default-testCompile + + + -ApluginPackage=org.apache.logging.log4j.core.junit + + + + + compile-module-info + + compile + + prepare-package + + + ${project.basedir}/src/main/module + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + true + + + + org.apache.maven.plugins + maven-surefire-plugin + + random + + false + + false + + + + + + + + org.apache.maven.plugins + maven-changes-plugin + + + + changes-report + + + + + %URL%/%ISSUE% + true + Appenders, Configuration, Configurators, Core, Filters, Layouts, Lookups, Pattern Converters, Reconfiguration + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + ${log4jParentDir}/checkstyle.xml + ${log4jParentDir}/checkstyle-suppressions.xml + false + basedir=${basedir} + licensedir=${log4jParentDir}/checkstyle-header.txt + + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + 8 + <p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> + Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, + and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p> + + ${javadoc.opts} + false + true + + http://docs.oracle.com/javaee/6/api/ + http://www.osgi.org/javadoc/r4v43/core/ + https://commons.apache.org/proper/commons-lang/javadocs/api-release/ + + + + + non-aggregate + + javadoc + + + + + + org.apache.maven.plugins + maven-jxr-plugin + + + non-aggregate + + jxr + + + + aggregate + + aggregate + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + ${maven.compiler.target} + + + + com.github.spotbugs + spotbugs-maven-plugin + + + + + + fast-test + + + + maven-surefire-plugin + + sleepy + + + + + + + diff --git a/log4j-core-test/revapi.json b/log4j-core-test/revapi.json new file mode 100644 index 00000000000..0d4f101c7a3 --- /dev/null +++ b/log4j-core-test/revapi.json @@ -0,0 +1,2 @@ +[ +] diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/AvailablePortFinder.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/AvailablePortFinder.java similarity index 99% rename from log4j-core/src/test/java/org/apache/logging/log4j/test/AvailablePortFinder.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/AvailablePortFinder.java index 14122c58efd..9cac7ac3a45 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/test/AvailablePortFinder.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/AvailablePortFinder.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.test; +package org.apache.logging.log4j.core.test; import java.io.IOException; import java.net.DatagramSocket; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/AvailablePortSystemPropertyTestRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/AvailablePortSystemPropertyTestRule.java new file mode 100644 index 00000000000..1448f53dfb2 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/AvailablePortSystemPropertyTestRule.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test; + +/** + * A JUnit TestRule to discover an available port and save it in a system property. Useful for setting up tests using + * Apache Active MQ. + */ +public class AvailablePortSystemPropertyTestRule extends SystemPropertyTestRule { + + public static AvailablePortSystemPropertyTestRule create(final String name) { + return new AvailablePortSystemPropertyTestRule(name); + } + + protected AvailablePortSystemPropertyTestRule(final String name) { + super(name, () -> Integer.toString(AvailablePortFinder.getNextAvailable())); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/BasicConfigurationFactory.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/BasicConfigurationFactory.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/BasicConfigurationFactory.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/BasicConfigurationFactory.java index d0289214948..4297dca4932 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/BasicConfigurationFactory.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/BasicConfigurationFactory.java @@ -14,11 +14,12 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core; +package org.apache.logging.log4j.core.test; import java.net.URI; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; @@ -45,7 +46,7 @@ public Configuration getConfiguration(final LoggerContext loggerContext, final C return null; } - public class BasicConfiguration extends AbstractConfiguration { + public static class BasicConfiguration extends AbstractConfiguration { private static final String DEFAULT_LEVEL = "org.apache.logging.log4j.level"; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CoreLoggerContexts.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/CoreLoggerContexts.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/CoreLoggerContexts.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/CoreLoggerContexts.java index ca2a0abce9b..a44fa9d6520 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CoreLoggerContexts.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/CoreLoggerContexts.java @@ -14,12 +14,12 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - -package org.apache.logging.log4j.core; +package org.apache.logging.log4j.core.test; import java.io.File; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LifeCycle; public class CoreLoggerContexts { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/ExtendedLevels.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ExtendedLevels.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/test/ExtendedLevels.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ExtendedLevels.java index d69433e4fdd..3f930b912ca 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/test/ExtendedLevels.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/ExtendedLevels.java @@ -14,15 +14,17 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.test; +package org.apache.logging.log4j.core.test; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; /** * */ -@Plugin(name="ExtendedLevel", category=Level.CATEGORY) +@Namespace(Level.CATEGORY) +@Plugin("ExtendedLevel") public class ExtendedLevels { public static final Level NOTE = Level.forName("NOTE", 350); diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/GetLogger.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/GetLogger.java new file mode 100644 index 00000000000..8fc3d811964 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/GetLogger.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; + +/** + * Used to profile obtaining a Logger + */ +public class GetLogger { + + public static void main(String[] args) { + int count = Integer.parseInt(args[0]); + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + for (int i = 0; i < count; ++i) { + Logger logger = LogManager.getLogger("Logger" + i); + logger.debug("This is a test"); + } + System.out.println("Number of Loggers: " + loggerContext.getLoggers().size()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/RuleChainFactory.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/RuleChainFactory.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/test/RuleChainFactory.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/RuleChainFactory.java index 30629b2a44e..803872ae906 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/test/RuleChainFactory.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/RuleChainFactory.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.test; +package org.apache.logging.log4j.core.test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/SystemPropertyTestRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/SystemPropertyTestRule.java new file mode 100644 index 00000000000..e1de101840e --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/SystemPropertyTestRule.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test; + +import java.util.Objects; +import java.util.function.Supplier; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * A JUnit TestRule to set and reset a system property during a test. + */ +public class SystemPropertyTestRule implements TestRule { + + public static SystemPropertyTestRule create(final String name, final String value) { + return new SystemPropertyTestRule(name, value); + } + + private final String name; + private final Supplier valueSupplier; + private String value; + + protected SystemPropertyTestRule(final String name, final String value) { + this(name, () -> value); + } + + protected SystemPropertyTestRule(final String name, final Supplier value) { + this.name = Objects.requireNonNull(name, "name"); + this.valueSupplier = value; + } + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + + @Override + public void evaluate() throws Throwable { + final String oldValue = System.getProperty(name); + try { + value = valueSupplier.get(); + System.setProperty(name, value); + base.evaluate(); + } finally { + // Restore if previously set + if (oldValue != null) { + System.setProperty(name, oldValue); + } + } + } + }; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public Supplier getValueSupplier() { + return valueSupplier; + } + + @Override + public String toString() { + // Value might be a secret... + return "SystemPropertyTestRule [name=" + name + "]"; + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/TestMarkers.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestMarkers.java similarity index 90% rename from log4j-core/src/test/java/org/apache/logging/log4j/TestMarkers.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestMarkers.java index 29ba32c3146..d9957bc94a7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/TestMarkers.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/TestMarkers.java @@ -15,7 +15,10 @@ * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.core.test; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; /** * Markers useful in tests. diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/AlwaysFailAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/AlwaysFailAppender.java new file mode 100644 index 00000000000..a2c1b77b601 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/AlwaysFailAppender.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.appender; + +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("AlwaysFail") +public class AlwaysFailAppender extends AbstractAppender { + + private AlwaysFailAppender(final String name) { + super(name, null, null, false, null); + } + + @Override + public void append(final LogEvent event) { + throw new LoggingException("Always fail"); + } + + @PluginFactory + public static AlwaysFailAppender createAppender( + @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name) { + return new AlwaysFailAppender(name); + } + +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/BlockingAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/BlockingAppender.java new file mode 100644 index 00000000000..75b7bae2340 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/BlockingAppender.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.appender; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.util.concurrent.TimeUnit; + +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Block") +public class BlockingAppender extends AbstractAppender { + public volatile boolean running = true; + + private BlockingAppender(final String name) { + super(name, null, null, false, null); + } + + @Override + public void append(final LogEvent event) { + while (running) { + try { + Thread.sleep(10L); + } catch (final InterruptedException e) { + running = false; // LOG4J2-1422 cooperate with signal to get us unstuck + Thread.currentThread().interrupt(); // set interrupt status + } + } + } + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + super.stop(timeout, timeUnit, false); + running = false; + setStopped(); + return true; + } + + @PluginFactory + public static BlockingAppender createAppender( + @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name) { + return new BlockingAppender(name); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/EncodingListAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/EncodingListAppender.java new file mode 100644 index 00000000000..4910494856d --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/EncodingListAppender.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.appender; + +import java.io.Serializable; +import java.nio.ByteBuffer; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.layout.ByteBufferDestination; + +/** + * This appender is primarily used for testing. Use in a real environment is discouraged as the List could eventually + * grow to cause an OutOfMemoryError. + * + * This appender will use {@link Layout#encode(Object, ByteBufferDestination)} (and not + * {@link Layout#toByteArray(LogEvent)}). + */ +public class EncodingListAppender extends ListAppender { + + public EncodingListAppender(final String name) { + super(name); + } + + public EncodingListAppender(final String name, final Filter filter, final Layout layout, + final boolean newline, final boolean raw) { + super(name, filter, layout, newline, raw); + } + + private static class Destination implements ByteBufferDestination { + // JUnit 5 stack traces can start to get looooong + ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[8192 * 2]); + + @Override + public ByteBuffer getByteBuffer() { + return byteBuffer; + } + + @Override + public ByteBuffer drain(final ByteBuffer buf) { + throw new IllegalStateException("Unexpected message larger than 16 KB"); + } + + @Override + public void writeBytes(final ByteBuffer data) { + byteBuffer.put(data); + } + + @Override + public void writeBytes(final byte[] data, final int offset, final int length) { + byteBuffer.put(data, offset, length); + } + } + + @Override + public synchronized void append(final LogEvent event) { + final Layout layout = getLayout(); + if (layout == null) { + events.add(event); + } else { + final Destination content = new Destination(); + layout.encode(event, content); + content.getByteBuffer().flip(); + final byte[] record = new byte[content.getByteBuffer().remaining()]; + content.getByteBuffer().get(record); + write(record); + } + } + +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/FailOnceAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/FailOnceAppender.java new file mode 100644 index 00000000000..206afbc122a --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/FailOnceAppender.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.appender; + +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * An {@link Appender} that fails on the first use and works for the rest. + */ +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("FailOnce") +public class FailOnceAppender extends AbstractAppender { + + private final Supplier throwableSupplier; + + private boolean failed = false; + + private List events = new ArrayList<>(); + + private FailOnceAppender(final String name, final Supplier throwableSupplier) { + super(name, null, null, false, Property.EMPTY_ARRAY); + this.throwableSupplier = throwableSupplier; + } + + @Override + public synchronized void append(final LogEvent event) { + if (!failed) { + failed = true; + Throwable throwable = throwableSupplier.get(); + Throwables.rethrow(throwable); + } + events.add(event); + } + + public synchronized boolean isFailed() { + return failed; + } + + /** + * Returns the list of accumulated events and resets the internal buffer. + */ + public synchronized List drainEvents() { + final List oldEvents = events; + this.events = new ArrayList<>(); + return oldEvents; + } + + @PluginFactory + public static FailOnceAppender createAppender( + @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name, + @PluginAttribute("throwableClassName") final String throwableClassName) { + final Supplier throwableSupplier = createThrowableSupplier(name, throwableClassName); + return new FailOnceAppender(name, throwableSupplier); + } + + private static Supplier createThrowableSupplier( + final String name, + final String throwableClassName) { + + // Fallback to LoggingException if none is given. + final String message = String.format("failing on purpose for appender '%s'", name); + if (throwableClassName == null || ThrowableClassName.LOGGING_EXCEPTION.equals(throwableClassName)) { + return () -> new LoggingException(message); + } + + // Check against the expected exception classes. + switch (throwableClassName) { + case ThrowableClassName.RUNTIME_EXCEPTION: return () -> new RuntimeException(message); + case ThrowableClassName.EXCEPTION: return () -> new Exception(message); + case ThrowableClassName.ERROR: return () -> new Error(message); + case ThrowableClassName.THROWABLE: return () -> new Throwable(message); + case ThrowableClassName.THREAD_DEATH: return () -> { + stopCurrentThread(); + throw new IllegalStateException("should not have reached here"); + }; + default: throw new IllegalArgumentException("unknown throwable class name: " + throwableClassName); + } + + } + + @SuppressWarnings("deprecation") + private static void stopCurrentThread() { + Thread.currentThread().stop(); + } + + public enum ThrowableClassName {; + + public static final String RUNTIME_EXCEPTION = "RuntimeException"; + + public static final String LOGGING_EXCEPTION = "LoggingException"; + + public static final String EXCEPTION = "Exception"; + + public static final String ERROR = "Error"; + + public static final String THROWABLE = "Throwable"; + + public static final String THREAD_DEATH = "ThreadDeath"; + + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/InMemoryAppender.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/InMemoryAppender.java index 07ac7fe284d..532a6b3d464 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/InMemoryAppender.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.test.appender; +package org.apache.logging.log4j.core.test.appender; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -23,6 +23,7 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; import org.apache.logging.log4j.core.appender.OutputStreamManager; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.filter.CompositeFilter; /** @@ -31,8 +32,8 @@ public class InMemoryAppender extends AbstractOutputStreamAppender { public InMemoryAppender(final String name, final Layout layout, final CompositeFilter filters, - final boolean ignoreExceptions, final boolean writeHeader) { - super(name, layout, filters, ignoreExceptions, true, new InMemoryManager(name, layout, writeHeader)); + final boolean ignoreExceptions, final boolean writeHeader, Property[] properties) { + super(name, layout, filters, ignoreExceptions, true, properties, new InMemoryManager(name, layout, writeHeader)); } @Override diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java new file mode 100644 index 00000000000..aac8bbc912d --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java @@ -0,0 +1,291 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.appender; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.impl.MutableLogEvent; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * This appender is primarily used for testing. Use in a real environment is discouraged as the List could eventually + * grow to cause an OutOfMemoryError. + * + * This appender is not thread-safe. + * + * This appender will use {@link Layout#toByteArray(LogEvent)}. + * + * @see LoggerContextRule#getListAppender(String) ILC.getListAppender + */ +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("List") +public class ListAppender extends AbstractAppender { + + // Use Collections.synchronizedList rather than CopyOnWriteArrayList because we expect + // more frequent writes than reads. + final List events = Collections.synchronizedList(new ArrayList<>()); + + private final List messages = Collections.synchronizedList(new ArrayList<>()); + + final List data = Collections.synchronizedList(new ArrayList<>()); + + private final boolean newLine; + + private final boolean raw; + + private static final String WINDOWS_LINE_SEP = "\r\n"; + + /** + * CountDownLatch for asynchronous logging tests. Example usage: + * + *
+     * @Rule
+     * public LoggerContextRule context = new LoggerContextRule("log4j-list.xml");
+     * private ListAppender listAppender;
+     *
+     * @Before
+     * public void before() throws Exception {
+     *     listAppender = context.getListAppender("List");
+     * }
+     *
+     * @Test
+     * public void testSomething() throws Exception {
+     *     listAppender.countDownLatch = new CountDownLatch(1);
+     *
+     *     Logger logger = LogManager.getLogger();
+     *     logger.info("log one event asynchronously");
+     *
+     *     // wait for the appender to finish processing this event (wait max 1 second)
+     *     listAppender.countDownLatch.await(1, TimeUnit.SECONDS);
+     *
+     *     // now assert something or do follow-up tests...
+     * }
+     * 
+ */ + public volatile CountDownLatch countDownLatch = null; + + public ListAppender(final String name) { + super(name, null, null, true, Property.EMPTY_ARRAY); + newLine = false; + raw = false; + } + + public ListAppender(final String name, final Filter filter, final Layout layout, + final boolean newline, final boolean raw) { + super(name, filter, layout, true, Property.EMPTY_ARRAY); + this.newLine = newline; + this.raw = raw; + if (layout != null) { + final byte[] bytes = layout.getHeader(); + if (bytes != null) { + write(bytes); + } + } + } + + @Override + public void append(final LogEvent event) { + final Layout layout = getLayout(); + if (layout == null) { + if (event instanceof MutableLogEvent) { + // must take snapshot or subsequent calls to logger.log() will modify this event + events.add(((MutableLogEvent) event).createMemento()); + } else { + events.add(event); + } + } else { + write(layout.toByteArray(event)); + } + if (countDownLatch != null) { + countDownLatch.countDown(); + } + } + + void write(final byte[] bytes) { + if (raw) { + data.add(bytes); + return; + } + final String str = new String(bytes); + if (newLine) { + int index = 0; + while (index < str.length()) { + int end; + final int wend = str.indexOf(WINDOWS_LINE_SEP, index); + final int lend = str.indexOf('\n', index); + int length; + if (wend >= 0 && wend < lend) { + end = wend; + length = 2; + } else { + end = lend; + length = 1; + } + if (index == end) { + if (!messages.get(messages.size() - length).isEmpty()) { + messages.add(""); + } + } else if (end >= 0) { + messages.add(str.substring(index, end)); + } else { + messages.add(str.substring(index)); + break; + } + index = end + length; + } + } else { + messages.add(str); + } + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + super.stop(timeout, timeUnit, false); + final Layout layout = getLayout(); + if (layout != null) { + final byte[] bytes = layout.getFooter(); + if (bytes != null) { + write(bytes); + } + } + setStopped(); + return true; + } + + public ListAppender clear() { + events.clear(); + messages.clear(); + data.clear(); + return this; + } + + /** Returns an immutable snapshot of captured log events */ + public List getEvents() { + return Collections.unmodifiableList(new ArrayList<>(events)); + } + + /** Returns an immutable snapshot of captured messages */ + public List getMessages() { + return List.copyOf(messages); + } + + /** + * Polls the messages list for it to grow to a given minimum size at most timeout timeUnits and return a copy of + * what we have so far. + */ + public List getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) + throws InterruptedException { + final long endMillis = System.currentTimeMillis() + timeUnit.toMillis(timeout); + while (messages.size() < minSize && System.currentTimeMillis() < endMillis) { + Thread.sleep(100); + } + return getMessages(); + } + + /** Returns an immutable snapshot of captured data */ + public List getData() { + return List.copyOf(data); + } + + public static ListAppender createAppender(final String name, final boolean newLine, final boolean raw, + final Layout layout, final Filter filter) { + return new ListAppender(name, filter, layout, newLine, raw); + } + + @PluginFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { + + private String name; + private boolean entryPerNewLine; + private boolean raw; + private Layout layout; + private Filter filter; + + public Builder setName(@Required @PluginAttribute final String name) { + this.name = name; + return this; + } + + public Builder setEntryPerNewLine(@PluginAttribute final boolean entryPerNewLine) { + this.entryPerNewLine = entryPerNewLine; + return this; + } + + public Builder setRaw(@PluginAttribute final boolean raw) { + this.raw = raw; + return this; + } + + public Builder setLayout(@PluginElement final Layout layout) { + this.layout = layout; + return this; + } + + public Builder setFilter(@PluginElement final Filter filter) { + this.filter = filter; + return this; + } + + @Override + public ListAppender build() { + return new ListAppender(name, filter, layout, entryPerNewLine, raw); + } + } + + /** + * Gets the named ListAppender if it has been registered. + * + * @param name + * the name of the ListAppender + * @return the named ListAppender or {@code null} if it does not exist + * @see LoggerContextRule#getListAppender(String) + */ + public static ListAppender getListAppender(final String name) { + return (LoggerContext.getContext(false)).getConfiguration().getAppender(name); + } + + @Override + public String toString() { + return "ListAppender [events=" + events + ", messages=" + messages + ", data=" + data + ", newLine=" + newLine + + ", raw=" + raw + ", countDownLatch=" + countDownLatch + ", getHandler()=" + getHandler() + + ", getLayout()=" + getLayout() + ", getName()=" + getName() + ", ignoreExceptions()=" + + ignoreExceptions() + ", getFilter()=" + getFilter() + ", getState()=" + getState() + "]"; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DummyFileAttributes.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/rolling/action/DummyFileAttributes.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DummyFileAttributes.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/rolling/action/DummyFileAttributes.java index 81e316f5096..9c1423035d9 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DummyFileAttributes.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/rolling/action/DummyFileAttributes.java @@ -1,86 +1,86 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.rolling.action; - -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; - -/** - * Test helper class: file attributes. - */ -public class DummyFileAttributes implements BasicFileAttributes { - - public FileTime lastModified; - public FileTime lastAccessTime; - public FileTime creationTime; - public boolean isRegularFile; - public boolean isDirectory; - public boolean isSymbolicLink; - public boolean isOther; - public long size; - public Object fileKey; - - public DummyFileAttributes() { - } - - @Override - public FileTime lastModifiedTime() { - return lastModified; - } - - @Override - public FileTime lastAccessTime() { - return lastAccessTime; - } - - @Override - public FileTime creationTime() { - return creationTime; - } - - @Override - public boolean isRegularFile() { - return isRegularFile; - } - - @Override - public boolean isDirectory() { - return isDirectory; - } - - @Override - public boolean isSymbolicLink() { - return isSymbolicLink; - } - - @Override - public boolean isOther() { - return isOther; - } - - @Override - public long size() { - return size; - } - - @Override - public Object fileKey() { - return fileKey; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.appender.rolling.action; + +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; + +/** + * Test helper class: file attributes. + */ +public class DummyFileAttributes implements BasicFileAttributes { + + public FileTime lastModified; + public FileTime lastAccessTime; + public FileTime creationTime; + public boolean isRegularFile; + public boolean isDirectory; + public boolean isSymbolicLink; + public boolean isOther; + public long size; + public Object fileKey; + + public DummyFileAttributes() { + } + + @Override + public FileTime lastModifiedTime() { + return lastModified; + } + + @Override + public FileTime lastAccessTime() { + return lastAccessTime; + } + + @Override + public FileTime creationTime() { + return creationTime; + } + + @Override + public boolean isRegularFile() { + return isRegularFile; + } + + @Override + public boolean isDirectory() { + return isDirectory; + } + + @Override + public boolean isSymbolicLink() { + return isSymbolicLink; + } + + @Override + public boolean isOther() { + return isOther; + } + + @Override + public long size() { + return size; + } + + @Override + public Object fileKey() { + return fileKey; + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/categories/Appenders.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Appenders.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/categories/Appenders.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Appenders.java index ec9c4037881..954a9f1e55a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/categories/Appenders.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Appenders.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.categories; +package org.apache.logging.log4j.core.test.categories; /** * Categories for appenders that require extra dependencies. diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/categories/AsyncLoggers.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/AsyncLoggers.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/categories/AsyncLoggers.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/AsyncLoggers.java index b46b185dab4..39831690073 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/categories/AsyncLoggers.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/AsyncLoggers.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.categories; +package org.apache.logging.log4j.core.test.categories; /** * Category for tests related to AsyncLogger (requires LMAX Disruptor dependency). diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/categories/Configurations.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Configurations.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/categories/Configurations.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Configurations.java index 3342726e5e0..69bea69c961 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/categories/Configurations.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Configurations.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.categories; +package org.apache.logging.log4j.core.test.categories; /** * Categories for configuration formats that require extra dependencies. diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/GarbageFree.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/GarbageFree.java new file mode 100644 index 00000000000..2c4987457a7 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/GarbageFree.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.categories; + +/** + * JUnit category to indicate a test is for Garbage Free Validation. + */ +public interface GarbageFree { +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/categories/Layouts.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/categories/Layouts.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java index 34775d634a7..daef790df8b 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/categories/Layouts.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Layouts.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.categories; +package org.apache.logging.log4j.core.test.categories; /** * Categories for layouts that require extra dependencies. diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/categories/PerformanceTests.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/PerformanceTests.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/categories/PerformanceTests.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/PerformanceTests.java index fb6eecf4085..67aaf735564 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/categories/PerformanceTests.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/PerformanceTests.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.categories; +package org.apache.logging.log4j.core.test.categories; /** * JUnit category to indicate a test is primarily for testing performance. diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Scripts.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Scripts.java new file mode 100644 index 00000000000..1fbbd82c831 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/Scripts.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.categories; + +/** + * Categories for script plugins that require extra dependencies. + */ +public interface Scripts { + interface Groovy {} +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/package-info.java new file mode 100644 index 00000000000..25cd9264b24 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/categories/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +/** + * JUnit test categories. Unit tests should not specify a category as most tests are unit tests. For performance and + * integration tests, an appropriate category interface should be specified. + */ +package org.apache.logging.log4j.core.test.categories; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/Descriptors.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/Descriptors.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/Descriptors.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/Descriptors.java index 5fe486bb2dd..0ddb27e2f1f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/Descriptors.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/Descriptors.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.hamcrest; +package org.apache.logging.log4j.core.test.hamcrest; import org.hamcrest.Description; import org.hamcrest.Matcher; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/FileMatchers.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/FileMatchers.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/FileMatchers.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/FileMatchers.java index 7336e33dac2..5c0be8edbef 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/FileMatchers.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/FileMatchers.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.hamcrest; +package org.apache.logging.log4j.core.test.hamcrest; import java.io.File; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/MapMatchers.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/MapMatchers.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/MapMatchers.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/MapMatchers.java index 805c8a6a5f9..0803189dc86 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/hamcrest/MapMatchers.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/hamcrest/MapMatchers.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.hamcrest; +package org.apache.logging.log4j.core.test.hamcrest; import java.util.Map; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AllocatePorts.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AllocatePorts.java new file mode 100644 index 00000000000..51a12e324dd --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AllocatePorts.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Sets system properties to free port numbers. These properties are refreshed for each test method. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@ExtendWith(PortAllocatorCallback.class) +public @interface AllocatePorts { + /** + * Names of system properties to set with one or more allocated ports via + * {@link org.apache.logging.log4j.core.test.AvailablePortFinder}. + */ + String[] value(); +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderManagerResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderManagerResolver.java new file mode 100644 index 00000000000..ea5f36e4007 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderManagerResolver.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.plugins.di.Keys; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.support.ReflectionSupport; + +import java.lang.reflect.Parameter; + +import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; + +/** + * Resolves parameters that extend {@link AbstractManager} and have a {@link org.apache.logging.log4j.plugins.Named} + * parameter of the corresponding appender that uses the manager. + */ +class AppenderManagerResolver implements ParameterResolver { + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException { + final Parameter parameter = parameterContext.getParameter(); + return AbstractManager.class.isAssignableFrom(parameter.getType()) && Keys.hasName(parameter); + } + + @Override + public Object resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException { + final LoggerContext loggerContext = getLoggerContext(extensionContext); + if (loggerContext == null) { + throw new ParameterResolutionException("No LoggerContext defined"); + } + final Configuration configuration = loggerContext.getConfiguration(); + final Parameter parameter = parameterContext.getParameter(); + final String name = Keys.getName(parameter); + final Appender appender = configuration.getAppender(name); + if (appender == null) { + throw new ParameterResolutionException("No appender named " + name); + } + final Class appenderClass = appender.getClass(); + final Object manager = ReflectionSupport.findMethod(appenderClass, "getManager") + .map(method -> ReflectionSupport.invokeMethod(method, appender)) + .orElseThrow(() -> new ParameterResolutionException("Cannot find getManager() on appender " + appenderClass)); + final Class parameterType = parameter.getType(); + if (!parameterType.isInstance(manager)) { + throw new ParameterResolutionException("Expected type " + parameterType + " but got type " + manager.getClass()); + } + return manager; + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderResolver.java new file mode 100644 index 00000000000..6e206591a38 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/AppenderResolver.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.plugins.di.Keys; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +import java.lang.reflect.Parameter; + +import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; + +/** + * Resolves parameters that implement {@link Appender} and have a {@link org.apache.logging.log4j.plugins.Named} + * value of the name of the appender. + */ +class AppenderResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + final Parameter parameter = parameterContext.getParameter(); + return Appender.class.isAssignableFrom(parameter.getType()) && Keys.hasName(parameter); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + final LoggerContext loggerContext = getLoggerContext(extensionContext); + if (loggerContext == null) { + throw new ParameterResolutionException("No LoggerContext defined"); + } + final String name = Keys.getName(parameterContext.getParameter()); + if (name.isEmpty()) { + throw new ParameterResolutionException("No named annotation present after checking earlier"); + } + final Appender appender = loggerContext.getConfiguration().getAppender(name); + if (appender == null) { + throw new ParameterResolutionException("No appender named " + name); + } + return appender; + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationResolver.java new file mode 100644 index 00000000000..9b809fc4a61 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ConfigurationResolver.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; + +import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; + +class ConfigurationResolver extends TypeBasedParameterResolver { + + public ConfigurationResolver() { + super(Configuration.class); + } + + @Override + public Configuration resolveParameter( + ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + final LoggerContext loggerContext = getLoggerContext(extensionContext); + if (loggerContext == null) { + throw new ParameterResolutionException("No LoggerContext defined"); + } + return loggerContext.getConfiguration(); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java new file mode 100644 index 00000000000..7848c778958 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorCallback.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +public class ContextSelectorCallback implements BeforeAllCallback, AfterAllCallback { + @Override + public void beforeAll(final ExtensionContext context) throws Exception { + AnnotationSupport.findAnnotation(context.getTestClass(), ContextSelectorType.class) + .map(ContextSelectorType::value) + .ifPresent(contextSelectorClass -> { + final Injector injector = DI.createInjector(); + injector.registerBinding(ContextSelector.KEY, injector.getFactory(contextSelectorClass)); + injector.init(); + final Log4jContextFactory factory = injector.getInstance(Log4jContextFactory.class); + LogManager.setFactory(factory); + }); + } + + @Override + public void afterAll(final ExtensionContext context) throws Exception { + AnnotationSupport.findAnnotation(context.getTestClass(), ContextSelectorType.class) + .ifPresent(ignored -> LogManager.setFactory(null)); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorType.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorType.java new file mode 100644 index 00000000000..d7deef3665e --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ContextSelectorType.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Inherited +@Tag("functional") +@ExtendWith(ContextSelectorCallback.class) +public @interface ContextSelectorType { + + /** + * Specifies the {@link ContextSelector} class to use for this test class. + */ + Class value(); +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java new file mode 100644 index 00000000000..53bd60172b7 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextResolver.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.junit; + +import java.net.URI; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.LoggerContextAccessor; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.platform.commons.support.AnnotationSupport; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class LoggerContextResolver extends TypeBasedParameterResolver implements BeforeAllCallback, + BeforeEachCallback, AfterEachCallback { + private static final String FQCN = LoggerContextResolver.class.getName(); + private static final Namespace BASE_NAMESPACE = Namespace.create(LoggerContext.class); + + public LoggerContextResolver() { + super(LoggerContext.class); + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + final Class testClass = context.getRequiredTestClass(); + AnnotationSupport.findAnnotation(testClass, LoggerContextSource.class) + .ifPresent(testSource -> setUpLoggerContext(testSource, context)); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + final Class testClass = context.getRequiredTestClass(); + if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) { + final Store testClassStore = context.getStore(BASE_NAMESPACE.append(testClass)); + final LoggerContextAccessor accessor = testClassStore.get(LoggerContextAccessor.class, LoggerContextAccessor.class); + if (accessor == null) { + throw new IllegalStateException( + "Specified @LoggerContextSource but no LoggerContext found for test class " + + testClass.getCanonicalName()); + } + if (testClassStore.get(ReconfigurationPolicy.class, ReconfigurationPolicy.class) == ReconfigurationPolicy.BEFORE_EACH) { + accessor.getLoggerContext().reconfigure(); + } + } + AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), LoggerContextSource.class) + .ifPresent(source -> { + final LoggerContext loggerContext = setUpLoggerContext(source, context); + if (source.reconfigure() == ReconfigurationPolicy.BEFORE_EACH) { + loggerContext.reconfigure(); + } + }); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + final Class testClass = context.getRequiredTestClass(); + if (AnnotationSupport.isAnnotated(testClass, LoggerContextSource.class)) { + final Store testClassStore = getTestStore(context); + if (testClassStore.get(ReconfigurationPolicy.class, ReconfigurationPolicy.class) == ReconfigurationPolicy.AFTER_EACH) { + testClassStore.get(LoggerContextAccessor.class, LoggerContextAccessor.class).getLoggerContext().reconfigure(); + } + } + } + + @Override + public LoggerContext resolveParameter( + ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return getLoggerContext(extensionContext); + } + + static LoggerContext getLoggerContext(ExtensionContext context) { + final Store store = getTestStore(context); + final LoggerContextAccessor accessor = store.get(LoggerContextAccessor.class, LoggerContextAccessor.class); + assertNotNull(accessor); + return accessor.getLoggerContext(); + } + + private static Store getTestStore(final ExtensionContext context) { + return context.getStore(BASE_NAMESPACE.append(context.getRequiredTestClass())); + } + + private static LoggerContext setUpLoggerContext(final LoggerContextSource source, final ExtensionContext extensionContext) { + final String displayName = extensionContext.getDisplayName(); + final Injector injector = extensionContext.getTestInstance().map(DI::createInjector).orElseGet(DI::createInjector); + injector.init(); + final Log4jContextFactory loggerContextFactory; + if (source.bootstrap()) { + loggerContextFactory = new Log4jContextFactory(injector); + LogManager.setFactory(loggerContextFactory); + } else { + loggerContextFactory = (Log4jContextFactory) LogManager.getFactory(); + } + final Class testClass = extensionContext.getRequiredTestClass(); + final ClassLoader classLoader = testClass.getClassLoader(); + final Map.Entry injectorContext = Map.entry(Injector.class.getName(), injector); + final String configLocation = source.value(); + final URI configUri; + if (source.v1config()) { + System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, configLocation); + configUri = null; // handled by system property + } else { + configUri = configLocation.isEmpty() ? null : NetUtils.toURI(configLocation); + } + final LoggerContext context = loggerContextFactory.getContext(FQCN, classLoader, injectorContext, false, configUri, displayName); + assertNotNull(context, () -> "No LoggerContext created for " + testClass + " and config file " + configLocation); + final Store store = getTestStore(extensionContext); + store.put(ReconfigurationPolicy.class, source.reconfigure()); + store.put(LoggerContextAccessor.class, new ContextHolder(context, source.timeout(), source.unit())); + return context; + } + + private static class ContextHolder implements Store.CloseableResource, LoggerContextAccessor { + private final LoggerContext context; + private final long shutdownTimeout; + private final TimeUnit unit; + + private ContextHolder(final LoggerContext context, final long shutdownTimeout, final TimeUnit unit) { + this.context = context; + this.shutdownTimeout = shutdownTimeout; + this.unit = unit; + } + + @Override + public LoggerContext getLoggerContext() { + return context; + } + + @Override + public void close() throws Throwable { + try { + context.stop(shutdownTimeout, unit); + } finally { + System.clearProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL); + System.clearProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY); + } + } + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextRule.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java similarity index 75% rename from log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextRule.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java index c0c0def4794..118f71b2450 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextRule.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextRule.java @@ -14,11 +14,12 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.junit; +package org.apache.logging.log4j.core.test.junit; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; @@ -26,32 +27,41 @@ import org.apache.logging.log4j.core.LoggerContextAccessor; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; import org.apache.logging.log4j.core.selector.ContextSelector; -import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.test.junit.CleanFiles; +import org.apache.logging.log4j.test.junit.CleanFolders; +import org.apache.logging.log4j.util.Strings; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; /** * JUnit {@link TestRule} for constructing a new LoggerContext using a specified configuration file. If the system * property {@code EBUG} is set (e.g., through the command line option {@code -DEBUG}), then the StatusLogger will be * set to the debug level. This allows for more debug messages as the StatusLogger will be in the error level until a * configuration file has been read and parsed into a tree of Nodes. + * + * @see LoggerContextSource + * @see Named */ public class LoggerContextRule implements TestRule, LoggerContextAccessor { public static LoggerContextRule createShutdownTimeoutLoggerContextRule(final String config) { return new LoggerContextRule(config, 10, TimeUnit.SECONDS); } - + private static final String SYS_PROP_KEY_CLASS_NAME = "org.apache.logging.log4j.junit.LoggerContextRule#ClassName"; private static final String SYS_PROP_KEY_DISPLAY_NAME = "org.apache.logging.log4j.junit.LoggerContextRule#DisplayName"; - private final String configLocation; + private final String configurationLocation; private LoggerContext loggerContext; private Class contextSelectorClass; private String testClassName; @@ -68,36 +78,36 @@ public LoggerContextRule() { /** * Constructs a new LoggerContextRule for a given configuration file. * - * @param configLocation + * @param configurationLocation * path to configuration file */ - public LoggerContextRule(final String configLocation) { - this(configLocation, null); + public LoggerContextRule(final String configurationLocation) { + this(configurationLocation, null); } /** * Constructs a new LoggerContextRule for a given configuration file and a custom {@link ContextSelector} class. * - * @param configLocation + * @param configurationLocation * path to configuration file * @param contextSelectorClass * custom ContextSelector class to use instead of default */ - public LoggerContextRule(final String configLocation, final Class contextSelectorClass) { - this(configLocation, contextSelectorClass, AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, + public LoggerContextRule(final String configurationLocation, final Class contextSelectorClass) { + this(configurationLocation, contextSelectorClass, AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); } - public LoggerContextRule(final String configLocation, final Class contextSelectorClass, + public LoggerContextRule(final String configurationLocation, final Class contextSelectorClass, final long shutdownTimeout, final TimeUnit shutdownTimeUnit) { - this.configLocation = configLocation; + this.configurationLocation = configurationLocation; this.contextSelectorClass = contextSelectorClass; this.shutdownTimeout = shutdownTimeout; this.shutdownTimeUnit = shutdownTimeUnit; } - public LoggerContextRule(final String config, final int shutdownTimeout, final TimeUnit shutdownTimeUnit) { - this(config, null, shutdownTimeout, shutdownTimeUnit); + public LoggerContextRule(final String configurationLocation, final int shutdownTimeout, final TimeUnit shutdownTimeUnit) { + this(configurationLocation, null, shutdownTimeout, shutdownTimeUnit); } @Override @@ -110,15 +120,29 @@ public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { + System.setProperty(SYS_PROP_KEY_CLASS_NAME, description.getClassName()); + final String displayName = description.getDisplayName(); + System.setProperty(SYS_PROP_KEY_DISPLAY_NAME, displayName); + final Injector injector = DI.createInjector(); if (contextSelectorClass != null) { - System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, contextSelectorClass.getName()); + injector.registerBinding(ContextSelector.KEY, injector.getFactory(contextSelectorClass)); } - // TODO Consider instead of the above: - // LogManager.setFactory(new Log4jContextFactory(LoaderUtil.newInstanceOf(contextSelectorClass))); - System.setProperty(SYS_PROP_KEY_CLASS_NAME, description.getClassName()); - System.setProperty(SYS_PROP_KEY_DISPLAY_NAME, description.getDisplayName()); - loggerContext = Configurator.initialize(description.getDisplayName(), - description.getTestClass().getClassLoader(), configLocation); + injector.init(); + final Log4jContextFactory factory = new Log4jContextFactory(injector); + LogManager.setFactory(factory); + final String fqcn = getClass().getName(); + final ClassLoader classLoader = description.getTestClass().getClassLoader(); + + if (Strings.isBlank(configurationLocation)) { + loggerContext = factory.getContext(fqcn, classLoader, null, false); + } else if (configurationLocation.contains(",")) { + loggerContext = factory.getContext(fqcn, classLoader, null, false, NetUtils.toURIs(configurationLocation), + displayName); + } else { + loggerContext = factory.getContext(fqcn, classLoader, null, false, NetUtils.toURI(configurationLocation), + displayName); + } + assertNotNull("Error initializing LoggerContext", loggerContext); try { base.evaluate(); } finally { @@ -129,7 +153,6 @@ public void evaluate() throws Throwable { loggerContext = null; contextSelectorClass = null; StatusLogger.getLogger().reset(); - System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR); System.clearProperty(SYS_PROP_KEY_CLASS_NAME); System.clearProperty(SYS_PROP_KEY_DISPLAY_NAME); } @@ -145,9 +168,10 @@ public void evaluate() throws Throwable { * the name of the Appender to look up. * @return the named Appender or {@code null} if it wasn't defined in the configuration. */ - public Appender getAppender(final String name) { - return getConfiguration().getAppenders().get(name); - } + @SuppressWarnings("unchecked") // Assume the call site knows what it is doing. + public T getAppender(final String name) { + return (T) getConfiguration().getAppenders().get(name); + } /** * Gets a named Appender for this LoggerContext. @@ -173,6 +197,15 @@ public Configuration getConfiguration() { return loggerContext.getConfiguration(); } + /** + * Gets the configuration location. + * + * @return the configuration location. + */ + public String getConfigurationLocation() { + return configurationLocation; + } + /** * Gets the current LoggerContext associated with this rule. * @@ -277,12 +310,12 @@ public Logger getRootLogger() { public void reconfigure() { loggerContext.reconfigure(); } - + @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("LoggerContextRule [configLocation="); - builder.append(configLocation); + builder.append(configurationLocation); builder.append(", contextSelectorClass="); builder.append(contextSelectorClass); builder.append("]"); diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.java new file mode 100644 index 00000000000..ccdc5027642 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerContextSource.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractManager; +import org.apache.logging.log4j.core.config.Configuration; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * Specifies a configuration file to use for unit tests. This configuration file will be loaded once and used for all tests + * executed in the annotated test class unless otherwise specified by {@link #reconfigure()}. When annotated on a test method, + * this will override the class-level configuration if provided for that method. By using this JUnit 5 extension, the following + * types can be injected into tests via constructor or method parameters: + * + *
    + *
  • {@link LoggerContext};
  • + *
  • {@link Configuration};
  • + *
  • {@link Logger} (with a {@link Named} annotation to use a different Logger than the test class;
  • + *
  • any subclass of {@link Appender} paired with a {@link Named} annotation to select the appender by name;
  • + *
  • any subclass of {@link AbstractManager} paired with a {@link Named} annotation to select the appender by name.
  • + *
+ * + * Tests using this extension will automatically be tagged as {@code functional} to indicate they perform functional tests that + * rely on configuration files and production code. + * + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Inherited +@Tag("functional") +@ExtendWith(LoggerContextResolver.class) +@ExtendWith(ConfigurationResolver.class) +@ExtendWith(AppenderResolver.class) +@ExtendWith(AppenderManagerResolver.class) +@ExtendWith(LoggerResolver.class) +public @interface LoggerContextSource { + /** + * Specifies the name of the configuration file to use for the annotated test. + */ + String value(); + + /** + * Specifies when to {@linkplain LoggerContext#reconfigure() reconfigure} the logging system. + */ + ReconfigurationPolicy reconfigure() default ReconfigurationPolicy.NEVER; + + /** + * Specifies the shutdown timeout limit. Defaults to 0 to mean no limit. + */ + long timeout() default 0L; + + /** + * Specifies the time unit {@link #timeout()} is measured in. + */ + TimeUnit unit() default TimeUnit.SECONDS; + + /** + * Toggles Log4j1 configuration file compatibility mode for XML and properties files. + */ + boolean v1config() default false; + + /** + * Determines whether to bootstrap a fresh LoggerContextFactory. + */ + boolean bootstrap() default false; +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerResolver.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerResolver.java new file mode 100644 index 00000000000..80f72a8b144 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/LoggerResolver.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.plugins.di.Keys; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +import java.lang.reflect.Parameter; + +import static org.apache.logging.log4j.core.test.junit.LoggerContextResolver.getLoggerContext; + +class LoggerResolver implements ParameterResolver { + @Override + public boolean supportsParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.getParameter().getType().isAssignableFrom(Logger.class); + } + + @Override + public Logger resolveParameter(final ParameterContext parameterContext, final ExtensionContext extensionContext) + throws ParameterResolutionException { + final LoggerContext loggerContext = getLoggerContext(extensionContext); + if (loggerContext == null) { + throw new ParameterResolutionException("No LoggerContext defined"); + } + final String loggerName; + final Parameter parameter = parameterContext.getParameter(); + if (Keys.hasName(parameter)) { + loggerName = Keys.getName(parameter); + } else { + loggerName = extensionContext.getRequiredTestClass().getCanonicalName(); + } + return loggerContext.getLogger(loggerName); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Named.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Named.java new file mode 100644 index 00000000000..aa8ea043e3c --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/Named.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider; +import org.apache.logging.log4j.plugins.name.NameProvider; +import org.apache.logging.log4j.util.Strings; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Optional; + +/** + * Specifies the name of an {@link org.apache.logging.log4j.core.Appender} to inject into JUnit 5 tests from the specified + * configuration. + * + * @see LoggerContextSource + * @since 2.14.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@Documented +@NameProvider(Named.Provider.class) +public @interface Named { + /** + * Specifies the name of the configuration item to inject. If blank, uses the name of the annotated parameter. + */ + String value() default ""; + + class Provider implements AnnotatedElementNameProvider { + @Override + public Optional getSpecifiedName(final Named annotation) { + return Strings.trimToOptional(annotation.value()); + } + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/PortAllocatorCallback.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/PortAllocatorCallback.java new file mode 100644 index 00000000000..bab800725dc --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/PortAllocatorCallback.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.junit; + +import java.util.stream.Stream; + +import org.apache.logging.log4j.core.test.AvailablePortFinder; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +class PortAllocatorCallback implements BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback { + @Override + public void beforeAll(final ExtensionContext context) throws Exception { + AnnotationSupport.findAnnotation(context.getTestClass(), AllocatePorts.class) + .map(AllocatePorts::value) + .stream() + .flatMap(Stream::of) + .forEach(PortAllocatorCallback::setSystemPropertyToAllocatedPort); + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + AnnotationSupport.findAnnotation(context.getTestMethod(), AllocatePorts.class) + .map(AllocatePorts::value) + .stream() + .flatMap(Stream::of) + .forEach(PortAllocatorCallback::setSystemPropertyToAllocatedPort); + } + + @Override + public void afterEach(final ExtensionContext context) throws Exception { + AnnotationSupport.findAnnotation(context.getTestMethod(), AllocatePorts.class) + .map(AllocatePorts::value) + .stream() + .flatMap(Stream::of) + .forEach(System::clearProperty); + } + + @Override + public void afterAll(final ExtensionContext context) throws Exception { + AnnotationSupport.findAnnotation(context.getTestClass(), AllocatePorts.class) + .map(AllocatePorts::value) + .stream() + .flatMap(Stream::of) + .forEach(System::clearProperty); + } + + private static void setSystemPropertyToAllocatedPort(final String key) { + int port = AvailablePortFinder.getNextAvailable(); + System.setProperty(key, Integer.toString(port)); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ReconfigurationPolicy.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ReconfigurationPolicy.java new file mode 100644 index 00000000000..1c19ac54c55 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/ReconfigurationPolicy.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.test.junit; + +import org.apache.logging.log4j.core.LoggerContext; + +/** + * Indicates when to {@linkplain LoggerContext#reconfigure() reconfigure} the logging system during unit tests. + * + * @see LoggerContextSource + * @since 2.14.0 + */ +public enum ReconfigurationPolicy { + /** Performs no reconfiguration of the logging system for the entire run of tests in a test class. This is the default. */ + NEVER, + /** Performs a reconfiguration before executing each test. */ + BEFORE_EACH, + /** Performs a reconfiguration after executing each test. */ + AFTER_EACH +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.java new file mode 100644 index 00000000000..56f68c873dd --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/junit/package-info.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +/** + * Log4j test fixtures for JUnit 4 and JUnit 5. + * + * @see org.apache.logging.log4j.core.test.junit.LoggerContextSource JUnit 5 extension + * @see org.apache.logging.log4j.core.test.junit.LoggerContextRule JUnit 4 test rule + */ +package org.apache.logging.log4j.core.test.junit; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_Test.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/Log4j2_1482_Test.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_Test.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/Log4j2_1482_Test.java index abd18526ec4..72c3178f96d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_Test.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/Log4j2_1482_Test.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.core.layout; +package org.apache.logging.log4j.core.test.layout; import java.io.File; import java.io.IOException; @@ -26,10 +26,10 @@ import java.util.Arrays; import java.util.List; -import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.core.test.categories.Layouts; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.junit.CleanFolders; +import org.apache.logging.log4j.test.junit.CleanFolders; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -89,4 +89,4 @@ public void testSingleRun() throws IOException { loopingRun(1); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/LogEventFixtures.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/LogEventFixtures.java index 0bdaaca4cba..f738e4c3568 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/layout/LogEventFixtures.java @@ -14,15 +14,11 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.layout; +package org.apache.logging.log4j.core.test.layout; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.*; import java.io.IOException; -import java.util.Collections; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -35,12 +31,12 @@ import org.apache.logging.log4j.spi.DefaultThreadContextStack; import org.apache.logging.log4j.util.StringMap; -class LogEventFixtures { +public class LogEventFixtures { /** * @return a log event that uses all the bells and whistles, features, nooks and crannies */ - static Log4jLogEvent createLogEvent() { + public static Log4jLogEvent createLogEvent() { final Marker cMarker = MarkerManager.getMarker("Marker1"); final Marker pMarker1 = MarkerManager.getMarker("ParentMarker1"); final Marker pMarker2 = MarkerManager.getMarker("ParentMarker2"); @@ -83,12 +79,10 @@ static Log4jLogEvent createLogEvent() { return expected; } - @SuppressWarnings("deprecation") - static void assertEqualLogEvents(final LogEvent expected, final LogEvent actual, final boolean includeSource, + public static void assertEqualLogEvents(final LogEvent expected, final LogEvent actual, final boolean includeSource, final boolean includeContext, final boolean includeStacktrace) { assertEquals(expected.getClass(), actual.getClass()); assertEquals(includeContext ? expected.getContextData() : ContextDataFactory.createContextData(), actual.getContextData()); - assertEquals(includeContext ? expected.getContextMap() : Collections.EMPTY_MAP, actual.getContextMap()); assertEquals(expected.getContextStack(), actual.getContextStack()); assertEquals(expected.getLevel(), actual.getLevel()); assertEquals(expected.getLoggerName(), actual.getLoggerName()); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServer.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockSyslogServer.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServer.java index 843f7df07c3..aa1b7cd0922 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockSyslogServer.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServer.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.mock; +package org.apache.logging.log4j.core.test.net.mock; import java.util.ArrayList; import java.util.List; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockSyslogServerFactory.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServerFactory.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockSyslogServerFactory.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServerFactory.java index 889df1c1a7e..b5368a0e735 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockSyslogServerFactory.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockSyslogServerFactory.java @@ -14,14 +14,14 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.mock; +package org.apache.logging.log4j.core.test.net.mock; import java.io.IOException; import java.net.SocketException; import javax.net.ssl.SSLServerSocket; -import org.apache.logging.log4j.core.net.ssl.TlsSyslogMessageFormat; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; public class MockSyslogServerFactory { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTcpSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTcpSyslogServer.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTcpSyslogServer.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTcpSyslogServer.java index be791928e3d..6dac227853f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTcpSyslogServer.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTcpSyslogServer.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.mock; +package org.apache.logging.log4j.core.test.net.mock; import java.io.IOException; import java.io.InputStream; @@ -23,28 +23,32 @@ import java.net.Socket; public class MockTcpSyslogServer extends MockSyslogServer { - private final ServerSocket sock; + private final ServerSocket socketServer; private volatile boolean shutdown = false; private Thread thread; public MockTcpSyslogServer(final int numberOfMessagesToReceive, final int port) throws IOException { super(numberOfMessagesToReceive, port); - sock = new ServerSocket(port); + socketServer = new ServerSocket(port); } @Override public void shutdown() { this.shutdown = true; - try { - sock.close(); - } catch (final IOException e) { - e.printStackTrace(); + if (socketServer != null) { + try { + socketServer.close(); + } catch (final IOException e) { + e.printStackTrace(); + } } - thread.interrupt(); - try { - thread.join(100); - } catch (InterruptedException ie) { - System.out.println("Shutdown of TCP server thread failed."); + if (thread != null) { + thread.interrupt(); + try { + thread.join(100); + } catch (InterruptedException ie) { + System.out.println("Shutdown of TCP server thread failed."); + } } } @@ -57,7 +61,7 @@ public void run() { final byte[] buffer = new byte[4096]; Socket socket = null; try { - socket = sock.accept(); + socket = socketServer.accept(); socket.setSoLinger(true, 0); final InputStream in = socket.getInputStream(); int i = in.read(buffer, 0, buffer.length); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTlsSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTlsSyslogServer.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTlsSyslogServer.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTlsSyslogServer.java index b96b4f5c19e..9f4d1f430e2 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTlsSyslogServer.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockTlsSyslogServer.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.mock; +package org.apache.logging.log4j.core.test.net.mock; import java.io.IOException; import java.io.InputStream; @@ -24,10 +24,10 @@ import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; -import org.apache.logging.log4j.core.net.ssl.LegacyBsdTlsSyslogInputStreamReader; -import org.apache.logging.log4j.core.net.ssl.TlsSyslogInputStreamReader; -import org.apache.logging.log4j.core.net.ssl.TlsSyslogInputStreamReaderBase; -import org.apache.logging.log4j.core.net.ssl.TlsSyslogMessageFormat; +import org.apache.logging.log4j.core.test.net.ssl.LegacyBsdTlsSyslogInputStreamReader; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogInputStreamReader; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogInputStreamReaderBase; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; import org.apache.logging.log4j.util.Strings; public class MockTlsSyslogServer extends MockSyslogServer { @@ -53,27 +53,30 @@ public MockTlsSyslogServer(final int loopLen, final TlsSyslogMessageFormat forma public void shutdown() { this.shutdown = true; try { - try { - this.serverSocket.close(); - } - catch (final Exception e) { - e.printStackTrace(); + if (serverSocket != null) { + try { + this.serverSocket.close(); + } catch (final Exception e) { + e.printStackTrace(); + } } this.interrupt(); } catch (final Exception e) { e.printStackTrace(); } - try { - thread.join(100); - } catch (InterruptedException ie) { - System.out.println("Shutdown of TLS server thread failed."); + if (thread != null) { + try { + thread.join(100); + } catch (InterruptedException ie) { + System.out.println("Shutdown of TLS server thread failed."); + } } } @Override public void run() { System.out.println("TLS Server Started"); - this.thread = Thread.currentThread(); + this.thread = currentThread(); try { waitForConnection(); processFrames(); @@ -148,4 +151,4 @@ private boolean isEndOfMessages(final int count) { public List getMessageList() { return messageList; } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockUdpSyslogServer.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockUdpSyslogServer.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockUdpSyslogServer.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockUdpSyslogServer.java index ce379d8dfd3..c6209ea58a1 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockUdpSyslogServer.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/mock/MockUdpSyslogServer.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.mock; +package org.apache.logging.log4j.core.test.net.mock; import java.net.DatagramPacket; import java.net.DatagramSocket; @@ -35,19 +35,23 @@ public MockUdpSyslogServer(final int numberOfMessagesToReceive, final int port) @Override public void shutdown() { this.shutdown = true; - socket.close(); - thread.interrupt(); - try { - thread.join(100); - } catch (InterruptedException ie) { - System.out.println("Shutdown of Log4j UDP server thread failed."); + if (socket != null) { + socket.close(); + } + if (thread != null) { + thread.interrupt(); + try { + thread.join(100); + } catch (InterruptedException ie) { + System.out.println("Shutdown of Log4j UDP server thread failed."); + } } } @Override public void run() { System.out.println("Log4j UDP Server started."); - this.thread = Thread.currentThread(); + this.thread = currentThread(); final byte[] bytes = new byte[4096]; final DatagramPacket packet = new DatagramPacket(bytes, bytes.length); try { @@ -63,4 +67,4 @@ public void run() { } System.out.println("Log4j UDP server stopped."); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/LegacyBsdTlsSyslogInputStreamReader.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/LegacyBsdTlsSyslogInputStreamReader.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/LegacyBsdTlsSyslogInputStreamReader.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/LegacyBsdTlsSyslogInputStreamReader.java index 73623906e65..bef4fe2ae27 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/LegacyBsdTlsSyslogInputStreamReader.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/LegacyBsdTlsSyslogInputStreamReader.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.ssl; +package org.apache.logging.log4j.core.test.net.ssl; import java.io.ByteArrayOutputStream; import java.io.EOFException; diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TestConstants.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TestConstants.java new file mode 100644 index 00000000000..b2a54502977 --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TestConstants.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.net.ssl; + +public class TestConstants { + + public static final String SOURCE_FOLDER = "src/test/resources/"; + public static final String RESOURCE_ROOT = "org/apache/logging/log4j/core/net/ssl/"; + + public static final String PATH = SOURCE_FOLDER + RESOURCE_ROOT; + public static final String TRUSTSTORE_PATH = PATH; + public static final String TRUSTSTORE_RESOURCE = RESOURCE_ROOT; + public static final String TRUSTSTORE_FILE = TRUSTSTORE_PATH + "truststore.jks"; + public static final String TRUSTSTORE_FILE_RESOURCE = TRUSTSTORE_RESOURCE + "truststore.jks"; + public static final char[] TRUSTSTORE_PWD() { return "changeit".toCharArray(); } + public static final String TRUSTSTORE_TYPE = "JKS"; + + public static final String KEYSTORE_PATH = PATH; + public static final String KEYSTORE_RESOURCE = RESOURCE_ROOT; + public static final String KEYSTORE_FILE = KEYSTORE_PATH + "client.log4j2-keystore.jks"; + public static final String KEYSTORE_FILE_RESOURCE = KEYSTORE_RESOURCE + "client.log4j2-keystore.jks"; + public static final char[] KEYSTORE_PWD() { return "changeit".toCharArray(); } + public static final String KEYSTORE_TYPE = "JKS"; + + public static final char[] NULL_PWD = null; +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReader.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReader.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReader.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReader.java index 8ea3142bcfb..910b0abb1f1 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReader.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReader.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.ssl; +package org.apache.logging.log4j.core.test.net.ssl; import java.io.ByteArrayOutputStream; import java.io.EOFException; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReaderBase.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReaderBase.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReaderBase.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReaderBase.java index 5db722455a3..30bef7b0606 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReaderBase.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogInputStreamReaderBase.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.ssl; +package org.apache.logging.log4j.core.test.net.ssl; import java.io.IOException; import java.io.InputStream; @@ -32,4 +32,4 @@ protected TlsSyslogInputStreamReaderBase(final InputStream inputStream, final Tl public String read() throws IOException { throw new UnsupportedOperationException(); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogMessageFormat.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogMessageFormat.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogMessageFormat.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogMessageFormat.java index d0a8aaf77a2..26e053637e0 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogMessageFormat.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/net/ssl/TlsSyslogMessageFormat.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.net.ssl; +package org.apache.logging.log4j.core.test.net.ssl; public enum TlsSyslogMessageFormat { LEGACY_BSD, diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/parser/AbstractLogEventParserTest.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/parser/AbstractLogEventParserTest.java new file mode 100644 index 00000000000..7087551f31f --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/parser/AbstractLogEventParserTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.parser; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; + +import java.util.Arrays; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +/** + * Subclassed by JSON, XML, and YAML modules. + */ +public abstract class AbstractLogEventParserTest { + + protected void assertLogEvent(final LogEvent logEvent) { + assertThat(logEvent, is(notNullValue())); + assertThat(logEvent.getInstant().getEpochMillisecond(), equalTo(1493121664118L)); + assertThat(logEvent.getThreadName(), equalTo("main")); + assertThat(logEvent.getThreadId(), equalTo(1L)); + assertThat(logEvent.getThreadPriority(), equalTo(5)); + assertThat(logEvent.getLevel(), equalTo(Level.INFO)); + assertThat(logEvent.getLoggerName(), equalTo("HelloWorld")); + assertThat(logEvent.getMarker().getName(), equalTo("child")); + assertThat(logEvent.getMarker().getParents()[0].getName(), equalTo("parent")); + assertThat(logEvent.getMarker().getParents()[0].getParents()[0].getName(), + equalTo("grandparent")); + assertThat(logEvent.getMessage().getFormattedMessage(), equalTo("Hello, world!")); + assertThat(logEvent.getThrown(), is(nullValue())); + assertThat(logEvent.getThrownProxy().getMessage(), equalTo("error message")); + assertThat(logEvent.getThrownProxy().getName(), equalTo("java.lang.RuntimeException")); + assertThat(logEvent.getThrownProxy().getExtendedStackTrace()[0].getClassName(), + equalTo("logtest.Main")); + assertThat(logEvent.getLoggerFqcn(), equalTo("org.apache.logging.log4j.spi.AbstractLogger")); + assertThat(logEvent.getContextStack().asList(), equalTo(Arrays.asList("one", "two"))); + assertThat(logEvent.getContextData().getValue("foo"), equalTo("FOO")); + assertThat(logEvent.getContextData().getValue("bar"), equalTo("BAR")); + assertThat(logEvent.getSource().getClassName(), equalTo("logtest.Main")); + } +} diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/FixedTimeClock.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/FixedTimeClock.java new file mode 100644 index 00000000000..cc8a0ce009a --- /dev/null +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/FixedTimeClock.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.test.util; + +import org.apache.logging.log4j.core.time.Clock; + +/** + * Class Description goes here. + */ +public class FixedTimeClock implements Clock { + public static final long FIXED_TIME = 1234567890L; + + /* + * (non-Javadoc) + * + * @see org.apache.logging.log4j.core.helpers.Clock#currentTimeMillis() + */ + @Override + public long currentTimeMillis() { + return FIXED_TIME; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/Profiler.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/Profiler.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/Profiler.java rename to log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/Profiler.java index 4e28860f577..e63e6a57a3d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/Profiler.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/util/Profiler.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.core.test.util; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.status.StatusLogger; diff --git a/log4j-core-test/src/main/module/module-info.java b/log4j-core-test/src/main/module/module-info.java new file mode 100644 index 00000000000..00b74a0d5ac --- /dev/null +++ b/log4j-core-test/src/main/module/module-info.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +import org.apache.logging.log4j.core.test.plugins.Log4jPlugins; +import org.apache.logging.log4j.plugins.model.PluginService; +module org.apache.logging.log4j.core.test { + exports org.apache.logging.log4j.core.test; + exports org.apache.logging.log4j.core.test.appender; + exports org.apache.logging.log4j.core.test.appender.rolling.action; + exports org.apache.logging.log4j.core.test.categories; + exports org.apache.logging.log4j.core.test.hamcrest; + exports org.apache.logging.log4j.core.test.junit; + exports org.apache.logging.log4j.core.test.layout; + exports org.apache.logging.log4j.core.test.net.mock; + exports org.apache.logging.log4j.core.test.parser; + exports org.apache.logging.log4j.core.test.util; + + opens org.apache.logging.log4j.core.test.junit to + org.junit.platform.commons; + + requires java.naming; + requires org.apache.logging.log4j; + requires org.apache.logging.log4j.test; + requires transitive org.apache.logging.log4j.plugins; + requires org.apache.logging.log4j.plugins.test; + requires transitive org.apache.logging.log4j.core; + requires static org.hamcrest; + requires static junit; + requires static org.junit.jupiter.api; + requires static org.junit.platform.commons; + provides PluginService with Log4jPlugins; +} diff --git a/log4j-core-test/src/main/resources/Log4j-config.xsd b/log4j-core-test/src/main/resources/Log4j-config.xsd new file mode 100644 index 00000000000..8c173e24833 --- /dev/null +++ b/log4j-core-test/src/main/resources/Log4j-config.xsd @@ -0,0 +1,1383 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The Advertiser plugin name which will be used to advertise individual FileAppender or SocketAppender configurations. The only + Advertiser plugin provided is 'multicastdns". + + + + + + Either "err" for stderr, "out" for stdout, a file path, or a URL. + + + + + The minimum amount of time, in seconds, that must elapse before the file configuration is checked for changes. + + + + + The name of the configuration. + + + + + A comma separated list of package names to search for plugins. Plugins are only loaded once per classloader so changing this value + may not have any effect upon reconfiguration. + + + + + + Identifies the location for the classloader to located the XML Schema to use to validate the configuration. Only valid when strict + is set to true. If not set no schema validation will take place. + + + + + + Specifies whether or not Log4j should automatically shutdown when the JVM shuts down. The shutdown hook is enabled by default but + may be disabled by setting this attribute to "disable". + + + + + + + + + + + Specifies how many milliseconds appenders and background tasks will get to shutdown when the JVM shuts down. Default is zero which + mean that each appender uses its default timeout, and don't wait for background tasks. + + + + + + The level of internal Log4j events that should be logged to the console. Valid values for this attribute are "trace", "debug", + "info", "warn", "error" and "fatal". + + + + + + + + + + + Enables the use of the strict XML format. Not supported in JSON configurations. + + + + + Enables diagnostic information while loading plugins. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Action to take when the filter matches. May be ACCEPT, DENY or NEUTRAL. + + + + + Action to take when the filter does not match. May be ACCEPT, DENY or NEUTRAL. + + + + + + + + + + + + + + + + + + + + + + + One or more KeyValuePair elements that define the matching value for the key and the Level to evaluate when the key matches. + + + + + + + + + + + + + + The average number of events per second to allow. + + + + + The maximum number of events that can occur before events are filtered for exceeding the average rate. The default is 10 times + the rate. + + + + + + Level of messages to be filtered. Anything at or below this level will be filtered out if maxBurst has been exceeded. The + default is WARN meaning any messages that are higher than warn will be logged regardless of the size of a burst. + + + + + + + + + + + + + + + + The filter type, see https://logging.apache.org/log4j/2.x/manual/filters.html + + + + + + + + + + + + + The name of the item in the ThreadContext Map to compare. + + + + + + Level of messages to be filtered. The default threshold only applies if the log event contains the specified ThreadContext Map + item and its value does not match any key in the key/value pairs. + + + + + + + + + + + + + + The location of the configuration for the filter. + + + + + + + The frequency in seconds that the configuration file should be checked for modifications. + + + + + + + If the operator is "or" then a match by any one of the key/value pairs will be considered to be a match, otherwise all the + key/value pairs must match. + + + + + + + + + + + + + + If the operator is "or" then a match by any one of the key/value pairs will be considered to be a match, otherwise all the + key/value pairs must match. + + + + + + + + + + + + + + + + + + + + + + + + + + + The regular expression. + + + + + If true the unformatted message will be used, otherwise the formatted message will be used. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If the operator is "or" then a match by any one of the key/value pairs will be considered to be a match, otherwise all the + key/value pairs must match. + + + + + + + + + + + + + A valid Level name to match on. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If true exceptions are always written even if the pattern contains no exception conversions. + + + + + The character set to use when converting the syslog String to a byte array. + + + + + If true, do not output ANSI escape codes. + + + + + Footer string to include at the bottom of each log file. + + + + + Header string to include at the top of each log file. + + + + + If true and System.console() is null, do not output ANSI escape codes. + + + + + + + + + + + + + + + + + + + + One or more KeyValuePair elements that define custom field in the output. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + One or more KeyValuePair elements that define custom field in the output. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the appender. + + + + + + The default is true, causing exceptions encountered while appending events to be internally logged and then ignored. + When set to false + exceptions will be propagated to the caller, instead. + You must set this to false when wrapping this Appender in a FailoverAppender. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Write directly to java.io.FileDescriptor and bypass java.lang.System.out/.err. Can give up to 10x performance boost when the + output is redirected to file or other process. Cannot be used with Jansi on Windows. Cannot be used with follow. + + + + + + + Identifies whether the appender honors reassignments of System.out or System.err via System.setOut or System.setErr made after + configuration. Note that the follow attribute cannot be used with Jansi on Windows. Cannot be used with direct. + + + + + + Default is SYSTEM_OUT. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + One or more KeyValuePair elements that define the matching value for the key and the Level to evaluate when the + key + matches. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + When true, records will be appended to the end of the file. + When set to false, the file will be cleared before new records are + written. + + + + + + + + + The name of the file to write to. If the file, or any of its parent directories, do not exist, they will be created. + + + + + + + When set to true, each write will be followed by a flush. + This will guarantee that the data is passed to the operating system for writing; + it does not guarantee that the data is actually written to a physical device + such as a disk drive. + + + + + + + + + + + + + + + When true, records will be written to a buffer and the data will be written to disk when the buffer is full or, if + immediateFlush is set, when the record is written. + File locking cannot be used with bufferedIO. + + + + + + When bufferedIO is true, this is the buffer size, the default is 8192 bytes. + + + + + + + + + + + + + + The appender creates the file on-demand. The appender only creates the file when a log event passes all filters and is routed + to this appender. + + + + + + + File attribute permissions in POSIX format to apply whenever the file is created. + Underlying files system shall support POSIX + file attribute view. + + + + + + + File owner to define whenever the file is created. + Underlying files system shall support POSIX file attribute view. + + + + + + + File group to define whenever the file is created. + Underlying files system shall support POSIX file attribute view. + + + + + + + + + + + + + + The length of the mapped region, defaults to 32 MB (32 * 1024 * 1024 bytes). This parameter must be a value between 256 and + 1,073,741,824 (1 GB or 2^30); values outside this range will be adjusted to the closest valid value. Log4j will round the specified value + up to the nearest power of two. + + + + + + + + + + + + + + + + + + + + + + The pattern of the file name of the archived log file. + The format of the pattern is dependent on the RolloverPolicy that is + used. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The cron expression. The expression is the same as what is allowed in the Quartz scheduler. + + + + + + On startup the cron expression will be evaluated against the file's last modification timestamp. If the cron expression indicates a + rollover should have occurred between that time and the current time the file will be immediately rolled over. + + + + + + + + + + The minimum size the file must have to roll over. A size of zero will cause a roll over no matter what the file size is. The default + value is 1, which will prevent rolling over an empty file. + + + + + + + + + + The size can be specified in bytes, with the suffix KB, MB or GB, for example 20MB. + + + + + + + + + + How often a rollover should occur based on the most specific time unit in the date pattern. + + + + + + + Indicates whether the interval should be adjusted to cause the next rollover to occur on the interval boundary. + + + + + + + Indicates the maximum number of seconds to randomly delay a rollover. By default, this is 0 which indicates no delay. + + + + + + + + + + + + + + + Sets the compression level, 0-9, where 0 = none, 1 = best speed, through 9 = best compression. Only implemented for ZIP files. + + + + + + + If set to "max", files with a higher index will be newer than files with a smaller index. + If set to "min", file renaming and the + counter will follow the Fixed Window strategy described above. + + + + + + + + + + + + + + The maximum value of the counter. Once this values is reached older archives will be deleted on subsequent rollovers. + + + + + + The minimum value of the counter. + + + + + + The pattern of the file name of the archived log file during compression. + + + + + + + + + + + + Sets the compression level, 0-9, where 0 = none, 1 = best speed, through 9 = best compression. Only implemented for ZIP files. + + + + + + + The maximum number of files to allow in the time period matching the file pattern. If the number of files is exceeded the oldest file + will be deleted. If specified, the value must be greater than 1. If the value is less than zero or omitted then the number of files will not be + limited. + + + + + + The pattern of the file name of the archived log file during compression. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Allow boolean values or variable place holders in the form of ${variablename} + + + + + + + + + Allow long values or variable place holders in the form of ${variablename} + + + + + + + diff --git a/log4j-core-test/src/main/resources/Log4j-events.dtd b/log4j-core-test/src/main/resources/Log4j-events.dtd new file mode 100644 index 00000000000..92bff1c9501 --- /dev/null +++ b/log4j-core-test/src/main/resources/Log4j-events.dtd @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/main/resources/Log4j-events.xsd b/log4j-core-test/src/main/resources/Log4j-events.xsd new file mode 100644 index 00000000000..529e21a8bd6 --- /dev/null +++ b/log4j-core-test/src/main/resources/Log4j-events.xsd @@ -0,0 +1,81 @@ + + + + + + Log4J 2.0 XML Schema for XML log event files. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/main/resources/Log4j-levels.xsd b/log4j-core-test/src/main/resources/Log4j-levels.xsd new file mode 100644 index 00000000000..3ca10bbb0c5 --- /dev/null +++ b/log4j-core-test/src/main/resources/Log4j-levels.xsd @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/META-INF/LICENSE b/log4j-core-test/src/main/resources/META-INF/LICENSE similarity index 100% rename from log4j-core/src/test/resources/META-INF/LICENSE rename to log4j-core-test/src/main/resources/META-INF/LICENSE diff --git a/log4j-core-test/src/main/resources/META-INF/MANIFEST.MF b/log4j-core-test/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..e69de29bb2d diff --git a/log4j-core-test/src/main/resources/META-INF/NOTICE b/log4j-core-test/src/main/resources/META-INF/NOTICE new file mode 100644 index 00000000000..97ccc5358ba --- /dev/null +++ b/log4j-core-test/src/main/resources/META-INF/NOTICE @@ -0,0 +1,8 @@ +Apache Log4j Core +Copyright 1999-2012 Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +ResolverUtil.java +Copyright 2005-2006 Tim Fennell \ No newline at end of file diff --git a/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider new file mode 100644 index 00000000000..7917658d6db --- /dev/null +++ b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider @@ -0,0 +1 @@ +org.apache.logging.log4j.core.impl.ThreadContextDataProvider \ No newline at end of file diff --git a/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory new file mode 100644 index 00000000000..fad272d78b2 --- /dev/null +++ b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory @@ -0,0 +1 @@ +org.apache.logging.log4j.core.message.ExtendedThreadInfoFactory diff --git a/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider new file mode 100644 index 00000000000..c4c6c6ca91c --- /dev/null +++ b/log4j-core-test/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider @@ -0,0 +1 @@ +org.apache.logging.log4j.core.impl.Log4jProvider \ No newline at end of file diff --git a/log4j-core-test/src/main/resources/log4j2-calling-class.xml b/log4j-core-test/src/main/resources/log4j2-calling-class.xml new file mode 100644 index 00000000000..4432c82806d --- /dev/null +++ b/log4j-core-test/src/main/resources/log4j2-calling-class.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/site/manual/index.md b/log4j-core-test/src/site/manual/index.md new file mode 100644 index 00000000000..7595a08e9a9 --- /dev/null +++ b/log4j-core-test/src/site/manual/index.md @@ -0,0 +1,33 @@ + + + +# Log4j 2 Implementation + +The Log4j 2 implementation provides the functional components of the logging system. +Users are free to create their own plugins and include them in the logging configuration. + +## Requirements + +As of version 2.4, Log4j 2 requires Java 7. Versions 2.3 and earlier require Java 6. +Some features may require optional +[dependencies](../runtime-dependencies.html). These dependencies are specified in the +documentation for those features. + +Some Log4j features require external dependencies. +See the [Dependency Tree](dependencies.html#Dependency_Tree) +for the exact list of JAR files needed for these features. diff --git a/log4j-core-test/src/site/site.xml b/log4j-core-test/src/site/site.xml new file mode 100644 index 00000000000..6d4cddc0b14 --- /dev/null +++ b/log4j-core-test/src/site/site.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/categories/Scripts.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/categories/Scripts.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/categories/Scripts.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/categories/Scripts.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelJsonTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelJsonTest.java new file mode 100644 index 00000000000..2d3157f6ae1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelJsonTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.hasSize; + +@Tag("json") +@LoggerContextSource("log4j-reference-level.json") +public class AppenderRefLevelJsonTest { + + private final ListAppender app1; + private final ListAppender app2; + + org.apache.logging.log4j.Logger logger1; + org.apache.logging.log4j.Logger logger2; + org.apache.logging.log4j.Logger logger3; + Marker testMarker = MarkerManager.getMarker("TEST"); + + public AppenderRefLevelJsonTest(final LoggerContext context, @Named("LIST1") final ListAppender first, @Named("LIST2") final ListAppender second) { + logger1 = context.getLogger("org.apache.logging.log4j.test1"); + logger2 = context.getLogger("org.apache.logging.log4j.test2"); + logger3 = context.getLogger("org.apache.logging.log4j.test3"); + app1 = first.clear(); + app2 = second.clear(); + } + + @Test + public void logger1() { + logger1.traceEntry(); + logger1.debug("debug message"); + logger1.error("Test Message"); + logger1.info("Info Message"); + logger1.warn("warn Message"); + logger1.traceExit(); + assertThat(app1.getEvents(), hasSize(6)); + assertThat(app2.getEvents(), hasSize(1)); + } + + @Test + public void logger2() { + logger2.traceEntry(); + logger2.debug("debug message"); + logger2.error("Test Message"); + logger2.info("Info Message"); + logger2.warn("warn Message"); + logger2.traceExit(); + assertThat(app1.getEvents(), hasSize(2)); + assertThat(app2.getEvents(), hasSize(4)); + } + + @Test + public void logger3() { + logger3.traceEntry(); + logger3.debug(testMarker, "debug message"); + logger3.error("Test Message"); + logger3.info(testMarker, "Info Message"); + logger3.warn("warn Message"); + logger3.traceExit(); + assertThat(app1.getEvents(), hasSize(4)); + } +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelTest.java new file mode 100644 index 00000000000..022ce48ea01 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/AppenderRefLevelTest.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import java.util.List; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-reference-level.xml") +public class AppenderRefLevelTest { + + private final ListAppender app1; + private final ListAppender app2; + + public AppenderRefLevelTest(final LoggerContext context, @Named("LIST1") final ListAppender first, @Named("LIST2") final ListAppender second) { + logger1 = context.getLogger("org.apache.logging.log4j.test1"); + logger2 = context.getLogger("org.apache.logging.log4j.test2"); + logger3 = context.getLogger("org.apache.logging.log4j.test3"); + app1 = first.clear(); + app2 = second.clear(); + } + + org.apache.logging.log4j.Logger logger1; + org.apache.logging.log4j.Logger logger2; + org.apache.logging.log4j.Logger logger3; + Marker testMarker = MarkerManager.getMarker("TEST"); + + @Test + public void logger1() { + logger1.traceEntry(); + logger1.debug("debug message"); + logger1.error("Test Message"); + logger1.info("Info Message"); + logger1.warn("warn Message"); + logger1.traceExit(); + List events = app1.getEvents(); + assertEquals(6, events.size(), "Incorrect number of events. Expected 6, actual " + events.size()); + events = app2.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + } + + @Test + public void logger2() { + logger2.traceEntry(); + logger2.debug("debug message"); + logger2.error("Test Message"); + logger2.info("Info Message"); + logger2.warn("warn Message"); + logger2.traceExit(); + List events = app1.getEvents(); + assertEquals(events.size(), 2, "Incorrect number of events. Expected 2, actual " + events.size()); + events = app2.getEvents(); + assertEquals(events.size(), 4, "Incorrect number of events. Expected 4, actual " + events.size()); + } + + @Test + public void logger3() { + logger3.traceEntry(); + logger3.debug(testMarker, "debug message"); + logger3.error("Test Message"); + logger3.info(testMarker, "Info Message"); + logger3.warn("warn Message"); + logger3.traceExit(); + final List events = app1.getEvents(); + assertEquals(4, events.size(), "Incorrect number of events. Expected 4, actual " + events.size()); + } +} + diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java index b25844fea20..568cf066dd1 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/BasicLoggingTest.java @@ -18,11 +18,10 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.junit.Test; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; -/** - * - */ +@Tag("smoke") public class BasicLoggingTest { @Test diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CollectionLoggingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CollectionLoggingTest.java new file mode 100644 index 00000000000..3fb1cee528c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CollectionLoggingTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.ReadsSystemProperty; + +import java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +@LoggerContextSource("log4j-collectionLogging.xml") +@Disabled("Work in progress") +public class CollectionLoggingTest { + + private final ListAppender app; + + public CollectionLoggingTest(@Named("List") final ListAppender app) { + this.app = app.clear(); + } + + @Test + @ReadsSystemProperty + public void testSystemProperties(final LoggerContext context) { + final Logger logger = context.getLogger(CollectionLoggingTest.class.getName()); + logger.error(System.getProperties()); + // logger.error(new MapMessage(System.getProperties())); + // TODO: some assertions + } + + @Test + @ReadsSystemProperty + public void testSimpleMap(final LoggerContext context) { + final Logger logger = context.getLogger(CollectionLoggingTest.class.getName()); + logger.error(System.getProperties()); + final Map map = new HashMap<>(); + map.put("MyKey1", "MyValue1"); + map.put("MyKey2", "MyValue2"); + logger.error(new StringMapMessage(map)); + logger.error(map); + // TODO: some assertions + } + + @Test + public void testNetworkInterfaces(final LoggerContext context) throws SocketException { + final Logger logger = context.getLogger(CollectionLoggingTest.class.getName()); + logger.error(NetworkInterface.getNetworkInterfaces()); + // TODO: some assertions + } + + @Test + public void testAvailableCharsets(final LoggerContext context) { + final Logger logger = context.getLogger(CollectionLoggingTest.class.getName()); + logger.error(Charset.availableCharsets()); + // TODO: some assertions + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java index d149148ccdc..6bf68c4f09c 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java @@ -37,7 +37,7 @@ public static void main(final String[] args) { } } catch (final Exception e) { //e.printStackTrace(); - logger.error("Excepcion general", e); + logger.error("Exception general", e); } } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java new file mode 100644 index 00000000000..f2007d19942 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.*; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-customLevels.xml") +public class CustomLevelsOverrideTest { + + private final ListAppender listAppender; + private final Level warnLevel; + private final Level infoLevel; + private final Level debugLevel; + private final Logger logger; + + public CustomLevelsOverrideTest(final LoggerContext context, @Named("List1") final ListAppender appender) { + warnLevel = Level.getLevel("WARN"); + infoLevel = Level.getLevel("INFO"); + debugLevel = Level.getLevel("DEBUG"); + listAppender = appender.clear(); + logger = context.getLogger(getClass().getName()); + } + + @Test + public void testCustomLevelInts() { + // assertEquals(350, warnLevel.intLevel()); + // assertEquals(450, infoLevel.intLevel()); + // assertEquals(550, debugLevel.intLevel()); + assertNotEquals(350, warnLevel.intLevel()); + assertNotEquals(450, infoLevel.intLevel()); + assertNotEquals(550, debugLevel.intLevel()); + } + + @Test + public void testCustomLevelPresence() { + assertNotNull(warnLevel); + assertNotNull(infoLevel); + assertNotNull(debugLevel); + } + + @Test + public void testCustomLevelVsStdLevel() { + assertEquals(Level.WARN, warnLevel); + assertEquals(Level.INFO, infoLevel); + assertEquals(Level.DEBUG, debugLevel); + } + + @Test + public void testLog() { + assertThat(listAppender.getEvents(), hasSize(0)); + logger.debug("Hello, {}", "World"); + assertThat(listAppender.getEvents(), hasSize(1)); + logger.log(warnLevel, "Hello DIAG"); + assertThat(listAppender.getEvents(), hasSize(2)); + assertEquals(listAppender.getEvents().get(1).getLevel(), warnLevel); + + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java new file mode 100644 index 00000000000..b4da830de96 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.*; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-customLevels.xml") +public class CustomLevelsTest { + + private final ListAppender listAppender; + private final Level diagLevel; + private final Level noticeLevel; + private final Level verboseLevel; + private final Logger logger; + + public CustomLevelsTest(final LoggerContext context, @Named("List1") final ListAppender appender) { + diagLevel = Level.getLevel("DIAG"); + noticeLevel = Level.getLevel("NOTICE"); + verboseLevel = Level.getLevel("VERBOSE"); + listAppender = appender.clear(); + logger = context.getLogger(getClass().getName()); + } + + @Test + public void testCustomLevelInts() { + assertEquals(350, diagLevel.intLevel()); + assertEquals(450, noticeLevel.intLevel()); + assertEquals(550, verboseLevel.intLevel()); + } + + @Test + public void testCustomLevelPresence() { + assertNotNull(diagLevel); + assertNotNull(noticeLevel); + assertNotNull(verboseLevel); + } + + @Test + public void testLog() { + assertThat(listAppender.getEvents(), hasSize(0)); + logger.debug("Hello, {}", "World"); + assertThat(listAppender.getEvents(), hasSize(1)); + logger.log(diagLevel, "Hello DIAG"); + assertThat(listAppender.getEvents(), hasSize(2)); + assertEquals(listAppender.getEvents().get(1).getLevel(), diagLevel); + + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java index 18084922a4e..da1efc6ea63 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/CustomLevelsWithFiltersTest.java @@ -16,44 +16,33 @@ */ package org.apache.logging.log4j.core; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.appender.FileAppender; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.filter.CompositeFilter; import org.apache.logging.log4j.core.filter.ThresholdFilter; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.junit.Assert; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; - -/** - * - */ -public class CustomLevelsWithFiltersTest { +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; - private static final String CONFIG = "log4j-customLevelsWithFilters.xml"; +import static org.junit.jupiter.api.Assertions.*; - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); +@LoggerContextSource("log4j-customLevelsWithFilters.xml") +public class CustomLevelsWithFiltersTest { private Level infom1Level; private Level infop1Level; - @Before + @BeforeEach public void before() { infom1Level = Level.getLevel("INFOM1"); infop1Level = Level.getLevel("INFOP1"); } @Test - public void testConfiguration() { - final Configuration configuration = context.getConfiguration(); + public void testConfiguration(final Configuration configuration, @Named("info") final FileAppender appender) { assertNotNull(configuration); - final FileAppender appender = configuration.getAppender("info"); assertNotNull(appender); final CompositeFilter compFilter = (CompositeFilter) appender.getFilter(); assertNotNull(compFilter); @@ -67,7 +56,7 @@ public void testConfiguration() { break; } } - Assert.assertTrue("Level not found: " + infom1Level, foundLevel); + assertTrue(foundLevel, "Level not found: " + infom1Level); } @Test diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/DeadlockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/DeadlockTest.java new file mode 100644 index 00000000000..ff23f0565d3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/DeadlockTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@LoggerContextSource("log4j-deadlock.xml") +@Tag("concurrency") +public class DeadlockTest { + + @Test + public void deadlockOnReconfigure(final LoggerContext context) { + context.reconfigure(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/EventParameterMemoryLeakTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/EventParameterMemoryLeakTest.java new file mode 100644 index 00000000000..76c1b64e12b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/EventParameterMemoryLeakTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.lang.ref.Cleaner; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("functional") +@SetSystemProperty(key = "log4j2.enable.direct.encoders", value = "true") +@SetSystemProperty(key = "log4j2.configurationFile", value = "EventParameterMemoryLeakTest.xml") +public class EventParameterMemoryLeakTest { + + @Test + @SuppressWarnings("UnusedAssignment") // parameter set to null to allow garbage collection + public void testParametersAreNotLeaked() throws Exception { + final File file = new File("target", "EventParameterMemoryLeakTest.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + CountDownLatch latch = new CountDownLatch(1); + final Cleaner cleaner = Cleaner.create(); + Object parameter = new ParameterObject("paramValue"); + cleaner.register(parameter, latch::countDown); + log.info("Message with parameter {}", parameter); + log.info(parameter); + log.info("test", new ObjectThrowable(parameter)); + log.info("test {}", "hello", new ObjectThrowable(parameter)); + parameter = null; + CoreLoggerContexts.stopLoggerContext(file); + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + final String line3 = reader.readLine(); + final String line4 = reader.readLine(); + final String line5 = reader.readLine(); + reader.close(); + file.delete(); + assertThat(line1, containsString("Message with parameter paramValue")); + assertThat(line2, containsString("paramValue")); + assertThat(line3, containsString("paramValue")); + assertThat(line4, containsString("paramValue")); + assertNull(line5, "Expected only three lines"); + try (GarbageCollectionHelper gcHelper = new GarbageCollectionHelper()) { + gcHelper.run(); + assertTrue(latch.await(30, TimeUnit.SECONDS), "Parameter should have been garbage collected"); + } + } + + private static final class ParameterObject { + private final String value; + ParameterObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + + private static final class ObjectThrowable extends RuntimeException { + private final Object object; + + ObjectThrowable(Object object) { + super(String.valueOf(object)); + this.object = object; + } + + @Override + public String toString() { + return "ObjectThrowable " + object; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ExtendedLevelTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ExtendedLevelTest.java new file mode 100644 index 00000000000..b6b497d67bc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ExtendedLevelTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.ExtendedLevels; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-customLevel.xml") +public class ExtendedLevelTest { + + private final ListAppender list1; + private final ListAppender list2; + + public ExtendedLevelTest(@Named("List1") final ListAppender list1, @Named("List2") final ListAppender list2) { + this.list1 = list1.clear(); + this.list2 = list2.clear(); + } + + @Test + public void testLevelLogging(final LoggerContext context) { + org.apache.logging.log4j.Logger logger = context.getLogger("org.apache.logging.log4j.test1"); + logger.log(ExtendedLevels.DETAIL, "Detail message"); + logger.log(Level.DEBUG, "Debug message"); + List events = list1.getEvents(); + assertNotNull(events, "No events"); + assertThat(events, hasSize(1)); + LogEvent event = events.get(0); + assertEquals("DETAIL", event.getLevel().name(), "Expected level DETAIL, got" + event.getLevel()); + logger = context.getLogger("org.apache.logging.log4j.test2"); + logger.log(ExtendedLevels.NOTE, "Note message"); + logger.log(Level.INFO, "Info message"); + events = list2.getEvents(); + assertNotNull(events, "No events"); + assertThat(events, hasSize(1)); + event = events.get(0); + assertEquals("NOTE", event.getLevel().name(), "Expected level NOTE, got" + event.getLevel()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/FormatterLoggerManualExample.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/FormatterLoggerManualExample.java similarity index 91% rename from log4j-core/src/test/java/org/apache/logging/log4j/FormatterLoggerManualExample.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/FormatterLoggerManualExample.java index d5a14389d98..8370b569004 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/FormatterLoggerManualExample.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/FormatterLoggerManualExample.java @@ -14,12 +14,13 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.core; import java.util.Calendar; import java.util.GregorianCalendar; -import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; public class FormatterLoggerManualExample { @@ -41,7 +42,7 @@ String getName() { */ public static void main(final String[] args) { try (final LoggerContext ctx = Configurator.initialize(FormatterLoggerManualExample.class.getName(), - "target/test-classes/log4j2-console.xml");) { + "target/test-classes/log4j2-console.xml")) { final User user = new User(); logger.debug("User %s with birthday %s", user.getName(), user.getBirthdayCalendar()); logger.debug("User %1$s with birthday %2$tm %2$te, %2$tY", user.getName(), user.getBirthdayCalendar()); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java new file mode 100644 index 00000000000..afb38179f50 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import com.google.common.io.ByteStreams; + +import java.io.Closeable; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertTrue; + +public final class GarbageCollectionHelper implements Closeable, Runnable { + private static final OutputStream sink = ByteStreams.nullOutputStream(); + private final AtomicBoolean running = new AtomicBoolean(); + private final CountDownLatch latch = new CountDownLatch(1); + private final Thread gcThread = new Thread(new Runnable() { + @Override + public void run() { + try { + while (running.get()) { + // Allocate data to help suggest a GC + try { + // 1mb of heap + sink.write(new byte[1024 * 1024]); + } catch (IOException ignored) { + } + // May no-op depending on the jvm configuration + System.gc(); + } + } finally { + latch.countDown(); + } + } + }); + + @Override + public void run() { + if (running.compareAndSet(false, true)) { + gcThread.start(); + } + } + + @Override + public void close() { + running.set(false); + try { + assertTrue("GarbageCollectionHelper did not shut down cleanly", + latch.await(10, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/HostNameTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/HostNameTest.java new file mode 100644 index 00000000000..256a0adcaef --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/HostNameTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import java.util.List; + +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-test2.xml") +public class HostNameTest { + + private final ListAppender host; + private final RollingFileAppender hostFile; + + public HostNameTest(@Named("HostTest") final ListAppender list, @Named("HostFile") final RollingFileAppender rolling) { + host = list.clear(); + hostFile = rolling; + } + + @Test + public void testHostname(final LoggerContext context) { + final org.apache.logging.log4j.Logger testLogger = context.getLogger("org.apache.logging.log4j.hosttest"); + testLogger.debug("Hello, {}", "World"); + final List msgs = host.getMessages(); + assertThat(msgs, hasSize(1)); + String expected = NetUtils.getLocalHostname() + Strings.LINE_SEPARATOR; + assertThat(msgs.get(0), endsWith(expected)); + assertNotNull(hostFile.getFileName(), "No Host FileAppender file name"); + expected = "target/" + NetUtils.getLocalHostname() + ".log"; + String name = hostFile.getFileName(); + assertEquals(name, + expected, "Incorrect HostFile FileAppender file name - expected " + expected + " actual - " + name); + name = hostFile.getFilePattern(); + assertNotNull(name, "No file pattern"); + expected = "target/" + NetUtils.getLocalHostname() + "-%d{MM-dd-yyyy}-%i.log"; + assertEquals(name, + expected, "Incorrect HostFile FileAppender file pattern - expected " + expected + " actual - " + name); + + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LateConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LateConfigTest.java new file mode 100644 index 00000000000..f51f411bd56 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LateConfigTest.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.stream.Stream; + +import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; +import org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.xml.XmlConfiguration; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.selector.BasicContextSelector; +import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +@Tag("functional") +public class LateConfigTest { + + private static final String CONFIG = "target/test-classes/log4j-test1.xml"; + // This class will be the caller of `Log4jContextFactory` + private static final String FQCN = Log4jContextFactory.class.getName(); + + static Stream selectors() { + Injector injector = DI.createInjector(); + injector.init(); + return Stream + .of(new ClassLoaderContextSelector(injector.copy()), new BasicContextSelector(injector.copy()), + new AsyncLoggerContextSelector(injector.copy()), new BasicAsyncLoggerContextSelector(injector.copy())) + .map(Log4jContextFactory::new); + } + + @ParameterizedTest + @MethodSource("selectors") + public void testReconfiguration(final Log4jContextFactory factory) throws Exception { + LoggerContext context = factory.getContext(FQCN, null, null, false); + final Configuration cfg = context.getConfiguration(); + assertNotNull(cfg, "No configuration"); + assertTrue(cfg instanceof DefaultConfiguration, "Not set to default configuration"); + final File file = new File(CONFIG); + final LoggerContext loggerContext = factory.getContext(FQCN, null, null, false, file.toURI(), null); + assertNotNull(loggerContext, "No Logger Context"); + final Configuration newConfig = loggerContext.getConfiguration(); + assertNotSame(cfg, newConfig, "Configuration not reset"); + assertTrue(newConfig instanceof XmlConfiguration, "Reconfiguration failed"); + context = factory.getContext(FQCN, null, null, false); + final Configuration sameConfig = context.getConfiguration(); + assertSame(newConfig, sameConfig, "Configuration should not have been reset"); + } +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LevelTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LevelTest.java new file mode 100644 index 00000000000..c177c434c54 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LevelTest.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ObjectMessage; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-Level.xml") +public class LevelTest { + + private final ListAppender listAll; + private final ListAppender listTrace; + private final ListAppender listDebug; + private final ListAppender listInfo; + private final ListAppender listWarn; + private final ListAppender listError; + private final ListAppender listFatal; + + public LevelTest(@Named("ListAll") final ListAppender listAll, @Named("ListTrace") final ListAppender listTrace, + @Named("ListDebug") final ListAppender listDebug, @Named("ListInfo") final ListAppender listInfo, + @Named("ListWarn") final ListAppender listWarn, @Named("ListError") final ListAppender listError, + @Named("ListFatal") final ListAppender listFatal) { + this.listAll = listAll.clear(); + this.listTrace = listTrace.clear(); + this.listDebug = listDebug.clear(); + this.listInfo = listInfo.clear(); + this.listWarn = listWarn.clear(); + this.listError = listError.clear(); + this.listFatal = listFatal.clear(); + } + + // Helper class + private static class Expected { + final ListAppender appender; + final int expectedEventCount; + final String expectedInitialEventLevel; + final String description; + + Expected(final ListAppender appender, final int expectedCount, final String level, final String description) { + this.appender = appender; + this.expectedEventCount = expectedCount; + this.expectedInitialEventLevel = level; + this.description = description; + } + } + + @Test + public void testLevelLogging(final LoggerContext context) { + final Marker marker = MarkerManager.getMarker("marker"); + final Message msg = new ObjectMessage("msg"); + final Throwable t = new Throwable("test"); + final Level[] levels = new Level[] { Level.TRACE, Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR, Level.FATAL }; + final String[] names = new String[] { "levelTest", "levelTest.Trace", "levelTest.Debug", "levelTest.Info", + "levelTest.Warn", "levelTest.Error", "levelTest.Fatal" }; + for (final Level level : levels) { + for (final String name : names) { + final Logger logger = context.getLogger(name); + logger.log(level, msg); // Message + logger.log(level, 123); // Object + logger.log(level, name); // String + logger.log(level, marker, msg); // Marker, Message + logger.log(level, marker, 123); // Marker, Object + logger.log(level, marker, name); // marker, String + logger.log(level, msg, t); // Message, Throwable + logger.log(level, 123, t); // Object, Throwable + logger.log(level, name, "param1", "param2"); // String, Object... + logger.log(level, name, t); // String, Throwable + logger.log(level, marker, msg, t); // Marker, Message, Throwable + logger.log(level, marker, 123, t); // Marker, Object, Throwable + logger.log(level, marker, name, "param1", "param2"); // Marker, String, Object... + logger.log(level, marker, name, t); // Marker, String, Throwable + } + } + // Logger "levelTest" will not receive same events as "levelTest.Trace" + int levelCount = names.length - 1; + + final int UNIT = 14; + final Expected[] expectedResults = new Expected[] { // + new Expected(listAll, UNIT * levelCount, "TRACE", "All"), // + new Expected(listTrace, UNIT * levelCount--, "TRACE", "Trace"), // + new Expected(listDebug, UNIT * levelCount--, "DEBUG", "Debug"), // + new Expected(listInfo, UNIT * levelCount--, "INFO", "Info"), // + new Expected(listWarn, UNIT * levelCount--, "WARN", "Warn"), // + new Expected(listError, UNIT * levelCount--, "ERROR", "Error"), // + new Expected(listFatal, UNIT * levelCount--, "FATAL", "Fatal"), // + }; + for (final Expected expected : expectedResults) { + final String description = expected.description; + final List events = expected.appender.getEvents(); + assertNotNull(events, description + ": No events"); + assertThat(events, hasSize(expected.expectedEventCount)); + final LogEvent event = events.get(0); + assertEquals( + event.getLevel().name(), expected.expectedInitialEventLevel, + description + ": Expected level " + expected.expectedInitialEventLevel + ", got" + event.getLevel()); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/Log4j1222Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/Log4j1222Test.java new file mode 100644 index 00000000000..31b46c23977 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/Log4j1222Test.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.test.TestLogger; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests logging during shutdown. + */ +@Tag("functional") +public class Log4j1222Test +{ + + @Test + @SetSystemProperty(key = "log4j.configurationFile", value = "log4j2-console.xml") + public void homepageRendersSuccessfully() { + Runtime.getRuntime().addShutdownHook(new ShutdownHook()); + } + + private static class ShutdownHook extends Thread { + + private static class Holder { + private static final Logger LOGGER = LogManager.getLogger(Log4j1222Test.class); + } + + @Override + public void run() + { + super.run(); + trigger(); + } + + private void trigger() { + Holder.LOGGER.info("Attempt to trigger"); + assertTrue(Holder.LOGGER instanceof TestLogger, "Logger is of type " + Holder.LOGGER.getClass().getName()); + if (((TestLogger) Holder.LOGGER).getEntries().size() == 0) { + System.out.println("Logger contains no messages"); + } + for (final String msg : ((TestLogger) Holder.LOGGER).getEntries()) { + System.out.println(msg); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.java new file mode 100644 index 00000000000..bec9ab97437 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventFactoryTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.impl.ContextDataFactory; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.LogEventFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Factory; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * + */ +public class LogEventFactoryTest { + + @Test + @LoggerContextSource(value = "log4j2-config.xml") + public void testEvent(@Named("List") final ListAppender app, final LoggerContext context) { + final org.apache.logging.log4j.Logger logger = context.getLogger("org.apache.test.LogEventFactory"); + logger.error("error message"); + final List events = app.getEvents(); + assertNotNull(events, "No events"); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + final LogEvent event = events.get(0); + assertEquals("Test", event.getLoggerName(), "TestLogEventFactory wasn't used"); + } + + public static class TestLogEventFactory implements LogEventFactory { + private final ContextDataInjector injector; + + public TestLogEventFactory(final ContextDataInjector injector) { + this.injector = injector; + } + + @Override + public LogEvent createEvent(final String loggerName, final Marker marker, + final String fqcn, final Level level, final Message data, + final List properties, final Throwable t) { + return Log4jLogEvent.newBuilder() + .setLoggerName("Test") + .setMarker(marker) + .setLoggerFqcn(fqcn) + .setLevel(level) + .setMessage(data) + .setContextDataInjector(injector) + .setContextData(injector.injectContextData(properties, ContextDataFactory.createContextData())) + .setThrown(t) + .build(); + } + } + + @Factory + public LogEventFactory logEventFactory(final ContextDataInjector injector) { + return new TestLogEventFactory(injector); + } +} + diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/LogEventTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventTest.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/LogEventTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventTest.java index bd5ae3eb8a1..9561f37b584 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/LogEventTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogEventTest.java @@ -28,10 +28,10 @@ import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.FilteredObjectInputStream; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * @@ -41,6 +41,7 @@ public class LogEventTest { private static Message MESSAGE = new SimpleMessage("This is a test"); private static TestClass TESTER = new TestClass(); + @SuppressWarnings("BanSerializableRead") @Test public void testSerialization() throws Exception { final LogEvent event1 = Log4jLogEvent.newBuilder() // @@ -78,6 +79,7 @@ public void testSerialization() throws Exception { } } + @SuppressWarnings("BanSerializableRead") @Test public void testNanoTimeIsNotSerialized1() throws Exception { final LogEvent event1 = Log4jLogEvent.newBuilder() // @@ -96,13 +98,14 @@ public void testNanoTimeIsNotSerialized1() throws Exception { final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); final ObjectInputStream ois = new FilteredObjectInputStream(bais); - + final LogEvent actual = (LogEvent) ois.readObject(); - assertNotEquals("Different event: nanoTime", copy, actual); - assertNotEquals("Different nanoTime", copy.getNanoTime(), actual.getNanoTime()); - assertEquals("deserialized nanoTime is zero", 0, actual.getNanoTime()); + assertNotEquals(copy, actual, "Different event: nanoTime"); + assertNotEquals(copy.getNanoTime(), actual.getNanoTime(), "Different nanoTime"); + assertEquals(0, actual.getNanoTime(), "deserialized nanoTime is zero"); } + @SuppressWarnings("BanSerializableRead") @Test public void testNanoTimeIsNotSerialized2() throws Exception { final LogEvent event1 = Log4jLogEvent.newBuilder() // @@ -123,13 +126,13 @@ public void testNanoTimeIsNotSerialized2() throws Exception { final ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); final ObjectInputStream ois = new FilteredObjectInputStream(bais); - + final LogEvent actual = (LogEvent) ois.readObject(); - assertEquals("both zero nanoTime", event2, actual); + assertEquals(event2, actual, "both zero nanoTime"); } @Test - @Ignore + @Disabled public void testEquals() { final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()) // @@ -149,15 +152,15 @@ public void testEquals() { .setLevel(Level.INFO) // .setMessage(new SimpleMessage("Hello, world!")) // .build(); - assertNotEquals("Events should not be equal", event1, event2); - assertEquals("Events should be equal", event2, event3); + assertNotEquals(event1, event2, "Events should not be equal"); + assertEquals(event2, event3, "Events should be equal"); } @Test public void testLocation() { final StackTraceElement ste = TESTER.getEventSource(this.getClass().getName()); - assertNotNull("No StackTraceElement", ste); - assertEquals("Incorrect event", this.getClass().getName(), ste.getClassName()); + assertNotNull(ste, "No StackTraceElement"); + assertEquals(this.getClass().getName(), ste.getClassName(), "Incorrect event"); } private static class TestClass { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/LogRolloverTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogRolloverTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/LogRolloverTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogRolloverTest.java index f1739dd36c4..f91c0e5db66 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/LogRolloverTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogRolloverTest.java @@ -14,11 +14,12 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j; +package org.apache.logging.log4j.core; import java.io.File; -import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; /** diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogbackSubstitution.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogbackSubstitution.java new file mode 100644 index 00000000000..07ee718bf44 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LogbackSubstitution.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; +import org.slf4j.LoggerFactory; + +/** + * + */ +@SetSystemProperty(key = "logback.configurationFile", value = "logback-subst.xml") +public class LogbackSubstitution { + + private final org.slf4j.Logger logger = LoggerFactory.getLogger(LogbackSubstitution.class); + + @Test + public void testSubst() { + logger.debug("Hello, {}", "Log4j {}"); + logger.debug("Hello, {}"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerDateTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerDateTest.java new file mode 100644 index 00000000000..6d8229e109e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerDateTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import java.util.Calendar; + +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-date.xml") +public class LoggerDateTest { + + private final FileAppender fileApp; + + public LoggerDateTest(@Named("File") final FileAppender fileApp) { + this.fileApp = fileApp; + } + + @Test + public void testFileName() { + final String name = fileApp.getFileName(); + final int year = Calendar.getInstance().get(Calendar.YEAR); + assertTrue(name.contains(Integer.toString(year)), "Date was not substituted: " + name); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerSerializationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerSerializationTest.java new file mode 100644 index 00000000000..b1944ed9c92 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerSerializationTest.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import java.util.stream.Stream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.test.AbstractSerializationTest; +import org.junit.runners.Parameterized.Parameters; + +public class LoggerSerializationTest extends AbstractSerializationTest { + + @Parameters + protected Stream data() { + return Stream.of(new LoggerContext("").getLogger("", null), + LogManager.getRootLogger(), + LogManager.getLogger(), + LogManager.getLogger("test")); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java new file mode 100644 index 00000000000..c89c44629fb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java @@ -0,0 +1,511 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.ReconfigurationPolicy; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.ParameterizedMessageFactory; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.StringFormatterMessageFactory; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.spi.LoggingSystem; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource(value = LoggerTest.CONFIG, reconfigure = ReconfigurationPolicy.AFTER_EACH) +public class LoggerTest { + + static final String CONFIG = "log4j-test2.xml"; + private static void checkMessageFactory(final MessageFactory messageFactory, final Logger testLogger) { + if (messageFactory == null) { + assertSame(LoggingSystem.getMessageFactory(), testLogger.getMessageFactory()); + } else { + MessageFactory actual = testLogger.getMessageFactory(); + assertEquals(messageFactory, actual); + } + } + private static Logger testMessageFactoryMismatch(final String name, + final MessageFactory messageFactory1, + final MessageFactory messageFactory2) { + final Logger testLogger1 = (Logger) LogManager.getLogger(name, messageFactory1); + assertNotNull(testLogger1); + checkMessageFactory(messageFactory1, testLogger1); + final Logger testLogger2 = (Logger) LogManager.getLogger(name, messageFactory2); + assertNotNull(testLogger2); + checkMessageFactory(messageFactory2, testLogger2); + return testLogger1; + } + org.apache.logging.log4j.Logger logger; + org.apache.logging.log4j.Logger loggerChild; + org.apache.logging.log4j.Logger loggerGrandchild; + private final ListAppender app; + + private final ListAppender host; + + private final ListAppender noThrown; + + public LoggerTest(final LoggerContext context, @Named("List") final ListAppender app, @Named("HostTest") final ListAppender host, @Named("NoThrowable") final ListAppender noThrown) { + logger = context.getLogger("LoggerTest"); + loggerChild = context.getLogger("LoggerTest.child"); + loggerGrandchild = context.getLogger("LoggerTest.child.grand"); + this.app = app.clear(); + this.host = host.clear(); + this.noThrown = noThrown.clear(); + } + + private void assertEventCount(final List events, final int expected) { + assertEquals(expected, events.size(), "Incorrect number of events."); + } + + @Test + public void basicFlow() { + logger.traceEntry(); + logger.traceExit(); + final List events = app.getEvents(); + assertEventCount(events, 2); + } + + @Test + public void builder() { + logger.atDebug().withLocation().log("Hello"); + Marker marker = MarkerManager.getMarker("test"); + logger.atError().withMarker(marker).log("Hello {}", "John"); + logger.atWarn().withThrowable(new Throwable("This is a test")).log((Message) new SimpleMessage("Log4j rocks!")); + final List events = app.getEvents(); + assertEventCount(events, 3); + assertEquals( + "org.apache.logging.log4j.core.LoggerTest.builder(LoggerTest.java:110)", events.get(0).getSource().toString(), + "Incorrect location"); + assertEquals(Level.DEBUG, events.get(0).getLevel(), "Incorrect Level"); + MatcherAssert.assertThat("Incorrect message", events.get(1).getMessage().getFormattedMessage(), equalTo("Hello John")); + assertNotNull(events.get(2).getThrown(), "Missing Throwable"); + } + + @Test + public void catching() { + try { + throw new NullPointerException(); + } catch (final Exception e) { + logger.catching(e); + } + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + public void debug() { + logger.debug("Debug message"); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + public void debugChangeLevel_ForLogger() { + logger.debug("Debug message 1"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(logger, Level.OFF); + logger.debug("Debug message 2"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(logger, Level.DEBUG); + logger.debug("Debug message 3"); + assertEventCount(app.getEvents(), 2); + } + + @Test + public void debugChangeLevel_ForLoggerName() { + logger.debug("Debug message 1"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(logger.getName(), Level.OFF); + logger.debug("Debug message 2"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(logger.getName(), Level.DEBUG); + logger.debug("Debug message 3"); + assertEventCount(app.getEvents(), 2); + } + + @Test + public void debugChangeLevelAllChildrenLoggers() { + // Use logger AND child loggers + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 child"); + loggerGrandchild.debug("Debug message 1 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setAllLevels(logger.getName(), Level.OFF); + logger.debug("Debug message 2"); + loggerChild.warn("Warn message 2 child"); + loggerGrandchild.fatal("Fatal message 2 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setAllLevels(logger.getName(), Level.DEBUG); + logger.debug("Debug message 3"); + loggerChild.warn("Trace message 3 child"); + loggerGrandchild.trace("Fatal message 3 grandchild"); + assertEventCount(app.getEvents(), 5); + } + + @Test + public void debugChangeLevelChildLogger_ForLogger() { + // Use logger AND child loggers + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 child"); + loggerGrandchild.debug("Debug message 1 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger, Level.OFF); + logger.debug("Debug message 2"); + loggerChild.debug("Debug message 2 child"); + loggerGrandchild.debug("Debug message 2 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger, Level.DEBUG); + logger.debug("Debug message 3"); + loggerChild.debug("Debug message 3 child"); + loggerGrandchild.debug("Debug message 3 grandchild"); + assertEventCount(app.getEvents(), 6); + } + + @Test + public void debugChangeLevelChildLogger_ForLoggerName() { + // Use logger AND child loggers + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 child"); + loggerGrandchild.debug("Debug message 1 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger.getName(), Level.OFF); + logger.debug("Debug message 2"); + loggerChild.debug("Debug message 2 child"); + loggerGrandchild.debug("Debug message 2 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger.getName(), Level.DEBUG); + logger.debug("Debug message 3"); + loggerChild.debug("Debug message 3 child"); + loggerGrandchild.debug("Debug message 3 grandchild"); + assertEventCount(app.getEvents(), 6); + } + + @Test + public void debugChangeLevelName_ForLoggerName() { + logger.debug("Debug message 1"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(logger.getName(), Level.OFF.name()); + logger.debug("Debug message 2"); + assertEventCount(app.getEvents(), 1); + Configurator.setLevel(logger.getName(), Level.DEBUG.name()); + logger.debug("Debug message 3"); + assertEventCount(app.getEvents(), 2); + } + + @Test + public void debugChangeLevelNameChildLogger_ForLoggerName() { + // Use logger AND child loggers + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 child"); + loggerGrandchild.debug("Debug message 1 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger.getName(), Level.OFF.name()); + logger.debug("Debug message 2"); + loggerChild.debug("Debug message 2 child"); + loggerGrandchild.debug("Debug message 2 grandchild"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger.getName(), Level.DEBUG.name()); + logger.debug("Debug message 3"); + loggerChild.debug("Debug message 3 child"); + loggerGrandchild.debug("Debug message 3 grandchild"); + assertEventCount(app.getEvents(), 6); + } + + @Test + public void debugChangeLevelNameChildLoggers_ForLoggerName(final LoggerContext context) { + final org.apache.logging.log4j.Logger loggerChild = context.getLogger(logger.getName() + ".child"); + // Use logger AND loggerChild + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 child"); + assertEventCount(app.getEvents(), 2); + Configurator.setLevel(logger.getName(), Level.ERROR.name()); + Configurator.setLevel(loggerChild.getName(), Level.DEBUG.name()); + logger.debug("Debug message 2"); + loggerChild.debug("Debug message 2 child"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger.getName(), Level.DEBUG.name()); + logger.debug("Debug message 3"); + loggerChild.debug("Debug message 3 child"); + assertEventCount(app.getEvents(), 5); + } + + @Test + public void debugChangeLevelsChildLoggers_ForLogger(final LoggerContext context) { + final org.apache.logging.log4j.Logger loggerChild = context.getLogger(logger.getName() + ".child"); + // Use logger AND loggerChild + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 child"); + assertEventCount(app.getEvents(), 2); + Configurator.setLevel(logger, Level.ERROR); + Configurator.setLevel(loggerChild, Level.DEBUG); + logger.debug("Debug message 2"); + loggerChild.debug("Debug message 2 child"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger, Level.DEBUG); + logger.debug("Debug message 3"); + loggerChild.debug("Debug message 3 child"); + assertEventCount(app.getEvents(), 5); + } + + @Test + public void debugChangeLevelsChildLoggers_ForLoggerName(final LoggerContext context) { + final org.apache.logging.log4j.Logger loggerChild = context.getLogger(logger.getName() + ".child"); + // Use logger AND loggerChild + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 child"); + assertEventCount(app.getEvents(), 2); + Configurator.setLevel(logger.getName(), Level.ERROR); + Configurator.setLevel(loggerChild.getName(), Level.DEBUG); + logger.debug("Debug message 2"); + loggerChild.debug("Debug message 2 child"); + assertEventCount(app.getEvents(), 3); + Configurator.setLevel(logger.getName(), Level.DEBUG); + logger.debug("Debug message 3"); + loggerChild.debug("Debug message 3 child"); + assertEventCount(app.getEvents(), 5); + } + + @Test + public void debugChangeLevelsMap() { + logger.debug("Debug message 1"); + assertEventCount(app.getEvents(), 1); + final Map map = new HashMap<>(); + map.put(logger.getName(), Level.OFF); + Configurator.setLevel(map); + logger.debug("Debug message 2"); + assertEventCount(app.getEvents(), 1); + map.put(logger.getName(), Level.DEBUG); + Configurator.setLevel(map); + logger.debug("Debug message 3"); + assertEventCount(app.getEvents(), 2); + } + + @Test + public void debugChangeLevelsMapChildLoggers() { + logger.debug("Debug message 1"); + loggerChild.debug("Debug message 1 C"); + loggerGrandchild.debug("Debug message 1 GC"); + assertEventCount(app.getEvents(), 3); + final Map map = new HashMap<>(); + map.put(logger.getName(), Level.OFF); + map.put(loggerChild.getName(), Level.DEBUG); + map.put(loggerGrandchild.getName(), Level.WARN); + Configurator.setLevel(map); + logger.debug("Debug message 2"); + loggerChild.debug("Debug message 2 C"); + loggerGrandchild.debug("Debug message 2 GC"); + assertEventCount(app.getEvents(), 4); + map.put(logger.getName(), Level.DEBUG); + map.put(loggerChild.getName(), Level.OFF); + map.put(loggerGrandchild.getName(), Level.DEBUG); + Configurator.setLevel(map); + logger.debug("Debug message 3"); + loggerChild.debug("Debug message 3 C"); + loggerGrandchild.debug("Debug message 3 GC"); + assertEventCount(app.getEvents(), 6); + } + + @Test + public void debugChangeRootLevel() { + logger.debug("Debug message 1"); + assertEventCount(app.getEvents(), 1); + Configurator.setRootLevel(Level.OFF); + logger.debug("Debug message 2"); + assertEventCount(app.getEvents(), 1); + Configurator.setRootLevel(Level.DEBUG); + logger.debug("Debug message 3"); + assertEventCount(app.getEvents(), 2); + } + + @Test + public void debugObject() { + logger.debug(new Date()); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + public void debugWithParms() { + logger.debug("Hello, {}", "World"); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + public void getLogger_String_MessageFactoryMismatch(final TestInfo testInfo) { + final Logger testLogger = testMessageFactoryMismatch(testInfo.getTestMethod().map(Method::getName).orElseThrow(AssertionError::new), + StringFormatterMessageFactory.INSTANCE, ParameterizedMessageFactory.INSTANCE); + testLogger.debug("%,d", Integer.MAX_VALUE); + final List events = app.getEvents(); + assertEventCount(events, 1); + assertEquals(String.format("%,d", Integer.MAX_VALUE), events.get(0).getMessage().getFormattedMessage()); + } + + @Test + public void getLogger_String_MessageFactoryMismatchNull(final TestInfo testInfo) { + final Logger testLogger = testMessageFactoryMismatch(testInfo.getTestMethod().map(Method::getName).orElseThrow(AssertionError::new), StringFormatterMessageFactory.INSTANCE, null); + testLogger.debug("%,d", Integer.MAX_VALUE); + final List events = app.getEvents(); + assertEventCount(events, 1); + assertEquals(String.format("%,d", Integer.MAX_VALUE), events.get(0).getMessage().getFormattedMessage()); + } + + @Test + public void mdc() { + ThreadContext.put("TestYear", "2010"); + logger.debug("Debug message"); + ThreadContext.clearMap(); + logger.debug("Debug message"); + final List events = app.getEvents(); + assertEventCount(events, 2); + } + + @Test + public void paramWithExceptionTest() throws Exception { + logger.error("Throwing with parameters {}", "TestParam", new NullPointerException("Test Exception")); + final List events = app.getEvents(); + assertNotNull(events, "Log event list not returned"); + assertEquals(1, events.size(), "Incorrect number of log events"); + final LogEvent event = events.get(0); + final Throwable thrown = event.getThrown(); + assertNotNull(thrown, "No throwable present in log event"); + final Message msg = event.getMessage(); + assertEquals("Throwing with parameters {}", msg.getFormat()); + assertEquals("Throwing with parameters TestParam", msg.getFormattedMessage()); + assertArrayEquals(new Object[] { "TestParam", thrown }, msg.getParameters()); + } + + @Test + public void simpleFlow() { + logger.traceEntry(CONFIG); + logger.traceExit(0); + final List events = app.getEvents(); + assertEventCount(events, 2); + } + + @Test + public void structuredData() { + ThreadContext.put("loginId", "JohnDoe"); + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + final StructuredDataMessage msg = new StructuredDataMessage("Audit@18060", "Transfer Complete", "Transfer"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + logger.info(MarkerManager.getMarker("EVENT"), msg); + ThreadContext.clearMap(); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + public void testAdditivity(final LoggerContext context) throws Exception { + final Logger localLogger = context.getLogger("org.apache.test"); + localLogger.error("Test parent additivity"); + final List events = app.getEvents(); + assertEventCount(events, 1); + } + + @Test + public void testImpliedThrowable(final LoggerContext context) { + final org.apache.logging.log4j.Logger testLogger = context.getLogger("org.apache.logging.log4j.hosttest"); + testLogger.debug("This is a test", new Throwable("Testing")); + final List msgs = host.getMessages(); + assertEquals(1, msgs.size(), "Incorrect number of messages. Expected 1, actual " + msgs.size()); + final String expected = "java.lang.Throwable: Testing"; + assertTrue(msgs.get(0).contains(expected), "Incorrect message data"); + } + + @Test + public void testLevelInheritance(final LoggerContext context) throws Exception { + final Configuration config = context.getConfiguration(); + final LoggerConfig loggerConfig = config.getLoggerConfig("org.apache.logging.log4j.core.LoggerTest"); + assertNotNull(loggerConfig); + assertEquals("org.apache.logging.log4j.core.LoggerTest", loggerConfig.getName()); + assertEquals(Level.DEBUG, loggerConfig.getLevel()); + final Logger localLogger = context.getLogger("org.apache.logging.log4j.core.LoggerTest"); + assertSame(localLogger.getLevel(), Level.DEBUG, "Incorrect level - expected DEBUG, actual " + localLogger.getLevel()); + } + + @Test + @Tag("sleepy") + public void testReconfiguration(final LoggerContext context) throws Exception { + final Configuration oldConfig = context.getConfiguration(); + final int MONITOR_INTERVAL_SECONDS = 5; + final File file = new File("target/test-classes/" + CONFIG); + final long orig = file.lastModified(); + final long newTime = orig + 10000; + assertTrue(file.setLastModified(newTime), "setLastModified should have succeeded."); + TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS + 1); + for (int i = 0; i < 17; ++i) { + logger.debug("Reconfigure"); + } + Thread.sleep(100); + for (int i = 0; i < 20; i++) { + if (context.getConfiguration() != oldConfig) { + break; + } + Thread.sleep(50); + } + final Configuration newConfig = context.getConfiguration(); + assertNotNull(newConfig, "No configuration"); + assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); + } + + @Test + public void testSuppressedThrowable(final LoggerContext context) { + final org.apache.logging.log4j.Logger testLogger = context.getLogger("org.apache.logging.log4j.nothrown"); + testLogger.debug("This is a test", new Throwable("Testing")); + final List msgs = noThrown.getMessages(); + assertEquals(1, msgs.size(), "Incorrect number of messages. Expected 1, actual " + msgs.size()); + final String suppressed = "java.lang.Throwable: Testing"; + assertFalse(msgs.get(0).contains(suppressed), "Incorrect message data"); + } + + @Test + public void throwing() { + logger.throwing(new IllegalArgumentException("Test Exception")); + final List events = app.getEvents(); + assertEventCount(events, 1); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerUpdateTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerUpdateTest.java new file mode 100644 index 00000000000..0753ae8791f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerUpdateTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +@LoggerContextSource("log4j-test2.xml") +public class LoggerUpdateTest { + + private final ListAppender app; + + public LoggerUpdateTest(@Named("List") final ListAppender app) { + this.app = app.clear(); + } + + @Test + public void resetLevel(final LoggerContext context) { + final org.apache.logging.log4j.Logger logger = context.getLogger("com.apache.test"); + logger.traceEntry(); + List events = app.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + app.clear(); + final Configuration config = context.getConfiguration(); + final LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); + /* You could also specify the actual logger name as below and it will return the LoggerConfig used by the Logger. + LoggerConfig loggerConfig = getLoggerConfig("com.apache.test"); + */ + loggerConfig.setLevel(Level.DEBUG); + context.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig. + logger.traceEntry(); + events = app.getEvents(); + assertEquals(0, events.size(), "Incorrect number of events. Expected 0, actual " + events.size()); + } + + @Test + public void testUpdateLoggersPropertyListeners(final LoggerContext context) throws Exception { + context.addPropertyChangeListener(evt -> { + assertEquals(LoggerContext.PROPERTY_CONFIG, evt.getPropertyName()); + assertSame(context, evt.getSource()); + }); + context.updateLoggers(); + } +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LookupTest.java new file mode 100644 index 00000000000..fc4ba9fed43 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LookupTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-lookup.xml") +public class LookupTest { + + @Test + public void testHostname(@Named final ConsoleAppender console) { + final Layout layout = console.getLayout(); + assertNotNull(layout, "No Layout"); + assertTrue(layout instanceof PatternLayout, "Layout is not a PatternLayout"); + final String pattern = ((PatternLayout) layout).getConversionPattern(); + assertNotNull(pattern, "No conversion pattern"); + assertTrue(pattern.contains("org.junit,org.apache.maven,org.eclipse,sun.reflect,java.lang.reflect"), + "No filters"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java new file mode 100644 index 00000000000..b16d36af2df --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PatternResolverDoesNotEvaluateThreadContextTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@LoggerContextSource("log4j2-pattern-layout-with-context.xml") +public class PatternResolverDoesNotEvaluateThreadContextTest { + + private static final String PARAMETER = "user"; + + private final LoggerContext context; + private final ListAppender listAppender; + + public PatternResolverDoesNotEvaluateThreadContextTest( + LoggerContext context, + @Named("list") ListAppender app) { + this.context = context; + this.listAppender = app.clear(); + } + + @Test + public void testNoUserSet() { + Logger logger = context.getLogger(getClass()); + logger.info("This is a test"); + List messages = listAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages returned"); + String message = messages.get(0); + assertEquals("INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest ${ctx:user} This is a test", message); + } + + @Test + public void testMessageIsNotLookedUp() { + Logger logger = context.getLogger(getClass()); + logger.info("This is a ${upper:test}"); + List messages = listAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages returned"); + String message = messages.get(0); + assertEquals("INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest ${ctx:user} This is a ${upper:test}", message); + } + + @Test + public void testUser() { + Logger logger = context.getLogger(getClass()); + ThreadContext.put(PARAMETER, "123"); + try { + logger.info("This is a test"); + } finally { + ThreadContext.remove(PARAMETER); + } + List messages = listAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages returned"); + String message = messages.get(0); + assertEquals("INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest 123 This is a test", message); + } + + @Test + public void testUserIsLookup() { + Logger logger = context.getLogger(getClass()); + ThreadContext.put(PARAMETER, "${java:version}"); + try { + logger.info("This is a test"); + } finally { + ThreadContext.remove(PARAMETER); + } + List messages = listAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages returned"); + String message = messages.get(0); + assertEquals("INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest ${java:version} This is a test", message); + } + + @Test + public void testUserHasLookup() { + Logger logger = context.getLogger(getClass()); + ThreadContext.put(PARAMETER, "user${java:version}name"); + try { + logger.info("This is a test"); + } finally { + ThreadContext.remove(PARAMETER); + } + List messages = listAppender.getMessages(); + assertTrue(messages != null && messages.size() > 0, "No messages returned"); + String message = messages.get(0); + assertEquals("INFO org.apache.logging.log4j.core." + + "PatternResolverDoesNotEvaluateThreadContextTest user${java:version}name This is a test", message); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java new file mode 100644 index 00000000000..e4982675490 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@LoggerContextSource("log4j-test2.properties") +@Tag("sleepy") +public class PropertiesFileConfigTest { + + private static final String CONFIG = "target/test-classes/log4j-test2.properties"; + + private final org.apache.logging.log4j.Logger logger; + + public PropertiesFileConfigTest(final LoggerContext context) { + logger = context.getLogger("LoggerTest"); + } + + @BeforeEach + void clear(@Named("List") final ListAppender appender) { + appender.clear(); + } + + @Test + public void testReconfiguration(final LoggerContext context) throws Exception { + final Configuration oldConfig = context.getConfiguration(); + final int MONITOR_INTERVAL_SECONDS = 5; + final File file = new File(CONFIG); + final long orig = file.lastModified(); + final long newTime = orig + 10000; + assertTrue(file.setLastModified(newTime), "setLastModified should have succeeded."); + TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS + 1); + for (int i = 0; i < 17; ++i) { + logger.info("Reconfigure"); + } + int loopCount = 0; + Configuration newConfig; + do { + Thread.sleep(100); + newConfig = context.getConfiguration(); + } while (newConfig == oldConfig && loopCount++ < 5); + assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); + } +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ReusableParameterizedMessageMemoryLeakTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ReusableParameterizedMessageMemoryLeakTest.java new file mode 100644 index 00000000000..93081ee1e8c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ReusableParameterizedMessageMemoryLeakTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.message.ReusableMessage; +import org.apache.logging.log4j.message.ReusableMessageFactory; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ReusableParameterizedMessageMemoryLeakTest { + + @Test + public void testParametersAreNotLeaked() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + ReusableMessage message = (ReusableMessage) ReusableMessageFactory.INSTANCE.newMessage( + "foo {}", new ParameterObject("paramValue", latch)); + // Large enough for the parameters, but smaller than the default reusable array size. + message.swapParameters(new Object[5]); + try (GarbageCollectionHelper gcHelper = new GarbageCollectionHelper()) { + gcHelper.run(); + assertTrue(latch.await(30, TimeUnit.SECONDS), "Parameter should have been garbage collected"); + } + } + + private static final class ParameterObject { + private final String value; + private final CountDownLatch latch; + ParameterObject(String value, CountDownLatch latch) { + this.value = value; + this.latch = latch; + } + + @Override + public String toString() { + return value; + } + + @Override + protected void finalize() throws Throwable { + latch.countDown(); + super.finalize(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.java new file mode 100644 index 00000000000..aa553a4ad76 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +@LoggerContextSource("log4j-test3.xml") +public class ShutdownDisabledTest { + + @Test + public void testShutdownFlag(final Configuration config) { + assertFalse(config.isShutdownHookEnabled(), "Shutdown hook is enabled"); + } + +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownTimeoutConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownTimeoutConfigurationTest.java new file mode 100644 index 00000000000..e97a66687a9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownTimeoutConfigurationTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-test-shutdownTimeout.xml") +public class ShutdownTimeoutConfigurationTest { + + @Test + public void testShutdownFlag(final Configuration config) { + assertEquals(5000, config.getShutdownTimeoutMillis()); + } + +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.java new file mode 100644 index 00000000000..d76434068a9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.EntryMessage; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@LoggerContextSource("log4j-strict1.xml") +public class StrictXmlConfigTest { + + org.apache.logging.log4j.Logger logger; + private ListAppender app; + + public StrictXmlConfigTest(final LoggerContext context, @Named("List") final ListAppender app) { + logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + public void basicFlow() { + final EntryMessage entry = logger.traceEntry(); + logger.traceExit(entry); + final List events = app.getEvents(); + assertEquals(2, events.size(), "Incorrect number of events. Expected 2, actual " + events.size()); + } + + @Test + public void basicFlowDeprecated() { + logger.traceEntry(); + logger.traceExit(); + final List events = app.getEvents(); + assertEquals(2, events.size(), "Incorrect number of events. Expected 2, actual " + events.size()); + } + + @Test + public void simpleFlow() { + logger.traceEntry(); + logger.traceExit(0); + final List events = app.getEvents(); + assertEquals(2, events.size(), "Incorrect number of events. Expected 2, actual " + events.size()); + } + + @Test + public void throwing() { + logger.throwing(new IllegalArgumentException("Test Exception")); + final List events = app.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + } + + @Test + public void catching() { + try { + throw new NullPointerException(); + } catch (final Exception e) { + logger.catching(e); + } + final List events = app.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + } + + @Test + public void debug() { + logger.debug("Debug message"); + final List events = app.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + } + + @Test + public void debugObject() { + logger.debug(new Date()); + final List events = app.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + } + + @Test + public void debugWithParms() { + logger.debug("Hello, {}", "World"); + final List events = app.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + } + + @Test + public void mdc() { + ThreadContext.put("TestYear", "2010"); + logger.debug("Debug message"); + ThreadContext.clearMap(); + logger.debug("Debug message"); + final List events = app.getEvents(); + assertEquals(2, events.size(), "Incorrect number of events. Expected 2, actual " + events.size()); + } + + @Test + public void structuredData() { + ThreadContext.put("loginId", "JohnDoe"); + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + final StructuredDataMessage msg = new StructuredDataMessage("Audit@18060", "Transfer Complete", "Transfer"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + logger.info(MarkerManager.getMarker("EVENT"), msg); + ThreadContext.clearMap(); + final List events = app.getEvents(); + assertEquals(1, events.size(), "Incorrect number of events. Expected 1, actual " + events.size()); + } +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.java new file mode 100644 index 00000000000..23cb4a0c9cd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.util.StringBuilders; + +/** + * {@link TestPatternConverters} provides {@link LogEventPatternConverter} implementations that may be + * useful in tests. + */ +public final class TestPatternConverters { + private TestPatternConverters() { + } + + @Namespace("Converter") + @Plugin("TestParametersPatternConverter") + @ConverterKeys("testparameters") + public static final class TestParametersPatternConverter extends LogEventPatternConverter { + + private TestParametersPatternConverter() { + super("Parameters", "testparameters"); + } + + public static TestParametersPatternConverter newInstance(final String[] options) { + return new TestParametersPatternConverter(); + } + + @Override + public void format(final LogEvent event, final StringBuilder toAppendTo) { + toAppendTo.append('['); + Object[] parameters = event.getMessage().getParameters(); + if (parameters != null) { + for (int i = 0; i < parameters.length; i++) { + StringBuilders.appendValue(toAppendTo, parameters[i]); + if (i != parameters.length - 1) { + toAppendTo.append(','); + } + } + } + toAppendTo.append(']'); + } + } + + @Namespace("Converter") + @Plugin("TestFormatPatternConverter") + @ConverterKeys("testformat") + public static final class TestFormatPatternConverter extends LogEventPatternConverter { + + private TestFormatPatternConverter() { + super("Format", "testformat"); + } + + public static TestFormatPatternConverter newInstance(final String[] options) { + return new TestFormatPatternConverter(); + } + + @Override + public void format(final LogEvent event, final StringBuilder toAppendTo) { + toAppendTo.append(event.getMessage().getFormat()); + } + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ThreadContextTestAccess.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ThreadContextTestAccess.java new file mode 100644 index 00000000000..fc35ae51b8d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ThreadContextTestAccess.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import java.lang.reflect.Method; + +import org.apache.logging.log4j.ThreadContext; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + *

+ * Utility class to access package protected methods in {@code ThreadContext}. + *

+ * + * @see ThreadContext + * @since 2.7 + */ +public final class ThreadContextTestAccess { + private ThreadContextTestAccess() { // prevent instantiation + } + + public static void init() { + try { + Class clazz = ThreadContext.class; + Method method = clazz.getDeclaredMethod("init"); + method.setAccessible(true); + method.invoke(null); + } catch (Exception ex) { + fail("Unable to reinitialize ThreadContext"); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java new file mode 100644 index 00000000000..a742bcd226c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.TimestampMessage; +import org.apache.logging.log4j.plugins.Factory; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Confirms that if you log a {@link TimestampMessage} then there are no unnecessary calls to {@link Clock}. + *

+ * See LOG4J2-744. + *

+ */ +@LoggerContextSource("log4j2-744.xml") +public class TimestampMessageTest { + @Factory + public static Clock poisonClock() { + return new PoisonClock(); + } + + @Test + public void testTimestampMessage(final LoggerContext context, @Named("List") final ListAppender list) { + final Logger log = context.getLogger("TimestampMessageTest"); + log.info((Message) new TimeMsg("Message with embedded timestamp", 123456789000L)); + final List msgs = list.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size()); + final String NL = System.lineSeparator(); + assertEquals("123456789000 Message with embedded timestamp" + NL, msgs.get(0)); + } + + public static class PoisonClock implements Clock { + public PoisonClock() { + super(); + // Breakpoint here for debugging. + } + + @Override + public long currentTimeMillis() { + throw new RuntimeException("This should not have been called"); + } + } + + static class TimeMsg extends SimpleMessage implements TimestampMessage { + private static final long serialVersionUID = 1L; + private final long timestamp; + + public TimeMsg(final String msg, final long timestamp) { + super(msg); + this.timestamp = timestamp; + } + + @Override + public long getTimestamp() { + return timestamp; + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/XmlEvents.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/XmlEvents.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/XmlEvents.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/XmlEvents.java index c9b6539aac3..64179537361 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/XmlEvents.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/XmlEvents.java @@ -16,25 +16,19 @@ */ package org.apache.logging.log4j.core; -import java.util.Locale; - import org.apache.logging.log4j.EventLogger; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.message.AsynchronouslyFormattable; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.junit.ClassRule; -import org.junit.Test; - -/** - * - */ -public class XmlEvents { +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; - private static final String CONFIG = "xml-events.xml"; +import java.util.Locale; - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); +@LoggerContextSource("xml-events.xml") +@Disabled("TODO") +public class XmlEvents { @Test public void testEvents() { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderQueueFullPolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderQueueFullPolicyTest.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderQueueFullPolicyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderQueueFullPolicyTest.java index bdd2aa68f93..e2acfea2508 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderQueueFullPolicyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderQueueFullPolicyTest.java @@ -16,22 +16,22 @@ */ package org.apache.logging.log4j.core.appender; -import java.lang.reflect.Field; -import java.util.concurrent.atomic.AtomicLong; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.async.DefaultAsyncQueueFullPolicy; import org.apache.logging.log4j.core.async.EventRoute; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.BlockingAppender; -import org.junit.After; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; +import org.apache.logging.log4j.core.test.appender.BlockingAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicLong; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests the AsyncAppender (LOG4J2-1080) event routing logic: @@ -43,21 +43,19 @@ * else queue.add(event) // blocking call * */ +@LoggerContextSource("log4j-asynch-queue-full.xml") +@Tag("sleepy") public class AsyncAppenderQueueFullPolicyTest { - private static final String CONFIG = "log4j-asynch-queue-full.xml"; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); - - private BlockingAppender blockingAppender; - private AsyncAppender asyncAppender; - private CountingAsyncQueueFullPolicy policy; - @Before - public void before() throws Exception { - blockingAppender = context.getAppender("Block", BlockingAppender.class); - asyncAppender = context.getAppender("Async", AsyncAppender.class); + private final BlockingAppender blockingAppender; + private final AsyncAppender asyncAppender; + private final CountingAsyncQueueFullPolicy policy; + public AsyncAppenderQueueFullPolicyTest( + @Named("Block") final BlockingAppender blockingAppender, @Named("Async") final AsyncAppender asyncAppender) + throws Exception { + this.blockingAppender = blockingAppender; + this.asyncAppender = asyncAppender; final Field field = AsyncAppender.class.getDeclaredField("asyncQueueFullPolicy"); field.setAccessible(true); policy = new CountingAsyncQueueFullPolicy(); @@ -65,7 +63,7 @@ public void before() throws Exception { policy.queueFull.set(0L); } - @After + @AfterEach public void after() { blockingAppender.running = false; policy.queueFull.set(0L); @@ -84,8 +82,8 @@ public void testRouter() throws Exception { Thread.yield(); // wait until background thread takes one element off the queue } logger.info("event 5 - now the queue is full"); - assertEquals("queue remaining capacity", 0, asyncAppender.getQueueRemainingCapacity()); - assertEquals("EventRouter invocations", 0, policy.queueFull.get()); + assertEquals(0, asyncAppender.getQueueRemainingCapacity(), "queue remaining capacity"); + assertEquals(0, policy.queueFull.get(), "EventRouter invocations"); final Thread release = new Thread("AsyncAppenderReleaser") { @Override diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java new file mode 100644 index 00000000000..79e1049087a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/AsyncAppenderTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LoggingException; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +public class AsyncAppenderTest { + + static void exceptionTest(final LoggerContext context) throws InterruptedException { + final ExtendedLogger logger = context.getLogger(AsyncAppender.class); + final Exception parent = new IllegalStateException("Test"); + final Throwable child = new LoggingException("This is a test", parent); + logger.error("This is a test", child); + final ListAppender appender = context.getConfiguration().getAppender("List"); + final List messages; + try { + messages = appender.getMessages(1, 2, TimeUnit.SECONDS); + } finally { + appender.clear(); + } + assertNotNull(messages); + assertEquals(1, messages.size()); + assertTrue(messages.get(0).contains(parent.getClass().getName())); + } + + static void rewriteTest(final LoggerContext context) throws InterruptedException { + final ExtendedLogger logger = context.getLogger(AsyncAppender.class); + logger.error("This is a test"); + logger.warn("Hello world!"); + final ListAppender appender = context.getConfiguration().getAppender("List"); + final List messages; + try { + messages = appender.getMessages(2, 2, TimeUnit.SECONDS); + } finally { + appender.clear(); + } + assertNotNull(messages); + assertEquals(2, messages.size()); + final String messagePrefix = AsyncAppenderTest.class.getName() + " rewriteTest "; + assertEquals(messagePrefix + "This is a test", messages.get(0)); + assertEquals(messagePrefix + "Hello world!", messages.get(1)); + } + + @Test + @LoggerContextSource("BlockingQueueFactory-ArrayBlockingQueue.xml") + public void testArrayBlockingQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("log4j-asynch.xml") + public void testDefaultAsyncAppenderConfig(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + + List backgroundThreads = Thread.getAllStackTraces().keySet().stream() + .filter(AsyncAppenderEventDispatcher.class::isInstance) + .collect(Collectors.toList()); + assertFalse(backgroundThreads.isEmpty(), "Failed to locate background thread"); + for (Thread thread : backgroundThreads) { + assertTrue(thread.isDaemon(), "AsyncAppender should use daemon threads"); + } + } + + @Test + @Tag("disruptor") + @LoggerContextSource("BlockingQueueFactory-DisruptorBlockingQueue.xml") + public void testDisruptorBlockingQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("log4j-asynch.xml") + public void testGetAppenderRefStrings(final LoggerContext context) throws InterruptedException { + final AsyncAppender appender = context.getConfiguration().getAppender("Async"); + assertArrayEquals(new String[] {"List"}, appender.getAppenderRefStrings()); + assertNotSame(appender.getAppenderRefStrings(), appender.getAppenderRefStrings()); + } + + @Test + @LoggerContextSource("log4j-asynch.xml") + public void testGetErrorRef(final LoggerContext context) throws InterruptedException { + final AsyncAppender appender = context.getConfiguration().getAppender("Async"); + assertEquals("STDOUT", appender.getErrorRef()); + } + + @Test + @Tag("jctools") + @LoggerContextSource("BlockingQueueFactory-JCToolsBlockingQueue.xml") + public void testJcToolsBlockingQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("BlockingQueueFactory-LinkedTransferQueue.xml") + public void testLinkedTransferQueue(final LoggerContext context) throws InterruptedException { + rewriteTest(context); + exceptionTest(context); + } + + @Test + @LoggerContextSource("log4j-asynch-no-location.xml") + public void testNoLocationInformation(final LoggerContext context, @Named("List") final ListAppender appender) throws InterruptedException { + final ExtendedLogger logger = context.getLogger(getClass()); + logger.error("This is a test"); + logger.warn("Hello world!"); + final List messages; + try { + messages = appender.getMessages(2, 2, TimeUnit.SECONDS); + } finally { + appender.clear(); + } + assertNotNull(messages); + assertEquals(2, messages.size()); + assertEquals("? This is a test", messages.get(0)); + assertEquals("? Hello world!", messages.get(1)); + } + + @Test + @Timeout(5) + @LoggerContextSource("log4j-asynch-shutdownTimeout.xml") + public void testShutdownTimeout(final LoggerContext context) { + context.getLogger("Logger").info("This is a test"); + context.stop(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConfigurationTestUtils.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConfigurationTestUtils.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConfigurationTestUtils.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConfigurationTestUtils.java index c0e4dc8c92e..54a7676faf5 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConfigurationTestUtils.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConfigurationTestUtils.java @@ -1,37 +1,37 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.LoggerConfig; - -public class ConfigurationTestUtils { - - static void updateLoggers(final Appender appender, final Configuration config) { - final Level level = null; - final Filter filter = null; - for (final LoggerConfig loggerConfig : config.getLoggers().values()) { - loggerConfig.addAppender(appender, level, filter); - } - config.getRootLogger().addAppender(appender, level, filter); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; + +public class ConfigurationTestUtils { + + static void updateLoggers(final Appender appender, final Configuration config) { + final Level level = null; + final Filter filter = null; + for (final LoggerConfig loggerConfig : config.getLoggers().values()) { + loggerConfig.addAppender(appender, level, filter); + } + config.getRootLogger().addAppender(appender, level, filter); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiMessagesMain.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira180Main.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira272Main.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleJira319Main.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleLayoutMain.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderAnsiStyleNameLayoutMain.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.java new file mode 100644 index 00000000000..8ff76240563 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.ErrorHandler; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ConsoleAppenderBuilderTest { + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1620 + */ + @Test + public void testDefaultImmediateFlush() { + assertTrue(ConsoleAppender.newBuilder().isImmediateFlush()); + } + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1636 + * + * Tested with Oracle 7 and 8 and IBM Java 8. + */ + @Test + public void testDefaultLayoutDefaultCharset() { + final ConsoleAppender appender = ConsoleAppender.newBuilder().setName("test").build(); + final PatternLayout layout = (PatternLayout) appender.getLayout(); + final String charsetName = System.getProperty("sun.stdout.encoding"); + final String expectedName = charsetName != null ? charsetName : Charset.defaultCharset().name(); + assertEquals(expectedName, layout.getCharset().name()); + } + + /** + * Tests https://issues.apache.org/jira/browse/LOG4J2-2441 + */ + @Test + public void testSetNullErrorHandlerIsNotAllowed() { + final ConsoleAppender appender = ConsoleAppender.newBuilder().setName("test").build(); + ErrorHandler handler = appender.getHandler(); + assertNotNull(handler); + // This could likely be allowed to throw, but we're just testing that + // setting null does not actually set a null handler. + appender.setHandler(null); + assertSame(handler, appender.getHandler()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java similarity index 99% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java index 8437232a0be..7587bf6ef99 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderDefaultSuppressedThrowable.java @@ -30,7 +30,7 @@ *

* Running from a Windows command line from the root of the project: *

- * + * *
  * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderNoAnsiStyleLayoutMain log4j-core/target/test-classes/log4j2-console-style-ansi.xml
  * 
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutDefaultMain.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutLogbackMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutLogbackMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutLogbackMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutLogbackMain.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderHighlightLayoutMain.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java index f8008060c51..5461669fbfd 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiMessageMain.java @@ -24,11 +24,13 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.core.test.categories.Layouts; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; /** * Shows how to use ANSI escape codes to color messages. Each message is printed to the console in color, but the rest @@ -59,6 +61,7 @@ public static void main(final String[] args) { * This is a @Test method to make it easy to run from a command line with {@code mvn -Dtest=FQCN test} */ @Test + @ResourceLock(Resources.SYSTEM_PROPERTIES) public void test() { test(null); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java index 34901ff55d7..334a4e15458 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJAnsiXExceptionMain.java @@ -21,7 +21,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.core.test.categories.Layouts; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; import org.junit.Test; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderJira1002ShortThrowableLayoutMain.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderNoAnsiStyleLayoutMain.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java new file mode 100644 index 00000000000..148d079427b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.ConsoleAppender.Target; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junitpioneer.jupiter.SetSystemProperty; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.atLeastOnce; + +@ExtendWith(MockitoExtension.class) +@SetSystemProperty(key = "log4j.skipJansi", value = "true") +public class ConsoleAppenderTest { + + ByteArrayOutputStream baos; + + @Mock + PrintStream psMock; + + @BeforeEach + public void before() { + baos = new ByteArrayOutputStream(); + } + + private enum SystemSetter { + SYSTEM_OUT { + @Override + void systemSet(final PrintStream printStream) { + System.setOut(printStream); + } + }, + SYSTEM_ERR { + @Override + void systemSet(final PrintStream printStream) { + System.setErr(printStream); + } + }, + ; + abstract void systemSet(PrintStream printStream); + } + + private void testConsoleStreamManagerDoesNotClose(final PrintStream ps, final Target targetName, final SystemSetter systemSetter) { + try { + systemSetter.systemSet(psMock); + final Layout layout = PatternLayout.newBuilder().setAlwaysWriteExceptions(true).build(); + final ConsoleAppender app = ConsoleAppender.newBuilder().setLayout(layout).setTarget(targetName) + .setName("Console").setIgnoreExceptions(false).build(); + app.start(); + assertTrue(app.isStarted(), "Appender did not start"); + + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("TestLogger") // + .setLoggerFqcn(ConsoleAppenderTest.class.getName()) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Test")) // + .build(); + app.append(event); + + app.stop(); + assertFalse(app.isStarted(), "Appender did not stop"); + } finally { + systemSetter.systemSet(ps); + } + then(psMock).should(atLeastOnce()).write(any(byte[].class), anyInt(), anyInt()); + then(psMock).should(atLeastOnce()).flush(); + } + + @Test + public void testFollowSystemErr() { + testFollowSystemPrintStream(System.err, Target.SYSTEM_ERR, SystemSetter.SYSTEM_ERR); + } + + @Test + public void testFollowSystemOut() { + testFollowSystemPrintStream(System.out, Target.SYSTEM_OUT, SystemSetter.SYSTEM_OUT); + } + + private void testFollowSystemPrintStream(final PrintStream ps, final Target target, final SystemSetter systemSetter) { + final ConsoleAppender app = ConsoleAppender.newBuilder().setTarget(target).setFollow(true) + .setIgnoreExceptions(false).setName("test").build(); + assertEquals(target, app.getTarget()); + app.start(); + try { + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("TestLogger") // + .setLoggerFqcn(ConsoleAppenderTest.class.getName()) // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Test")) // + .build(); + + assertTrue(app.isStarted(), "Appender did not start"); + systemSetter.systemSet(new PrintStream(baos)); + try { + app.append(event); + } finally { + systemSetter.systemSet(ps); + } + final String msg = baos.toString(); + assertNotNull(msg, "No message"); + assertTrue(msg.endsWith("Test" + Strings.LINE_SEPARATOR), "Incorrect message: \"" + msg + "\""); + } finally { + app.stop(); + } + assertFalse(app.isStarted(), "Appender did not stop"); + } + + @Test + public void testSystemErrStreamManagerDoesNotClose() { + testConsoleStreamManagerDoesNotClose(System.err, Target.SYSTEM_ERR, SystemSetter.SYSTEM_ERR); + } + + @Test + public void testSystemOutStreamManagerDoesNotClose() { + testConsoleStreamManagerDoesNotClose(System.out, Target.SYSTEM_OUT, SystemSetter.SYSTEM_OUT); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.java new file mode 100644 index 00000000000..a3374943e21 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FailoverAppenderTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.appender.FailOnceAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@LoggerContextSource("log4j-failover.xml") +@Tag("sleepy") +public class FailoverAppenderTest { + private final ListAppender app; + private final FailOnceAppender foApp; + private final Logger logger; + private final Logger onceLogger; + + public FailoverAppenderTest(final LoggerContext context, @Named("List") final ListAppender app, + @Named("Once") final FailOnceAppender foApp) { + this.app = app; + this.foApp = foApp; + logger = context.getLogger("LoggerTest"); + onceLogger = context.getLogger("Once"); + } + + @AfterEach + public void tearDown() throws Exception { + app.clear(); + } + + @Test + public void testFailover() { + logger.error("This is a test"); + List events = app.getEvents(); + assertNotNull(events); + assertEquals(events.size(), 1, "Incorrect number of events. Should be 1 is " + events.size()); + app.clear(); + logger.error("This is a test"); + events = app.getEvents(); + assertNotNull(events); + assertEquals(events.size(), 1, "Incorrect number of events. Should be 1 is " + events.size()); + } + + @Test + @Tag("sleepy") + public void testRecovery() throws Exception { + onceLogger.error("Fail once"); + onceLogger.error("Fail again"); + List events = app.getEvents(); + assertNotNull(events); + assertEquals(events.size(), 2, "Incorrect number of events. Should be 2 is " + events.size()); + app.clear(); + Thread.sleep(1100); + onceLogger.error("Fail after recovery interval"); + onceLogger.error("Second log message"); + events = app.getEvents(); + assertEquals(events.size(), 0, "Did not recover"); + events = foApp.drainEvents(); + assertEquals(events.size(), 2, "Incorrect number of events in primary appender"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderBuilderTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderBuilderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderBuilderTest.java index 8305a07ff0f..f27fb8ff6ba 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderBuilderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderBuilderTest.java @@ -16,8 +16,9 @@ */ package org.apache.logging.log4j.core.appender; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; public class FileAppenderBuilderTest { @@ -26,6 +27,6 @@ public class FileAppenderBuilderTest { */ @Test public void testDefaultImmediateFlush() { - Assert.assertTrue(FileAppender.newBuilder().isImmediateFlush()); + assertTrue(FileAppender.newBuilder().isImmediateFlush()); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.java new file mode 100644 index 00000000000..7b07328b4ed --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.commons.lang3.SystemUtils; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.FileUtils; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.stream.Stream; + +import static org.apache.logging.log4j.util.Unbox.box; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * Tests {@link FileAppender}. + */ +@CleanUpDirectories(FileAppenderPermissionsTest.DIR) +public class FileAppenderPermissionsTest { + + static final String DIR = "target/permissions1"; + + @BeforeAll + public static void beforeClass() { + assumeTrue(FileUtils.isFilePosixAttributeViewSupported()); + } + + @ParameterizedTest + @CsvSource({ "rwxrwxrwx,true,2", "rw-r--r--,false,3", "rw-------,true,4", "rw-rw----,false,5" }) + @Tag("sleepy") + public void testFilePermissionsAPI(final String filePermissions, final boolean createOnDemand, final int fileIndex) + throws Exception { + final File file = new File(DIR, "AppenderTest-" + fileIndex + ".log"); + final Path path = file.toPath(); + final Layout layout = PatternLayout.newBuilder().setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .build(); + // @formatter:off + final FileAppender appender = FileAppender.newBuilder() + .setFileName(file.getAbsolutePath()) + .setName("test") + .setImmediateFlush(false) + .setIgnoreExceptions(false) + .setBufferedIo(false) + .setBufferSize(1) + .setLayout(layout) + .setCreateOnDemand(createOnDemand) + .setFilePermissions(filePermissions) + .build(); + // @formatter:on + try { + appender.start(); + assertTrue(appender.isStarted(), "Appender did not start"); + assertNotEquals(createOnDemand, Files.exists(path)); + long curLen = file.length(); + long prevLen = curLen; + assertEquals(curLen, 0, "File length: " + curLen); + for (int i = 0; i < 100; ++i) { + final LogEvent event = Log4jLogEvent.newBuilder().setLoggerName("TestLogger") // + .setLoggerFqcn(FileAppenderPermissionsTest.class.getName()).setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Test")).setThreadName(this.getClass().getSimpleName()) // + .setTimeMillis(System.currentTimeMillis()).build(); + try { + appender.append(event); + curLen = file.length(); + assertTrue(curLen > prevLen, "File length: " + curLen); + // Give up control long enough for another thread/process to occasionally do something. + Thread.sleep(25); + } catch (final Exception ex) { + throw ex; + } + prevLen = curLen; + } + assertEquals(filePermissions, PosixFilePermissions.toString(Files.getPosixFilePermissions(path))); + } finally { + appender.stop(); + } + assertFalse(appender.isStarted(), "Appender did not stop"); + } + + @ParameterizedTest + @CsvSource({ "rwxrwxrwx,2", "rw-r--r--,3", "rw-------,4", "rw-rw----,5" }) + @Tag("sleepy") + public void testFileUserGroupAPI(final String filePermissions, final int fileIndex) + throws Exception { + final File file = new File(DIR, "AppenderTest-" + (1000 + fileIndex) + ".log"); + final Path path = file.toPath(); + final String user = findAUser(); + assertNotNull(user); + final String group = findAGroup(user); + assertNotNull(group); + + final Layout layout = PatternLayout.newBuilder().setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .build(); + // @formatter:off + final FileAppender appender = FileAppender.newBuilder() + .setFileName(file.getAbsolutePath()) + .setName("test") + .setImmediateFlush(true) + .setIgnoreExceptions(false) + .setBufferedIo(false) + .setBufferSize(1) + .setLayout(layout) + .setFilePermissions(filePermissions) + .setFileOwner(user) + .setFileGroup(group) + .build(); + // @formatter:on + try { + appender.start(); + assertTrue(appender.isStarted(), "Appender did not start"); + long curLen = file.length(); + long prevLen = curLen; + assertEquals(curLen, 0, file + " File length: " + curLen); + for (int i = 0; i < 100; ++i) { + final LogEvent event = Log4jLogEvent.newBuilder().setLoggerName("TestLogger") // + .setLoggerFqcn(FileAppenderPermissionsTest.class.getName()).setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Test")).setThreadName(this.getClass().getSimpleName()) // + .setTimeMillis(System.currentTimeMillis()).build(); + try { + appender.append(event); + curLen = file.length(); + assertTrue(curLen > prevLen, "File length: " + curLen); + // Give up control long enough for another thread/process to occasionally do something. + Thread.sleep(25); + } catch (final Exception ex) { + throw ex; + } + prevLen = curLen; + } + assertEquals(filePermissions, PosixFilePermissions.toString(Files.getPosixFilePermissions(path))); + assertEquals(user, Files.getOwner(path).getName()); + assertEquals(group, Files.readAttributes(path, PosixFileAttributes.class).group().getName()); + } finally { + appender.stop(); + } + assertFalse(appender.isStarted(), "Appender did not stop"); + } + + @Test + @LoggerContextSource(value = "log4j-posix.xml", timeout = 10) + void testFilePermissions(final LoggerContext context) throws IOException { + final ExtendedLogger logger = context.getLogger(getClass()); + for (int i = 0; i < 1000; i++) { + logger.debug("This is test message number {}", box(i)); + } + final String permissions = PosixFilePermissions.toString( + Files.getPosixFilePermissions(Paths.get("target/permissions1/AppenderTest-1.log"))); + assertEquals("rw-------", permissions); + } + + public static String findAGroup(final String user) throws IOException { + if (SystemUtils.IS_OS_MAC_OSX) { + return "staff"; + } + try (final Stream lines = Files.lines(Paths.get("/etc/group"))) { + return lines.filter(group -> !group.startsWith(user) && group.contains(user)) + .map(group -> group.substring(0, group.indexOf(':'))) + .findAny() + .orElse(user); + } + } + + private static String findAUser() throws IOException { + // On jenkins build within ubuntu, it is not possible to chmod to another user + return System.getProperty("user.name"); + } + + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java new file mode 100644 index 00000000000..28cd98f0bda --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests {@link FileAppender}. + */ +@CleanUpFiles(FileAppenderTest.FILE_NAME) +@Tag("sleepy") +public class FileAppenderTest { + + static final String FILE_NAME = "target/fileAppenderTest.log"; + private static final Path PATH = Paths.get(FILE_NAME); + private static final int THREADS = 2; + + @AfterAll + public static void cleanupClass() { + assertFalse(AbstractManager.hasManager(FILE_NAME), "Manager for " + FILE_NAME + " not removed"); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + public void testAppender(final boolean createOnDemand) throws Exception { + final int logEventCount = 1; + writer(false, logEventCount, "test", createOnDemand, false); + verifyFile(logEventCount); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + public void testLazyCreate(final boolean createOnDemand) throws Exception { + final Layout layout = createPatternLayout(); + // @formatter:off + final FileAppender appender = FileAppender.newBuilder() + .setFileName(FILE_NAME) + .setName("test") + .setImmediateFlush(false) + .setIgnoreExceptions(false) + .setBufferedIo(false) + .setBufferSize(1) + .setLayout(layout) + .setCreateOnDemand(createOnDemand) + .build(); + // @formatter:on + assertEquals(createOnDemand, appender.getManager().isCreateOnDemand()); + try { + assertNotEquals(createOnDemand, Files.exists(PATH)); + appender.start(); + assertNotEquals(createOnDemand, Files.exists(PATH)); + } finally { + appender.stop(); + } + assertNotEquals(createOnDemand, Files.exists(PATH)); + } + + private static PatternLayout createPatternLayout() { + return PatternLayout.newBuilder().setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .build(); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + public void testSmallestBufferSize(final boolean createOnDemand) throws Exception { + final Layout layout = createPatternLayout(); + // @formatter:off + final FileAppender appender = FileAppender.newBuilder() + .setFileName(FILE_NAME) + .setName("test") + .setImmediateFlush(false) + .setIgnoreExceptions(false) + .setBufferedIo(false) + .setBufferSize(1) + .setLayout(layout) + .setCreateOnDemand(createOnDemand) + .build(); + // @formatter:on + try { + appender.start(); + final File file = new File(FILE_NAME); + assertTrue(appender.isStarted(), "Appender did not start"); + assertNotEquals(createOnDemand, Files.exists(PATH)); + long curLen = file.length(); + long prevLen = curLen; + assertEquals(0, curLen, "File length: " + curLen); + for (int i = 0; i < 100; ++i) { + // @formatter:off + final LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName("TestLogger") + .setLoggerFqcn(FileAppenderTest.class.getName()) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("Test")) + .setThreadName(this.getClass().getSimpleName()) + .setTimeMillis(System.currentTimeMillis()) + .build(); + // @formatter:on + appender.append(event); + curLen = file.length(); + assertTrue(curLen > prevLen, "File length: " + curLen); + // Give up control long enough for another thread/process to occasionally do something. + Thread.sleep(25); + prevLen = curLen; + } + } finally { + appender.stop(); + } + assertFalse(appender.isStarted(), "Appender did not stop"); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + public void testLockingAppender(final boolean createOnDemand) throws Exception { + final int logEventCount = 1; + writer(true, logEventCount, "test", createOnDemand, false); + verifyFile(logEventCount); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + public void testMultipleAppenderThreads(final boolean createOnDemand) throws Exception { + testMultipleLockingAppenderThreads(false, THREADS, createOnDemand); + } + + private void testMultipleLockingAppenderThreads(final boolean lock, final int threadCount, boolean createOnDemand) + throws InterruptedException, Exception { + final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount); + final AtomicReference throwableRef = new AtomicReference<>(); + final int logEventCount = 100; + final Runnable runnable = new FileWriterRunnable(createOnDemand, lock, logEventCount, throwableRef); + for (int i = 0; i < threadCount; ++i) { + threadPool.execute(runnable); + } + threadPool.shutdown(); + boolean stopped = false; + for (int i = 0; i < 20; i++) { + // intentional assignment + if (stopped = threadPool.awaitTermination(1, TimeUnit.SECONDS)) { + break; + } + } + if (throwableRef.get() != null) { + Throwables.rethrow(throwableRef.get()); + } + assertTrue(stopped, "The thread pool has not shutdown: " + threadPool); + verifyFile(threadCount * logEventCount); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + public void testMultipleLockingAppenders(final boolean createOnDemand) throws Exception { + testMultipleLockingAppenderThreads(true, THREADS, createOnDemand); + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + @Disabled + public void testMultipleVMs(final boolean createOnDemand) throws Exception { + final String classPath = System.getProperty("java.class.path"); + final int logEventCount = 10; + final int processCount = 3; + final Process[] processes = new Process[processCount]; + final ProcessBuilder[] builders = new ProcessBuilder[processCount]; + for (int index = 0; index < processCount; ++index) { + builders[index] = new ProcessBuilder("java", "-cp", classPath, ProcessTest.class.getName(), + "Process " + index, Integer.toString(logEventCount), "true", Boolean.toString(createOnDemand)); + } + for (int index = 0; index < processCount; ++index) { + processes[index] = builders[index].start(); + } + for (int index = 0; index < processCount; ++index) { + final Process process = processes[index]; + // System.out.println("Process " + index + " exited with " + p.waitFor()); + try (final BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } + process.destroy(); + } + verifyFile(logEventCount * processCount); + } + + private static void writer(final boolean locking, final int logEventCount, final String name, final boolean createOnDemand, + final boolean concurrent) throws Exception { + final Layout layout = createPatternLayout(); + // @formatter:off + final FileAppender appender = FileAppender.newBuilder() + .setFileName(FILE_NAME) + .setName("test") + .setImmediateFlush(false) + .setIgnoreExceptions(false) + .setLocking(locking) + .setBufferedIo(false) + .setLayout(layout) + .setCreateOnDemand(createOnDemand) + .build(); + // @formatter:on + assertEquals(createOnDemand, appender.getManager().isCreateOnDemand()); + try { + appender.start(); + assertTrue(appender.isStarted(), "Appender did not start"); + final boolean exists = Files.exists(PATH); + final String msg = String.format("concurrent = %s, createOnDemand = %s, file exists = %s", concurrent, createOnDemand, + exists); + // If concurrent the file might have been created (or not.) + // Can't really test createOnDemand && concurrent. + final boolean expectFileCreated = !createOnDemand; + if (concurrent && expectFileCreated) { + assertTrue(exists, msg); + } else if (expectFileCreated) { + assertNotEquals(createOnDemand, exists, msg); + } + for (int i = 0; i < logEventCount; ++i) { + // @formatter:off + final LogEvent logEvent = Log4jLogEvent.newBuilder() + .setLoggerName("TestLogger") + .setLoggerFqcn(FileAppenderTest.class.getName()) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("Test")) + .setThreadName(name) + .setTimeMillis(System.currentTimeMillis()) + .build(); + // @formatter:on + appender.append(logEvent); + Thread.sleep(25); // Give up control long enough for another thread/process to occasionally do something. + } + } finally { + appender.stop(); + } + assertFalse(appender.isStarted(), "Appender did not stop"); + } + + private void verifyFile(final int count) throws Exception { + // String expected = "[\\w]* \\[\\s*\\] INFO TestLogger - Test$"; + final String expected = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3} \\[[^\\]]*\\] INFO TestLogger - Test"; + final Pattern pattern = Pattern.compile(expected); + int lines = 0; + try (final BufferedReader is = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_NAME)))) { + String str; + while (is.ready()) { + str = is.readLine(); + // System.out.println(str); + ++lines; + final Matcher matcher = pattern.matcher(str); + assertTrue(matcher.matches(), "Unexpected data: " + str); + } + } + assertEquals(count, lines); + } + + public static class FileWriterRunnable implements Runnable { + private final boolean createOnDemand; + private final boolean lock; + private final int logEventCount; + private final AtomicReference throwableRef; + + public FileWriterRunnable( + boolean createOnDemand, final boolean lock, final int logEventCount, final AtomicReference throwableRef) { + this.createOnDemand = createOnDemand; + this.lock = lock; + this.logEventCount = logEventCount; + this.throwableRef = throwableRef; + } + + @Override + public void run() { + final Thread thread = Thread.currentThread(); + + try { + writer(lock, logEventCount, thread.getName(), createOnDemand, true); + } catch (final Throwable e) { + throwableRef.set(e); + } + } + } + + public static class ProcessTest { + + public static void main(final String[] args) { + + if (args.length != 3) { + System.out.println("Required arguments 'id', 'count' and 'lock' not provided"); + System.exit(-1); + } + final String id = args[0]; + + final int count = Integers.parseInt(args[1]); + + if (count <= 0) { + System.out.println("Invalid count value: " + args[1]); + System.exit(-1); + } + final boolean lock = Boolean.parseBoolean(args[2]); + + final boolean createOnDemand = Boolean.parseBoolean(args[2]); + + // System.out.println("Got arguments " + id + ", " + count + ", " + lock); + + try { + writer(lock, count, id, createOnDemand, true); + // thread.sleep(50); + + } catch (final Exception e) { + Throwables.rethrow(e); + } + + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java new file mode 100644 index 00000000000..6695f51be9b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.io.Serializable; +import java.util.concurrent.TimeUnit; + +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Hanging") +public class HangingAppender extends AbstractAppender { + + private final long delay; + private final long startupDelay; + private final long shutdownDelay; + + public HangingAppender(final String name, final long delay, final long startupDelay, final long shutdownDelay) { + super(name, null, null, true, Property.EMPTY_ARRAY); + this.delay = delay; + this.startupDelay = startupDelay; + this.shutdownDelay = shutdownDelay; + } + + @Override + public void append(final LogEvent event) { + try { + Thread.sleep(delay); + } catch (final InterruptedException ignore) { + // ignore + } + } + + @PluginFactory + public static HangingAppender createAppender( + @PluginAttribute + @Required(message = "No name provided for HangingAppender") + final String name, + @PluginAttribute final long delay, + @PluginAttribute final long startupDelay, + @PluginAttribute final long shutdownDelay, + @PluginElement final Layout layout, + @PluginElement final Filter filter) { + return new HangingAppender(name, delay, startupDelay, shutdownDelay); + } + + @Override + public void start() { + try { + Thread.sleep(startupDelay); + } catch (final InterruptedException ignore) { + // ignore + } + super.start(); + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + super.stop(timeout, timeUnit, false); + try { + Thread.sleep(shutdownDelay); + } catch (final InterruptedException ignore) { + // ignore + } + setStopped(); + return true; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java index 75d49298004..cf386e37cfe 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java @@ -22,40 +22,37 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.test.appender.InMemoryAppender; +import org.apache.logging.log4j.core.test.appender.InMemoryAppender; import org.apache.logging.log4j.util.Strings; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class InMemoryAppenderTest { @Test public void testAppender() { final Layout layout = PatternLayout.createDefaultLayout(); final boolean writeHeader = true; - final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader); + final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader, null); final String expectedHeader = null; assertMessage("Test", app, expectedHeader); } @Test public void testHeaderRequested() { - final PatternLayout layout = PatternLayout.newBuilder().withHeader("HEADERHEADER").build(); + final PatternLayout layout = PatternLayout.newBuilder().setHeader("HEADERHEADER").build(); final boolean writeHeader = true; - final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader); + final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader, null); final String expectedHeader = "HEADERHEADER"; assertMessage("Test", app, expectedHeader); } @Test public void testHeaderSuppressed() { - final PatternLayout layout = PatternLayout.newBuilder().withHeader("HEADERHEADER").build(); + final PatternLayout layout = PatternLayout.newBuilder().setHeader("HEADERHEADER").build(); final boolean writeHeader = false; - final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader); + final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader, null); final String expectedHeader = null; assertMessage("Test", app, expectedHeader); } @@ -68,15 +65,15 @@ private void assertMessage(final String string, final InMemoryAppender app, fina .setMessage(new SimpleMessage("Test")) // .build(); app.start(); - assertTrue("Appender did not start", app.isStarted()); + assertTrue(app.isStarted(), "Appender did not start"); app.append(event); app.append(event); final String msg = app.toString(); - assertNotNull("No message", msg); + assertNotNull(msg, "No message"); final String expectedHeader = header == null ? "" : header; final String expected = expectedHeader + "Test" + Strings.LINE_SEPARATOR + "Test" + Strings.LINE_SEPARATOR; - assertTrue("Incorrect message: " + msg, msg.equals(expected)); + assertEquals(expected, msg, "Incorrect message: " + msg); app.stop(); - assertFalse("Appender did not stop", app.isStarted()); + assertFalse(app.isStarted(), "Appender did not stop"); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/JansiConsoleAppenderJira965.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/Jira739Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/Jira739Test.java similarity index 99% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/Jira739Test.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/Jira739Test.java index f46af07e2f1..bfb233ef658 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/Jira739Test.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/Jira739Test.java @@ -27,7 +27,7 @@ *

* Running from a Windows command line from the root of the project: *

- * + * *
  * java -classpath log4j-core\target\test-classes;log4j-core\target\classes;log4j-api\target\classes;%HOME%\.m2\repository\org\fusesource\jansi\jansi\1.14\jansi-1.14.jar; org.apache.logging.log4j.core.appender.ConsoleAppenderAnsiMessagesMain log4j-core/target/test-classes/log4j2-console.xml
  * 
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppenderTest.java new file mode 100644 index 00000000000..d33b0ca6c55 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppenderTest.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests that logged strings appear in the file, that the initial file size is the specified region length, + * that the file is extended by region length when necessary, and that the file is shrunk to its actual usage when done. + * + * @since 2.1 + */ +@CleanUpFiles({ + "target/MemoryMappedFileAppenderTest.log", + "target/MemoryMappedFileAppenderRemapTest.log", + "target/MemoryMappedFileAppenderLocationTest.log" +}) +public class MemoryMappedFileAppenderTest { + + @Test + @LoggerContextSource("MemoryMappedFileAppenderTest.xml") + public void testMemMapBasics(final LoggerContext context) throws Exception { + final Logger log = context.getLogger(getClass()); + final Path logFile = Paths.get("target", "MemoryMappedFileAppenderTest.log"); + try { + log.warn("Test log1"); + assertTrue(Files.exists(logFile)); + assertEquals(MemoryMappedFileManager.DEFAULT_REGION_LENGTH, Files.size(logFile)); + log.warn("Test log2"); + assertEquals(MemoryMappedFileManager.DEFAULT_REGION_LENGTH, Files.size(logFile)); + } finally { + context.stop(); + } + final int LINESEP = System.lineSeparator().length(); + assertEquals(18 + 2 * LINESEP, Files.size(logFile)); + + final List lines = Files.readAllLines(logFile); + assertThat(lines, both(hasSize(2)).and(contains("Test log1", "Test log2"))); + } + + @Test + @LoggerContextSource("MemoryMappedFileAppenderRemapTest.xml") + public void testMemMapExtendsIfNeeded(final LoggerContext context) throws Exception { + final Logger log = context.getLogger(getClass()); + final Path logFile = Paths.get("target", "MemoryMappedFileAppenderRemapTest.log"); + final char[] text = new char[256]; + Arrays.fill(text, 'A'); + final String str = new String(text); + try { + log.warn("Test log1"); + assertTrue(Files.exists(logFile)); + assertEquals(256, Files.size(logFile)); + log.warn(str); + assertEquals(2 * 256, Files.size(logFile)); + log.warn(str); + assertEquals(3 * 256, Files.size(logFile)); + } finally { + context.stop(); + } + assertEquals(521 + 3 * System.lineSeparator().length(), Files.size(logFile), "Expected file size to shrink"); + + final List lines = Files.readAllLines(logFile); + assertThat(lines, both(hasSize(3)).and(contains("Test log1", str, str))); + } + + @Test + @LoggerContextSource("MemoryMappedFileAppenderLocationTest.xml") + void testMemMapLocation(final LoggerContext context) throws Exception { + final Logger log = context.getLogger(getClass()); + final Path logFile = Paths.get("target", "MemoryMappedFileAppenderLocationTest.log"); + final int expectedFileLength = Integers.ceilingNextPowerOfTwo(32000); + assertEquals(32768, expectedFileLength); + try { + log.warn("Test log1"); + assertTrue(Files.exists(logFile)); + assertEquals(expectedFileLength, Files.size(logFile)); + log.warn("Test log2"); + assertEquals(expectedFileLength, Files.size(logFile)); + } finally { + context.stop(); + } + int expectedLength = Strings.isEmpty(System.getProperty("jdk.module.path")) ? 272 : 332; + assertEquals(expectedLength + 2 * System.lineSeparator().length(), Files.size(logFile), + "Expected file size to shrink"); + + final List lines = Files.readAllLines(logFile); + assertEquals(2, lines.size()); + assertTrue(lines.get(0).endsWith("org.apache.logging.log4j.core.appender.MemoryMappedFileAppenderTest.testMemMapLocation(MemoryMappedFileAppenderTest.java:105): Test log1")); + assertTrue(lines.get(1).endsWith("org.apache.logging.log4j.core.appender.MemoryMappedFileAppenderTest.testMemMapLocation(MemoryMappedFileAppenderTest.java:108): Test log2")); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java similarity index 79% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java index 84f7a4effa3..8cf8ef00458 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java @@ -16,17 +16,16 @@ */ package org.apache.logging.log4j.core.appender; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests the MemoryMappedFileManager class. @@ -35,12 +34,13 @@ */ public class MemoryMappedFileManagerTest { + @TempDir + File tempDir; + @Test public void testRemapAfterInitialMapSizeExceeded() throws IOException { final int mapSize = 64; // very small, on purpose - final File file = File.createTempFile("log4j2", "test"); - file.deleteOnExit(); - assertEquals(0, file.length()); + final File file = new File(tempDir, "memory-mapped-file.bin"); final boolean append = false; final boolean immediateFlush = false; @@ -57,8 +57,8 @@ public void testRemapAfterInitialMapSizeExceeded() throws IOException { try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line = reader.readLine(); for (int i = 0; i < 1000; i++) { - assertNotNull("line", line); - assertTrue("line incorrect", line.contains("Message " + i)); + assertNotNull(line, "line"); + assertTrue(line.contains("Message " + i), "line incorrect"); line = reader.readLine(); } } @@ -66,9 +66,7 @@ public void testRemapAfterInitialMapSizeExceeded() throws IOException { @Test public void testAppendDoesNotOverwriteExistingFile() throws IOException { - final File file = File.createTempFile("log4j2", "test"); - file.deleteOnExit(); - assertEquals(0, file.length()); + final File file = new File(tempDir, "memory-mapped-file.bin"); final int initialLength = 4 * 1024; @@ -77,7 +75,7 @@ public void testAppendDoesNotOverwriteExistingFile() throws IOException { fos.write(new byte[initialLength], 0, initialLength); fos.flush(); } - assertEquals("all flushed to disk", initialLength, file.length()); + assertEquals(initialLength, file.length(), "all flushed to disk"); final boolean isAppend = true; final boolean immediateFlush = false; @@ -86,6 +84,6 @@ public void testAppendDoesNotOverwriteExistingFile() throws IOException { manager.writeBytes(new byte[initialLength], 0, initialLength); } final int expected = initialLength * 2; - assertEquals("appended, not overwritten", expected, file.length()); + assertEquals(expected, file.length(), "appended, not overwritten"); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java index d3ffa387ef8..5bd2c32c630 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java @@ -19,13 +19,13 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.OutputStream; -import java.sql.SQLException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.filter.NoMarkerFilter; import org.apache.logging.log4j.core.layout.PatternLayout; import org.junit.Assert; import org.junit.Rule; @@ -60,7 +60,20 @@ private void addAppender(final OutputStream outputStream, final String outputStr } @Test - public void testOutputStreamAppenderToBufferedOutputStream() throws SQLException { + public void testBuildFilter() { + final NoMarkerFilter filter = NoMarkerFilter.newBuilder().build(); + // @formatter:off + final OutputStreamAppender.Builder builder = OutputStreamAppender.newBuilder() + .setName("test") + .setFilter(filter); + // @formatter:on + Assert.assertEquals(filter, builder.getFilter()); + final OutputStreamAppender appender = builder.build(); + Assert.assertEquals(filter, appender.getFilter()); + } + + @Test + public void testOutputStreamAppenderToBufferedOutputStream() { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final OutputStream os = new BufferedOutputStream(out); final String name = getName(out); @@ -72,7 +85,7 @@ public void testOutputStreamAppenderToBufferedOutputStream() throws SQLException } @Test - public void testOutputStreamAppenderToByteArrayOutputStream() throws SQLException { + public void testOutputStreamAppenderToByteArrayOutputStream() { final OutputStream out = new ByteArrayOutputStream(); final String name = getName(out); final Logger logger = LogManager.getLogger(name); @@ -93,12 +106,12 @@ public void testUpdatePatternWithFileAppender() { final Configuration config = ctx.getConfiguration(); // @formatter:off final Appender appender = FileAppender.newBuilder() - .withFileName("target/" + getClass().getName() + ".log") - .withAppend(false) - .withName("File") - .withIgnoreExceptions(false) - .withBufferedIo(false) - .withBufferSize(4000) + .setFileName("target/" + getClass().getName() + ".log") + .setAppend(false) + .setName("File") + .setIgnoreExceptions(false) + .setBufferedIo(false) + .setBufferSize(4000) .setConfiguration(config) .build(); // @formatter:on diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java new file mode 100644 index 00000000000..1064323c8a7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * OutputStreamManager Tests. + */ +public class OutputStreamManagerTest { + + @Test + @LoggerContextSource("multipleIncompatibleAppendersTest.xml") + public void narrow(final LoggerContext context) { + final Logger logger = context.getLogger(OutputStreamManagerTest.class); + logger.info("test"); + final List statusData = StatusLogger.getLogger().getStatusData(); + StatusData data = statusData.get(0); + if (data.getMessage().getFormattedMessage().contains("WindowsAnsiOutputStream")) { + data = statusData.get(1); + } + assertEquals(Level.ERROR, data.getLevel()); + assertEquals("Could not configure plugin element RollingRandomAccessFile: org.apache.logging.log4j.core.config.ConfigurationException: Configuration has multiple incompatible Appenders pointing to the same resource 'target/multiIncompatibleAppender.log'", + data.getMessage().getFormattedMessage()); + } + + @Test + public void testOutputStreamAppenderFlushClearsBufferOnException() { + IOException exception = new IOException(); + final OutputStream throwingOutputStream = new OutputStream() { + @Override + public void write(int b) throws IOException { + throw exception; + } + }; + + final int bufferSize = 3; + OutputStreamManager outputStreamManager = new OutputStreamManager(throwingOutputStream, "test", null, false, bufferSize); + + for (int i = 0; i < bufferSize - 1; i++) { + outputStreamManager.getByteBuffer().put((byte) 0); + } + + assertEquals(outputStreamManager.getByteBuffer().remaining(), 1); + + AppenderLoggingException appenderLoggingException = assertThrows(AppenderLoggingException.class, () -> outputStreamManager.flushBuffer(outputStreamManager.getByteBuffer())); + assertEquals(appenderLoggingException.getCause(), exception); + + assertEquals(outputStreamManager.getByteBuffer().limit(), outputStreamManager.getByteBuffer().capacity()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ProgressConsoleTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ProgressConsoleTest.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ProgressConsoleTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ProgressConsoleTest.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppenderTest.java new file mode 100644 index 00000000000..25bc0cad620 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppenderTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.test.junit.CleanFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.hamcrest.CoreMatchers.*; + +import static org.junit.Assert.*; + +/** + * Simple tests for both the RandomAccessFileAppender and RollingRandomAccessFileAppender. + */ +@RunWith(Parameterized.class) +public class RandomAccessFileAppenderTest { + + @Parameterized.Parameters(name = "{0}, locationEnabled={1}, type={2}") + public static Collection data() { + return Arrays.asList( + new Object[][]{ + { "RandomAccessFileAppenderTest", false, ".xml" }, + { "RandomAccessFileAppenderLocationTest", true, ".xml" }, + { "RollingRandomAccessFileAppenderTest", false, ".xml" }, + { "RollingRandomAccessFileAppenderLocationTest", true, ".xml" }, + { "RollingRandomAccessFileAppenderLocationPropsTest", false, ".properties" } + } + ); + } + + private final LoggerContextRule init; + private final CleanFiles files; + + @Rule + public final RuleChain chain; + + private final File logFile; + private final boolean locationEnabled; + + public RandomAccessFileAppenderTest(final String testName, final boolean locationEnabled, final String type) { + this.init = new LoggerContextRule(testName + type); + this.logFile = new File("target", testName + ".log"); + this.files = new CleanFiles(this.logFile); + this.locationEnabled = locationEnabled; + this.chain = RuleChain.outerRule(files).around(init); + } + + @Test + public void testRandomAccessConfiguration() throws Exception { + final Logger logger = this.init.getLogger("com.foo.Bar"); + final String message = "This is a test log message brought to you by Slurm."; + logger.info(message); + this.init.getLoggerContext().stop(); // stop async thread + + String line; + try (final BufferedReader reader = new BufferedReader(new FileReader(this.logFile))) { + line = reader.readLine(); + } + assertNotNull(line); + assertThat(line, containsString(message)); + final Matcher containsLocationInformation = containsString("testRandomAccessConfiguration"); + final Matcher containsLocationInformationIfEnabled = this.locationEnabled ? + containsLocationInformation : not(containsLocationInformation); + assertThat(line, containsLocationInformationIfEnabled); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.java new file mode 100644 index 00000000000..821727bf08a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RandomAccessFileManagerTest.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +import org.apache.logging.log4j.core.util.NullOutputStream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the RandomAccessFileManager class. + */ +public class RandomAccessFileManagerTest { + + @TempDir + File tempDir; + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.RandomAccessFileManager#writeBytes(byte[], int, int)}. + */ + @Test + public void testWrite_multiplesOfBufferSize() throws IOException { + final File file = new File(tempDir, "testWrite_multiplesOfBufferSize.bin"); + try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + final OutputStream os = NullOutputStream.getInstance(); + try (final RandomAccessFileManager manager = new RandomAccessFileManager( + null, raf, file.getName(), os, + RandomAccessFileManager.DEFAULT_BUFFER_SIZE, null, null, true)) { + final int size = RandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3; + final byte[] data = new byte[size]; + manager.write(data); // no buffer overflow exception + // all data is written if exceeds buffer size + assertEquals(RandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3, raf.length()); + } + } + } + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.RandomAccessFileManager#writeBytes(byte[], int, int)} + * . + */ + @Test + public void testWrite_dataExceedingBufferSize() throws IOException { + final File file = new File(tempDir, "testWrite_dataExceedingBufferSize.bin"); + try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + final OutputStream os = NullOutputStream.getInstance(); + final int size = RandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3 + 1; + try (final RandomAccessFileManager manager = new RandomAccessFileManager( + null, raf, file.getName(), os, + RandomAccessFileManager.DEFAULT_BUFFER_SIZE, null, null, true)) { + final byte[] data = new byte[size]; + manager.write(data); // no exception + // all data is written if exceeds buffer size + assertEquals(RandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3 + 1, raf.length()); + assertEquals(size, raf.length()); // all data written to file now + } + } + } + + @Test + public void testConfigurableBufferSize() throws IOException { + final File file = new File(tempDir, "testConfigurableBufferSize.bin"); + try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + final OutputStream os = NullOutputStream.getInstance(); + final int bufferSize = 4 * 1024; + assertNotEquals(bufferSize, RandomAccessFileManager.DEFAULT_BUFFER_SIZE); + try (final RandomAccessFileManager manager = new RandomAccessFileManager( + null, raf, file.getName(), os, bufferSize, null, null, true)) { + // check the resulting buffer size is what was requested + assertEquals(bufferSize, manager.getBufferSize()); + } + } + } + + @Test + public void testWrite_dataExceedingMinBufferSize() throws IOException { + final File file = new File(tempDir, "testWrite_dataExceedingMinBufferSize.bin"); + try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + final OutputStream os = NullOutputStream.getInstance(); + final int bufferSize = 1; + final int size = bufferSize * 3 + 1; + try (final RandomAccessFileManager manager = new RandomAccessFileManager( + null, raf, file.getName(), os, bufferSize, null, null, true)) { + final byte[] data = new byte[size]; + manager.write(data); // no exception + // all data is written if exceeds buffer size + assertEquals(bufferSize * 3 + 1, raf.length()); + assertEquals(size, raf.length()); // all data written to file now + } + } + } + + @Test + public void testAppendDoesNotOverwriteExistingFile() throws IOException { + final boolean isAppend = true; + final File file = new File(tempDir, "testAppendDoesNotOverwriteExistingFile.bin"); + assertEquals(0, file.length()); + + final byte[] bytes = new byte[4 * 1024]; + + // create existing file + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(bytes, 0, bytes.length); + fos.flush(); + } + assertEquals(bytes.length, file.length(), "all flushed to disk"); + + try (final RandomAccessFileManager manager = RandomAccessFileManager.getFileManager( + file.getAbsolutePath(), isAppend, true, + RandomAccessFileManager.DEFAULT_BUFFER_SIZE, null, null, null)) { + manager.write(bytes, 0, bytes.length, true); + } + final int expected = bytes.length * 2; + assertEquals(expected, file.length(), "appended, not overwritten"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ReconfigureAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ReconfigureAppenderTest.java new file mode 100644 index 00000000000..62be8788994 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/ReconfigureAppenderTest.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.lang.reflect.Field; +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; + +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.rolling.DirectWriteRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.util.Builder; +import org.junit.jupiter.api.Test; + +public class ReconfigureAppenderTest { + private RollingFileAppender appender; + + @Test + public void addAndRemoveAppenderTest() + { + // this will create a rolling file appender and add it to the logger + // of this class. The file manager is created for the first time. + // see AbstractManager.getManager(...). + this.createAndAddAppender(); + + // let's write something to the logger to ensure the output stream is opened. + // We expect this call to create a new output stream (which is does). + // see OutputStreamManager.writeToDestination(...). + Logger logger = (Logger)LogManager.getLogger(this.getClass()); + logger.info("test message 1"); + + // this will close the rolling file appender and remove it from the logger + // of this class. We expect the file output stream to be closed (which is it) + // however the FileManager instance is kept in AbstractManager.MAP. This means that + // when we create a new rolling file appender with the DirectWriteRolloverStrategy + // this OLD file manager will be retrieved from the map (since it has the SAME file pattern) + // and this is a problem as the output stream on that file manager is CLOSED. The problem + // here is that we attempt to remove a file manager call NULL instead of FILE PATTERN + this.removeAppender(); + + // create a new rolling file appender for this logger. We expect this to create a new file + // manager as the old one should have been removed. Since the instance of this file manager + // is still in AbstractManager.MAP, it is returned and assigned to our new rolling file + // appender instance. The problem here is that the file manager is create with the name + // FILE PATTERN and that its output stream is closed. + this.createAndAddAppender(); + + // try and log something. This will not be logged anywhere. An exception will be thrown: + // Caused by: java.io.IOException: Stream Closed + logger.info("test message 2"); + + // remove the appender as before. + this.removeAppender(); + + // this method will use reflection to go and remove the instance of FileManager from the AbstractManager.MAP + // ourselves. This means that the rolling file appender has been stopped (previous method) AND its + // manager has been removed. + this.removeManagerUsingReflection(); + + // now that the instance of FileManager is not present in MAP, creating the appender will actually + // create a new rolling file manager, and put this in the map (keyed on file pattern again). + this.createAndAddAppender(); + + // because we have a new instance of file manager, this will create a new output stream. We can verify + // this by looking inside the filepattern.1.log file inside the working directory, and noticing that + // we have 'test message 1' followed by 'test message 3'. 'test message 2' is missing because we attempted + // to write while the output stream was closed. + logger.info("test message 3"); + + // possible fixes: + // 1) create the RollingFileManager and set it's name to FILE PATTERN when using DirectWriteRolloverStrategy + // 2) when stopping the appender (and thus the manager), remove on FILE PATTERN if DirectWriteRolloverStrategy + // 3) on OutputStreamManager.getOutputStream(), determine if the output stream is closed, and if it is create + // a new one. Note that this isn't really desirable as the only fix as if the file pattern had to change + // an instance of file manager would still exist in MAP, causing a resource leak. + + // now the obvious problem here is that if multiple file appenders use the same rolling file manager. We may run + // into a case where the file manager is removed and the output stream is closed, and the remaining appenders + // may not work correctly. I'm not sure of the use case in this scenario, and if people actually do this + // but based on the code it would be possible. I have also not tested this scenario out as it is not the + // scenario we would ever use, but it should be considered while fixing this issue. + } + private void removeManagerUsingReflection() + { + try + { + Field field = AbstractManager.class.getDeclaredField("MAP"); + field.setAccessible(true); + + // Retrieve the map itself. + Map map = + (Map)field.get(null); + + // Remove the file manager keyed on file pattern. + map.remove(appender.getFilePattern()); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + private void removeAppender() + { + Logger logger = (Logger)LogManager.getLogger(this.getClass()); + + // This call attempts to remove the file manager, but uses the name of the appender + // (NULL in this case) instead of PATTERN. + // see AbstractManager.stop(...). + appender.stop(); + logger.removeAppender(appender); + } + + private void createAndAddAppender() + { + ConfigurationBuilder config_builder = + ConfigurationBuilderFactory.newConfigurationBuilder(); + + // All loggers must have a root logger. The default root logger logs ERRORs to the console. + // Override this with a root logger that does not log anywhere as we leave it up the + // appenders on the logger. + config_builder.add(config_builder.newRootLogger(Level.INFO)); + + // Initialise the logger context. + LoggerContext logger_context = + Configurator.initialize(config_builder.build()); + + // Retrieve the logger. + Logger logger = (Logger) LogManager.getLogger(this.getClass()); + + Builder pattern_builder = PatternLayout.newBuilder().setPattern( + "[%d{dd-MM-yy HH:mm:ss}] %p %m %throwable %n"); + + PatternLayout pattern_layout = (PatternLayout) pattern_builder.build(); + + appender = RollingFileAppender + .newBuilder() + .setLayout(pattern_layout) + .setName("rollingfileappender") + .setFilePattern("target/filepattern.%i.log") + .setPolicy(SizeBasedTriggeringPolicy.createPolicy("5 MB")) + .setAppend(true) + .setStrategy( + DirectWriteRolloverStrategy + .newBuilder() + .setConfig(logger_context.getConfiguration()) + .setMaxFiles("5") + .build()) + .setConfiguration(logger_context.getConfiguration()) + .build(); + + appender.start(); + + logger.addAppender(appender); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppenderRolloverTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppenderRolloverTest.java new file mode 100644 index 00000000000..21610cd95d4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppenderRolloverTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("sleepy") +@SetSystemProperty(key = "log4j.configurationFile", value = "RollingRandomAccessFileAppenderTest.xml") +public class RollingRandomAccessFileAppenderRolloverTest { + + @Test + @Disabled + public void testRollover() throws Exception { + final File file = new File("target", "RollingRandomAccessFileAppenderTest.log"); + // System.out.println(f.getAbsolutePath()); + final File after1 = new File("target", "afterRollover-1.log"); + file.delete(); + after1.delete(); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "First a short message that does not trigger rollover"; + log.info(msg); + Thread.sleep(50); + + BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + assertTrue(line1.contains(msg)); + reader.close(); + + assertFalse(after1.exists(), "afterRollover-1.log not created yet"); + + String exceed = "Long message that exceeds rollover size... "; + final char[] padding = new char[250]; + Arrays.fill(padding, 'X'); + exceed += new String(padding); + log.warn(exceed); + assertFalse(after1.exists(), "exceeded size but afterRollover-1.log not created yet"); + + final String trigger = "This message triggers rollover."; + log.warn(trigger); + Thread.sleep(100); + log.warn(trigger); + + CoreLoggerContexts.stopLoggerContext(); // stop async thread + CoreLoggerContexts.stopLoggerContext(false); // stop async thread + + final int MAX_ATTEMPTS = 50; + int count = 0; + while (!after1.exists() && count++ < MAX_ATTEMPTS) { + Thread.sleep(50); + } + + assertTrue(after1.exists(), "afterRollover-1.log created"); + + reader = new BufferedReader(new FileReader(file)); + final String new1 = reader.readLine(); + assertTrue(new1.contains(trigger), "after rollover only new msg"); + assertNull(reader.readLine(), "No more lines"); + reader.close(); + file.delete(); + + reader = new BufferedReader(new FileReader(after1)); + final String old1 = reader.readLine(); + assertTrue(old1.contains(msg), "renamed file line 1"); + final String old2 = reader.readLine(); + assertTrue(old2.contains(exceed), "renamed file line 2"); + String line = reader.readLine(); + if (line != null) { + assertTrue(line.contains("This message triggers rollover."), "strange..."); + line = reader.readLine(); + } + assertNull(line, "No more lines"); + reader.close(); + after1.delete(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBuilderTest.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBuilderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBuilderTest.java index bdfd35a65fa..3636af321af 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBuilderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderBuilderTest.java @@ -16,9 +16,9 @@ */ package org.apache.logging.log4j.core.appender; -import org.apache.logging.log4j.core.appender.SocketAppender; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; public class SocketAppenderBuilderTest { @@ -27,6 +27,7 @@ public class SocketAppenderBuilderTest { */ @Test public void testDefaultImmediateFlush() { - Assert.assertTrue(SocketAppender.newBuilder().isImmediateFlush()); + assertTrue(SocketAppender.newBuilder().isImmediateFlush(), + "Regression of LOG4J2-1620: default value for immediateFlush should be true"); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java index 716223e3488..68b2f005a05 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java @@ -31,7 +31,7 @@ protected Facility getExpectedFacility() { @Override protected Builder newSyslogAppenderBuilder(final String protocol, final String format, final boolean newLine) { final Builder builder = super.newSyslogAppenderBuilder(protocol, format, newLine); - builder.withLayout(SyslogLayout.newBuilder().setFacility(Facility.LOCAL3).setIncludeNewLine(true).build()); + builder.setLayout(SyslogLayout.newBuilder().setFacility(Facility.LOCAL3).setIncludeNewLine(true).build()); return builder; } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java new file mode 100644 index 00000000000..ad0a0dd3e6f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.io.IOException; +import java.net.SocketException; + +import org.apache.logging.log4j.core.appender.SyslogAppender.Builder; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.core.test.net.mock.MockSyslogServerFactory; +import org.apache.logging.log4j.util.EnglishEnums; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * + */ +public class SyslogAppenderTest extends SyslogAppenderTestBase { + + public SyslogAppenderTest() { + root = ctx.getLogger("SyslogAppenderTest"); + } + + @Before + public void setUp() { + sentMessages.clear(); + } + + @After + public void teardown() { + removeAppenders(); + if (syslogServer != null) { + syslogServer.shutdown(); + } + } + + @Test + public void testTCPAppender() throws Exception { + initTCPTestEnvironment(null); + + sendAndCheckLegacyBsdMessage("This is a test message"); + sendAndCheckLegacyBsdMessage("This is a test message 2"); + } + + @Test + public void testDefaultAppender() throws Exception { + initTCPTestEnvironment(null); + + sendAndCheckLegacyBsdMessage("This is a test message"); + sendAndCheckLegacyBsdMessage("This is a test message 2"); + } + + @Test + public void testTCPStructuredAppender() throws Exception { + initTCPTestEnvironment("RFC5424"); + + sendAndCheckStructuredMessage(); + } + + @Test + public void testUDPAppender() throws Exception { + initUDPTestEnvironment("bsd"); + + sendAndCheckLegacyBsdMessage("This is a test message"); + root.removeAppender(appender); + appender.stop(); + } + + @Test + public void testUDPStructuredAppender() throws Exception { + initUDPTestEnvironment("RFC5424"); + + sendAndCheckStructuredMessage(); + root.removeAppender(appender); + appender.stop(); + } + + protected void initUDPTestEnvironment(final String messageFormat) throws SocketException { + syslogServer = MockSyslogServerFactory.createUDPSyslogServer(1, PORTNUM); + syslogServer.start(); + initAppender("udp", messageFormat); + } + + protected void initTCPTestEnvironment(final String messageFormat) throws IOException { + syslogServer = MockSyslogServerFactory.createTCPSyslogServer(1, PORTNUM); + syslogServer.start(); + initAppender("tcp", messageFormat); + } + + protected void initAppender(final String transportFormat, final String messageFormat) { + appender = createAppender(transportFormat, messageFormat); + validate(appender); + appender.start(); + initRootLogger(appender); + } + + protected SyslogAppender createAppender(final String protocol, final String format) { + return newSyslogAppenderBuilder(protocol, format, includeNewLine).build(); + } + + protected Builder newSyslogAppenderBuilder(final String protocol, final String format, final boolean newLine) { + // @formatter:off + return SyslogAppender.newSyslogAppenderBuilder() + .setPort(PORTNUM) + .setProtocol(EnglishEnums.valueOf(Protocol.class, protocol)) + .setReconnectDelayMillis(-1) + .setName("TestApp") + .setIgnoreExceptions(false) + .setId("Audit") + .setEnterpriseNumber(18060) + .setMdcId("RequestContext") + .setNewLine(newLine) + .setAppName("TestApp") + .setMsgId("Test") + .setFormat(format); + // @formatter:on + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTestBase.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTestBase.java similarity index 99% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTestBase.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTestBase.java index f9927b2a7e9..3ef20d3ded5 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTestBase.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTestBase.java @@ -33,7 +33,7 @@ import org.apache.logging.log4j.core.layout.SyslogLayout; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.net.Priority; -import org.apache.logging.log4j.core.net.mock.MockSyslogServer; +import org.apache.logging.log4j.core.test.net.mock.MockSyslogServer; import org.apache.logging.log4j.message.StructuredDataMessage; import org.apache.logging.log4j.util.Strings; import org.junit.Assert; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java new file mode 100644 index 00000000000..756d21d9b2d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; + +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.net.Protocol; +import org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.core.net.ssl.StoreConfigurationException; +import org.apache.logging.log4j.core.net.ssl.TlsSyslogTestUtil; +import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration; +import org.apache.logging.log4j.core.test.net.mock.MockSyslogServerFactory; +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; +import org.apache.logging.log4j.util.EnglishEnums; +import org.junit.Test; + +public class TlsSyslogAppenderTest extends SyslogAppenderTest { + + private SSLServerSocketFactory serverSocketFactory; + private TlsSyslogMessageFormat messageFormat; + private SslConfiguration sslConfiguration; + + public TlsSyslogAppenderTest() throws StoreConfigurationException { + initServerSocketFactory(); + root = ctx.getLogger("TLSSyslogAppenderTest"); + } + + @Test + public void sendLargeLegacyBsdMessageOverTls() throws IOException, InterruptedException { + final String prefix = "BEGIN"; + initTlsTestEnvironment(1, TlsSyslogMessageFormat.LEGACY_BSD); + + final char[] msg = new char[2 * 1024 * 2014 + prefix.length()]; + Arrays.fill(msg, 'a'); + System.arraycopy(prefix.toCharArray(), 0, msg, 0, prefix.length()); + sendAndCheckLegacyBsdMessage(new String(msg)); + } + + @Test + public void sendLegacyBsdMessagesOverTls() throws IOException, InterruptedException { + final int numberOfMessages = 100; + initTlsTestEnvironment(numberOfMessages, TlsSyslogMessageFormat.LEGACY_BSD); + final List generatedMessages = TlsSyslogTestUtil.generateMessages(numberOfMessages, TlsSyslogMessageFormat.LEGACY_BSD); + sendAndCheckLegacyBsdMessages(generatedMessages); + } + + @Test + public void sendStructuredMessageOverTls() throws InterruptedException, IOException { + initTlsTestEnvironment(1, TlsSyslogMessageFormat.SYSLOG); + + sendAndCheckStructuredMessage(); + } + + @Test + public void sendStructuredMessagesOverTls() throws IOException, InterruptedException { + final int numberOfMessages = 100; + initTlsTestEnvironment(numberOfMessages, TlsSyslogMessageFormat.SYSLOG); + sendAndCheckStructuredMessages(numberOfMessages); + } + + private void initServerSocketFactory() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), null, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null, null); + sslConfiguration = SslConfiguration.createSSLConfiguration(null, ksc, tsc); + serverSocketFactory = sslConfiguration.getSslServerSocketFactory(); + } + + private SyslogAppender createAppender() { + String format; + + if (messageFormat == TlsSyslogMessageFormat.LEGACY_BSD) { + format = "LEGACY_BSD"; + } else { + format = "RFC5424"; + } + final SslConfiguration sslConfiguration1 = sslConfiguration; + final boolean newLine = includeNewLine; + final String format1 = format; + + // @formatter:off + return SyslogAppender.newSyslogAppenderBuilder() + .setHost("localhost") + .setPort(PORTNUM) + .setProtocol(EnglishEnums.valueOf(Protocol.class, "SSL")) + .setSslConfiguration(sslConfiguration1) + .setConnectTimeoutMillis(0) + .setReconnectDelayMillis(-1) + .setImmediateFail(true) + .setName("TestApp") + .setImmediateFlush(true) + .setIgnoreExceptions(false).setFilter(null) + .setConfiguration(null) + .setAdvertise(false) + .setFacility(Facility.LOCAL0) + .setId("Audit") + .setEnterpriseNumber(18060) + .setIncludeMdc(true) + .setMdcId("RequestContext") + .setMdcPrefix(null) + .setEventPrefix(null) + .setNewLine(newLine) + .setAppName("TestApp") + .setMsgId("Test") + .setExcludes(null) + .setIncludeMdc(true) + .setRequired(null) + .setFormat(format1) + .setCharsetName(null) + .setExceptionPattern(null) + .setLoggerFields(null) + .build(); + // @formatter:on + } + + private void initTlsTestEnvironment(final int numberOfMessages, final TlsSyslogMessageFormat messageFormat) throws IOException { + this.messageFormat = messageFormat; + final SSLServerSocket sslServerSocket = (SSLServerSocket) serverSocketFactory.createServerSocket(PORTNUM); + + syslogServer = MockSyslogServerFactory.createTLSSyslogServer(numberOfMessages, messageFormat, sslServerSocket); + syslogServer.start(); + initAppender(); + } + + protected void initAppender() { + appender = createAppender(); + validate(appender); + appender.start(); + initRootLogger(appender); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java index 887ba998845..03d23c170cc 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java @@ -19,8 +19,10 @@ import java.nio.charset.StandardCharsets; import org.apache.logging.log4j.util.Chars; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; public class TlsSyslogFrameTest { private static final String TEST_MESSAGE = "The quick brown fox jumps over the lazy dog"; @@ -29,16 +31,16 @@ public class TlsSyslogFrameTest { public void equals() { final TlsSyslogFrame first = new TlsSyslogFrame(TEST_MESSAGE); final TlsSyslogFrame second = new TlsSyslogFrame(TEST_MESSAGE); - Assert.assertEquals(first, second); - Assert.assertEquals(first.hashCode(), second.hashCode()); + assertEquals(first, second); + assertEquals(first.hashCode(), second.hashCode()); } @Test public void notEquals() { final TlsSyslogFrame first = new TlsSyslogFrame("A message"); final TlsSyslogFrame second = new TlsSyslogFrame("B message"); - Assert.assertNotEquals(first, second); - Assert.assertNotEquals(first.hashCode(), second.hashCode()); + assertNotEquals(first, second); + assertNotEquals(first.hashCode(), second.hashCode()); } @Test @@ -46,6 +48,6 @@ public void testToString() { final TlsSyslogFrame frame = new TlsSyslogFrame(TEST_MESSAGE); final int length = TEST_MESSAGE.getBytes(StandardCharsets.UTF_8).length; final String expected = Integer.toString(length) + Chars.SPACE + TEST_MESSAGE; - Assert.assertEquals(expected, frame.toString()); + assertEquals(expected, frame.toString()); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java new file mode 100644 index 00000000000..392862e5355 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Method; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +/** + * Tests {@link WriterAppender}. + */ +public class WriterAppenderTest { + + private static final String TEST_MSG = "FOO ERROR"; + + private String testMethodName; + + @BeforeEach + void setUp(final TestInfo testInfo) { + testMethodName = testInfo.getTestMethod().map(Method::getName).orElseGet(testInfo::getDisplayName); + } + + private String getName(final Writer writer) { + return writer.getClass().getSimpleName() + "." + testMethodName; + } + + private void test(final ByteArrayOutputStream out, final Writer writer) { + final String name = getName(writer); + addAppender(writer, name); + final Logger logger = LogManager.getLogger(name); + logger.error(TEST_MSG); + final String actual = out.toString(); + assertThat(actual, containsString(TEST_MSG)); + } + + private void test(final Writer writer) { + final String name = getName(writer); + addAppender(writer, name); + final Logger logger = LogManager.getLogger(name); + logger.error(TEST_MSG); + final String actual = writer.toString(); + assertThat(actual, containsString(TEST_MSG)); + } + + private void addAppender(final Writer writer, final String writerName) { + final LoggerContext context = LoggerContext.getContext(false); + final Configuration config = context.getConfiguration(); + final PatternLayout layout = PatternLayout.createDefaultLayout(config); + final Appender appender = WriterAppender.createAppender(layout, null, writer, writerName, false, true); + appender.start(); + config.addAppender(appender); + ConfigurationTestUtils.updateLoggers(appender, config); + } + + @Test + public void testWriterAppenderToCharArrayWriter() { + test(new CharArrayWriter()); + } + + @Test + public void testWriterAppenderToOutputStreamWriter() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final Writer writer = new OutputStreamWriter(out); + test(out, writer); + } + + @Test + public void testWriterAppenderToPrintWriter() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final Writer writer = new PrintWriter(out); + test(out, writer); + } + + @Test + public void testWriterAppenderToStringWriter() { + test(new StringWriter()); + } + + @Test + public void testBuilder() { + // This should compile + WriterAppender.newBuilder().setTarget(new StringWriter()).setName("testWriterAppender").build(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java index 2460cf5d378..6b5d7ef6e19 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java @@ -31,14 +31,29 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Property; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class AbstractDatabaseAppenderTest { + private static class LocalAbstractDatabaseAppender extends AbstractDatabaseAppender { + + public LocalAbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions, + final LocalAbstractDatabaseManager manager) { + super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY, manager); + } + } + private static abstract class LocalAbstractDatabaseManager extends AbstractDatabaseManager { + public LocalAbstractDatabaseManager(final String name, final int bufferSize) { + super(name, bufferSize); + } + } + private LocalAbstractDatabaseAppender appender; + @Mock private LocalAbstractDatabaseManager manager; @@ -46,6 +61,25 @@ public void setUp(final String name) { appender = new LocalAbstractDatabaseAppender(name, null, true, manager); } + @Test + public void testAppend() { + setUp("name"); + given(manager.commitAndClose()).willReturn(true); + + final LogEvent event1 = mock(LogEvent.class); + final LogEvent event2 = mock(LogEvent.class); + + appender.append(event1); + then(manager).should().isBuffered(); + then(manager).should().writeThrough(same(event1), (Serializable) isNull()); + reset(manager); + + appender.append(event2); + then(manager).should().isBuffered(); + then(manager).should().writeThrough(same(event2), (Serializable) isNull()); + reset(manager); + } + @Test public void testNameAndGetLayout01() { setUp("testName01"); @@ -62,17 +96,6 @@ public void testNameAndGetLayout02() { assertNull("The layout should always be null.", appender.getLayout()); } - @Test - public void testStartAndStop() throws Exception { - setUp("name"); - - appender.start(); - then(manager).should().startupInternal(); - - appender.stop(); - then(manager).should().stop(0L, TimeUnit.MILLISECONDS); - } - @Test public void testReplaceManager() throws Exception { setUp("name"); @@ -90,37 +113,13 @@ public void testReplaceManager() throws Exception { } @Test - public void testAppend() { + public void testStartAndStop() throws Exception { setUp("name"); - given(manager.commitAndClose()).willReturn(true); - - final LogEvent event1 = mock(LogEvent.class); - final LogEvent event2 = mock(LogEvent.class); - - appender.append(event1); - then(manager).should().connectAndStart(); - then(manager).should().writeInternal(same(event1), (Serializable) isNull()); - then(manager).should().commitAndClose(); - - reset(manager); - appender.append(event2); - then(manager).should().connectAndStart(); - then(manager).should().writeInternal(same(event2), (Serializable) isNull()); - then(manager).should().commitAndClose(); - } - - private static abstract class LocalAbstractDatabaseManager extends AbstractDatabaseManager { - public LocalAbstractDatabaseManager(final String name, final int bufferSize) { - super(name, bufferSize); - } - } - - private static class LocalAbstractDatabaseAppender extends AbstractDatabaseAppender { + appender.start(); + then(manager).should().startupInternal(); - public LocalAbstractDatabaseAppender(final String name, final Filter filter, final boolean exceptionSuppressed, - final LocalAbstractDatabaseManager manager) { - super(name, filter, exceptionSuppressed, manager); - } + appender.stop(); + then(manager).should().stop(0L, TimeUnit.MILLISECONDS); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java similarity index 87% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java index 932724d8624..dfb498eb09d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.Serializable; @@ -33,56 +35,44 @@ import org.junit.Test; public class AbstractDatabaseManagerTest { - private AbstractDatabaseManager manager; - - public void setUp(final String name, final int buffer) { - manager = spy(new StubDatabaseManager(name, buffer)); - } - - @Test - public void testStartupShutdown01() throws Exception { - setUp("testName01", 0); - - assertEquals("The name is not correct.", "testName01", manager.getName()); - assertFalse("The manager should not have started.", manager.isRunning()); - - manager.startup(); - then(manager).should().startupInternal(); - assertTrue("The manager should be running now.", manager.isRunning()); + // this stub is provided because mocking constructors is hard + private static class StubDatabaseManager extends AbstractDatabaseManager { - manager.shutdown(); - then(manager).should().shutdownInternal(); - assertFalse("The manager should not be running anymore.", manager.isRunning()); - } + protected StubDatabaseManager(final String name, final int bufferSize) { + super(name, bufferSize); + } - @Test - public void testStartupShutdown02() throws Exception { - setUp("anotherName02", 0); + @Override + protected boolean commitAndClose() { + return true; + } - assertEquals("The name is not correct.", "anotherName02", manager.getName()); - assertFalse("The manager should not have started.", manager.isRunning()); + @Override + protected void connectAndStart() { + // noop + } - manager.startup(); - then(manager).should().startupInternal(); - assertTrue("The manager should be running now.", manager.isRunning()); + @Override + protected boolean shutdownInternal() throws Exception { + return true; + } - manager.releaseSub(-1, null); - then(manager).should().shutdownInternal(); - assertFalse("The manager should not be running anymore.", manager.isRunning()); - } + @Override + protected void startupInternal() throws Exception { + // noop + } - @Test - public void testToString01() { - setUp("someName01", 0); + @Override + protected void writeInternal(final LogEvent event, final Serializable serializable) { + // noop + } - assertEquals("The string is not correct.", "someName01", manager.toString()); } - @Test - public void testToString02() { - setUp("bufferSize=12, anotherKey02=coolValue02", 12); + private AbstractDatabaseManager manager; - assertEquals("The string is not correct.", "bufferSize=12, anotherKey02=coolValue02", manager.toString()); + public void setUp(final String name, final int buffer) { + manager = spy(new StubDatabaseManager(name, buffer)); } @Test @@ -98,22 +88,31 @@ public void testBuffering01() throws Exception { reset(manager); manager.write(event1, null); + then(manager).should().writeThrough(same(event1), (Serializable) isNull()); then(manager).should().connectAndStart(); + then(manager).should().isBuffered(); then(manager).should().writeInternal(same(event1), (Serializable) isNull()); then(manager).should().commitAndClose(); + then(manager).shouldHaveNoMoreInteractions(); reset(manager); manager.write(event2, null); + then(manager).should().writeThrough(same(event2), (Serializable) isNull()); then(manager).should().connectAndStart(); + then(manager).should().isBuffered(); then(manager).should().writeInternal(same(event2), (Serializable) isNull()); then(manager).should().commitAndClose(); + then(manager).shouldHaveNoMoreInteractions(); reset(manager); manager.write(event3, null); + then(manager).should().writeThrough(same(event3), (Serializable) isNull()); then(manager).should().connectAndStart(); + then(manager).should().isBuffered(); then(manager).should().writeInternal(same(event3), (Serializable) isNull()); then(manager).should().commitAndClose(); then(manager).shouldHaveNoMoreInteractions(); + reset(manager); } @Test @@ -144,10 +143,15 @@ public void testBuffering02() throws Exception { manager.write(event4, null); then(manager).should().connectAndStart(); + verify(manager, times(5)).isBuffered(); // 4 + 1 in flush() then(manager).should().writeInternal(same(event1copy), (Serializable) isNull()); + then(manager).should().buffer(event1); then(manager).should().writeInternal(same(event2copy), (Serializable) isNull()); + then(manager).should().buffer(event2); then(manager).should().writeInternal(same(event3copy), (Serializable) isNull()); + then(manager).should().buffer(event3); then(manager).should().writeInternal(same(event4copy), (Serializable) isNull()); + then(manager).should().buffer(event4); then(manager).should().commitAndClose(); then(manager).shouldHaveNoMoreInteractions(); } @@ -177,9 +181,13 @@ public void testBuffering03() throws Exception { manager.flush(); then(manager).should().connectAndStart(); + verify(manager, times(4)).isBuffered(); then(manager).should().writeInternal(same(event1copy), (Serializable) isNull()); + then(manager).should().buffer(event1); then(manager).should().writeInternal(same(event2copy), (Serializable) isNull()); + then(manager).should().buffer(event2); then(manager).should().writeInternal(same(event3copy), (Serializable) isNull()); + then(manager).should().buffer(event3); then(manager).should().commitAndClose(); then(manager).shouldHaveNoMoreInteractions(); } @@ -209,50 +217,61 @@ public void testBuffering04() throws Exception { manager.shutdown(); then(manager).should().connectAndStart(); + verify(manager, times(4)).isBuffered(); then(manager).should().writeInternal(same(event1copy), (Serializable) isNull()); + then(manager).should().buffer(event1); then(manager).should().writeInternal(same(event2copy), (Serializable) isNull()); + then(manager).should().buffer(event2); then(manager).should().writeInternal(same(event3copy), (Serializable) isNull()); + then(manager).should().buffer(event3); then(manager).should().commitAndClose(); then(manager).should().shutdownInternal(); then(manager).shouldHaveNoMoreInteractions(); } - // this stub is provided because mocking constructors is hard - private static class StubDatabaseManager extends AbstractDatabaseManager { + @Test + public void testStartupShutdown01() throws Exception { + setUp("testName01", 0); - protected StubDatabaseManager(final String name, final int bufferSize) { - super(name, bufferSize); - } + assertEquals("The name is not correct.", "testName01", manager.getName()); + assertFalse("The manager should not have started.", manager.isRunning()); - @Override - protected void startupInternal() throws Exception { - // noop - } + manager.startup(); + then(manager).should().startupInternal(); + assertTrue("The manager should be running now.", manager.isRunning()); - @Override - protected boolean shutdownInternal() throws Exception { - return true; - } + manager.shutdown(); + then(manager).should().shutdownInternal(); + assertFalse("The manager should not be running anymore.", manager.isRunning()); + } - @Override - protected void connectAndStart() { - // noop - } + @Test + public void testStartupShutdown02() throws Exception { + setUp("anotherName02", 0); - @Deprecated - @Override - protected void writeInternal(final LogEvent event) { - // noop - } + assertEquals("The name is not correct.", "anotherName02", manager.getName()); + assertFalse("The manager should not have started.", manager.isRunning()); - @Override - protected void writeInternal(LogEvent event, Serializable serializable) { - // noop - } - @Override - protected boolean commitAndClose() { - return true; - } + manager.startup(); + then(manager).should().startupInternal(); + assertTrue("The manager should be running now.", manager.isRunning()); + manager.releaseSub(-1, null); + then(manager).should().shutdownInternal(); + assertFalse("The manager should not be running anymore.", manager.isRunning()); + } + + @Test + public void testToString01() { + setUp("someName01", 0); + + assertEquals("The string is not correct.", "someName01", manager.toString()); + } + + @Test + public void testToString02() { + setUp("bufferSize=12, anotherKey02=coolValue02", 12); + + assertEquals("The string is not correct.", "bufferSize=12, anotherKey02=coolValue02", manager.toString()); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java similarity index 75% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java index 6ab9419691e..7ce3b618375 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java @@ -16,12 +16,14 @@ */ package org.apache.logging.log4j.core.appender.nosql; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import static org.junit.Assert.*; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class NoSqlAppenderTest { @@ -31,14 +33,14 @@ public class NoSqlAppenderTest { @Test public void testNoProvider() { - final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, null); + final NoSqlAppender appender = NoSqlAppender.newBuilder().setName("myName01").build(); assertNull("The appender should be null.", appender); } @Test public void testProvider() { - final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, provider); + final NoSqlAppender appender = NoSqlAppender.newBuilder().setName("myName01").setProvider(provider).build(); assertNotNull("The appender should not be null.", appender); assertEquals("The toString value is not correct.", @@ -50,12 +52,14 @@ public void testProvider() { @Test public void testProviderBuffer() { - final NoSqlAppender appender = NoSqlAppender.createAppender("anotherName02", null, null, "25", provider); + final NoSqlAppender appender = NoSqlAppender.newBuilder().setName("anotherName02").setProvider(provider) + .setBufferSize(25).build(); assertNotNull("The appender should not be null.", appender); assertEquals("The toString value is not correct.", "anotherName02{ manager=noSqlManager{ description=anotherName02, bufferSize=25, provider=" + provider - + " } }", appender.toString()); + + " } }", + appender.toString()); appender.stop(); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java index 772a947d800..17c44622aa9 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManagerTest.java @@ -16,8 +16,16 @@ */ package org.apache.logging.log4j.core.appender.nosql; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.mock; + import java.io.IOException; -import java.sql.SQLException; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -31,8 +39,8 @@ import org.apache.logging.log4j.core.appender.AppenderLoggingException; import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.junit.ThreadContextStackRule; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.test.junit.ThreadContextStackRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -42,15 +50,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.mock; - @RunWith(MockitoJUnitRunner.class) public class NoSqlDatabaseManagerTest { @Mock @@ -173,9 +175,9 @@ public void testWriteInternal01() { assertNull("The thrown should be null.", object.get("thrown")); - assertTrue("The context map should be empty.", ((Map) object.get("contextMap")).isEmpty()); + assertTrue("The context map should be empty.", ((Map) object.get("contextMap")).isEmpty()); - assertTrue("The context stack should be null.", ((Collection) object.get("contextStack")).isEmpty()); + assertTrue("The context stack should be null.", ((Collection) object.get("contextStack")).isEmpty()); } } @@ -291,7 +293,7 @@ public void testWriteInternal03() { then(provider).should().getConnection(); final IOException exception1 = new IOException("This is the cause."); - final SQLException exception2 = new SQLException("This is the result.", exception1); + final IllegalStateException exception2 = new IllegalStateException("This is the result.", exception1); final Map context = new HashMap<>(); context.put("hello", "world"); context.put("user", "pass"); @@ -377,7 +379,7 @@ public void testWriteInternal03() { assertTrue("The thrown should be a map.", object.get("thrown") instanceof Map); @SuppressWarnings("unchecked") final Map thrown = (Map) object.get("thrown"); - assertEquals("The thrown type is not correct.", "java.sql.SQLException", thrown.get("type")); + assertEquals("The thrown type is not correct.", "java.lang.IllegalStateException", thrown.get("type")); assertEquals("The thrown message is not correct.", "This is the result.", thrown.get("message")); assertTrue("The thrown stack trace should be a list.", thrown.get("stackTrace") instanceof List); @SuppressWarnings("unchecked") diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicyTest.java similarity index 91% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicyTest.java index a30a0e6695e..b03d64b186c 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicyTest.java @@ -21,12 +21,13 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests {@link LoggerNameLevelRewritePolicy}. - * + * * @since 2.4 */ public class LoggerNameLevelRewritePolicyTest { @@ -44,26 +45,26 @@ public void testUpdate() { final LoggerNameLevelRewritePolicy updatePolicy = LoggerNameLevelRewritePolicy.createPolicy(loggerNameRewrite, rewrite); LogEvent rewritten = updatePolicy.rewrite(logEvent); - Assert.assertEquals(Level.DEBUG, rewritten.getLevel()); + assertEquals(Level.DEBUG, rewritten.getLevel()); logEvent = Log4jLogEvent.newBuilder().setLoggerName(loggerNameRewrite) .setLoggerFqcn("LoggerNameLevelRewritePolicyTest.testUpdate()").setLevel(Level.WARN) .setMessage(new SimpleMessage("Test")).setThrown(new RuntimeException("test")).setThreadName("none") .setTimeMillis(1).build(); rewritten = updatePolicy.rewrite(logEvent); - Assert.assertEquals(Level.INFO, rewritten.getLevel()); + assertEquals(Level.INFO, rewritten.getLevel()); final String loggerNameReadOnly = "com.nochange"; logEvent = Log4jLogEvent.newBuilder().setLoggerName(loggerNameReadOnly) .setLoggerFqcn("LoggerNameLevelRewritePolicyTest.testUpdate()").setLevel(Level.INFO) .setMessage(new SimpleMessage("Test")).setThrown(new RuntimeException("test")).setThreadName("none") .setTimeMillis(1).build(); rewritten = updatePolicy.rewrite(logEvent); - Assert.assertEquals(Level.INFO, rewritten.getLevel()); + assertEquals(Level.INFO, rewritten.getLevel()); logEvent = Log4jLogEvent.newBuilder().setLoggerName(loggerNameReadOnly) .setLoggerFqcn("LoggerNameLevelRewritePolicyTest.testUpdate()").setLevel(Level.WARN) .setMessage(new SimpleMessage("Test")).setThrown(new RuntimeException("test")).setThreadName("none") .setTimeMillis(1).build(); rewritten = updatePolicy.rewrite(logEvent); - Assert.assertEquals(Level.WARN, rewritten.getLevel()); + assertEquals(Level.WARN, rewritten.getLevel()); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java index 3247f821e69..74058da1bba 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java @@ -16,16 +16,6 @@ */ package org.apache.logging.log4j.core.appender.rewrite; -import static org.apache.logging.log4j.hamcrest.MapMatchers.hasSize; -import static org.hamcrest.Matchers.hasEntry; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.core.LogEvent; @@ -38,8 +28,18 @@ import org.apache.logging.log4j.spi.MutableThreadContextStack; import org.apache.logging.log4j.spi.ThreadContextStack; import org.apache.logging.log4j.util.StringMap; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.logging.log4j.core.test.hamcrest.MapMatchers.hasSize; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class MapRewritePolicyTest { @@ -48,11 +48,11 @@ public class MapRewritePolicyTest { private static KeyValuePair[] rewrite; private static LogEvent logEvent0, logEvent1, logEvent2, logEvent3; - @BeforeClass + @BeforeAll public static void setupClass() { stringMap.putValue("test1", "one"); stringMap.putValue("test2", "two"); - map = stringMap.toMap(); + map = stringMap.toMap(); logEvent0 = Log4jLogEvent.newBuilder() // .setLoggerName("test") // .setContextData(stringMap) // @@ -93,7 +93,7 @@ public void addTest() { final MapRewritePolicy addPolicy = MapRewritePolicy.createPolicy("Add", rewrite); LogEvent rewritten = addPolicy.rewrite(logEvent0); compareLogEvents(logEvent0, rewritten); - assertEquals("Simple log message changed", logEvent0.getMessage(), rewritten.getMessage()); + assertEquals(logEvent0.getMessage(), rewritten.getMessage(), "Simple log message changed"); rewritten = addPolicy.rewrite(logEvent1); compareLogEvents(logEvent1, rewritten); @@ -113,7 +113,7 @@ public void updateTest() { final MapRewritePolicy updatePolicy = MapRewritePolicy.createPolicy("Update", rewrite); LogEvent rewritten = updatePolicy.rewrite(logEvent0); compareLogEvents(logEvent0, rewritten); - assertEquals("Simple log message changed", logEvent0.getMessage(), rewritten.getMessage()); + assertEquals(logEvent0.getMessage(), rewritten.getMessage(), "Simple log message changed"); rewritten = updatePolicy.rewrite(logEvent1); compareLogEvents(logEvent1, rewritten); @@ -133,7 +133,7 @@ public void defaultIsAdd() { final MapRewritePolicy addPolicy = MapRewritePolicy.createPolicy(null, rewrite); LogEvent rewritten = addPolicy.rewrite(logEvent0); compareLogEvents(logEvent0, rewritten); - assertEquals("Simple log message changed", logEvent0.getMessage(), rewritten.getMessage()); + assertEquals(logEvent0.getMessage(), rewritten.getMessage(), "Simple log message changed"); rewritten = addPolicy.rewrite(logEvent1); compareLogEvents(logEvent1, rewritten); @@ -164,19 +164,18 @@ private void checkUpdated(final Map updatedMap) { @SuppressWarnings("deprecation") private void compareLogEvents(final LogEvent orig, final LogEvent changed) { // Ensure that everything but the Mapped Data is still the same - assertEquals("LoggerName changed", orig.getLoggerName(), changed.getLoggerName()); - assertEquals("Marker changed", orig.getMarker(), changed.getMarker()); - assertEquals("FQCN changed", orig.getLoggerFqcn(), changed.getLoggerFqcn()); - assertEquals("Level changed", orig.getLevel(), changed.getLevel()); - assertArrayEquals("Throwable changed", - orig.getThrown() == null ? null : orig.getThrownProxy().getExtendedStackTrace(), - changed.getThrown() == null ? null : changed.getThrownProxy().getExtendedStackTrace() - ); - assertEquals("ContextMap changed", orig.getContextMap(), changed.getContextMap()); - assertEquals("ContextData changed", orig.getContextData(), changed.getContextData()); - assertEquals("ContextStack changed", orig.getContextStack(), changed.getContextStack()); - assertEquals("ThreadName changed", orig.getThreadName(), changed.getThreadName()); - assertEquals("Source changed", orig.getSource(), changed.getSource()); - assertEquals("Millis changed", orig.getTimeMillis(), changed.getTimeMillis()); + assertEquals(orig.getLoggerName(), changed.getLoggerName(), "LoggerName changed"); + assertEquals(orig.getMarker(), changed.getMarker(), "Marker changed"); + assertEquals(orig.getLoggerFqcn(), changed.getLoggerFqcn(), "FQCN changed"); + assertEquals(orig.getLevel(), changed.getLevel(), "Level changed"); + assertArrayEquals( + orig.getThrown() == null ? null : orig.getThrownProxy().getExtendedStackTrace(), + changed.getThrown() == null ? null : changed.getThrownProxy().getExtendedStackTrace(), + "Throwable changed"); + assertEquals(orig.getContextData(), changed.getContextData(), "ContextData changed"); + assertEquals(orig.getContextStack(), changed.getContextStack(), "ContextStack changed"); + assertEquals(orig.getThreadName(), changed.getThreadName(), "ThreadName changed"); + assertEquals(orig.getSource(), changed.getSource(), "Source changed"); + assertEquals(orig.getTimeMillis(), changed.getTimeMillis(), "Millis changed"); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.java new file mode 100644 index 00000000000..deabcfbe74a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rewrite; + +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.hamcrest.MapMatchers; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-rewrite.xml") +public class RewriteAppenderTest { + private final ListAppender app; + private final ListAppender app2; + + public RewriteAppenderTest(@Named("List") final ListAppender app, @Named("List2") final ListAppender app2) { + this.app = app.clear(); + this.app2 = app2.clear(); + } + + @Test + public void rewriteTest() { + final StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + msg.put("Key1", "Value1"); + msg.put("Key2", "Value2"); + EventLogger.logEvent(msg); + final List list = app.getEvents(); + assertThat(list, hasSize(1)); + final LogEvent event = list.get(0); + final Message m = event.getMessage(); + assertThat(m, instanceOf(StructuredDataMessage.class)); + final StructuredDataMessage message = (StructuredDataMessage) m; + final Map map = message.getData(); + assertNotNull(map, "No Map"); + assertThat(map, MapMatchers.hasSize(3)); + final String value = map.get("Key1"); + assertEquals("Apache", value); + } + + + @Test + public void testProperties(final LoggerContext context) { + final Logger logger = context.getLogger(RewriteAppenderTest.class); + logger.debug("Test properties rewrite"); + final List list = app2.getMessages(); + assertThat(list, hasSize(1)); + assertThat(list.get(0), not(containsString("{user.dir}"))); + assertNotNull(list, "No events generated"); + assertEquals(list.size(), 1, "Incorrect number of events. Expected 1, got " + list.size()); + assertFalse(list.get(0).contains("{user."), "Did not resolve user name"); + } + + + @Test + public void testFilter(final LoggerContext context) { + StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + msg.put("Key1", "Value2"); + msg.put("Key2", "Value1"); + final Logger logger = context.getLogger("org.apache.logging.log4j.core.Logging"); + logger.debug(msg); + msg = new StructuredDataMessage("Test", "This is a test", "Service"); + msg.put("Key1", "Value1"); + msg.put("Key2", "Value2"); + logger.trace(msg); + + assertThat(app.getEvents(), empty()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java index 4380f9848c6..508ff34e568 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java @@ -16,16 +16,17 @@ */ package org.apache.logging.log4j.core.appender.rewrite; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; /** * */ -@Plugin(name = "TestRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = true) +@Configurable(elementType = "rewritePolicy", printObject = true) +@Plugin public class TestRewritePolicy implements RewritePolicy { @Override diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/AbstractRollingListenerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/AbstractRollingListenerTest.java new file mode 100644 index 00000000000..bf8cb2e340b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/AbstractRollingListenerTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.plugins.Factory; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Provides a controllable clock for rolling appender tests. + */ +public abstract class AbstractRollingListenerTest implements RolloverListener { + protected final AtomicLong currentTimeMillis = new AtomicLong(System.currentTimeMillis()); + + @Factory + Clock clock() { + return currentTimeMillis::get; + } + + @Override + public void rolloverTriggered(final String fileName) { + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java new file mode 100644 index 00000000000..f4e995a1f2a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.RollingRandomAccessFileAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class CronTriggeringPolicyTest { + + private static final String CRON_EXPRESSION = "0 0 0 * * ?"; + + private NullConfiguration configuration; + + // TODO Need a CleanRegexFiles("testcmd.\\.log\\..*"); + // @Rule + // public CleanFiles cleanFiles = new CleanFiles("testcmd1.log", "testcmd2.log", "testcmd3.log"); + + @BeforeEach + public void before() { + configuration = new NullConfiguration(); + } + + private CronTriggeringPolicy createPolicy() { + return CronTriggeringPolicy.createPolicy(configuration, Boolean.TRUE.toString(), CRON_EXPRESSION); + } + + private DefaultRolloverStrategy createStrategy() { + return DefaultRolloverStrategy.newBuilder().setMax("7").setMin("1").setFileIndex("max") + .setStopCustomActionsOnError(false).setConfig(configuration).build(); + } + + private void testBuilder() { + // @formatter:off + final RollingFileAppender raf = RollingFileAppender.newBuilder() + .setName("test1") + .setFileName("target/testcmd1.log") + .setFilePattern("target/testcmd1.log.%d{yyyy-MM-dd}") + .setPolicy(createPolicy()) + .setStrategy(createStrategy()) + .setConfiguration(configuration) + .build(); + // @formatter:on + assertNotNull(raf); + } + + /** + * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is + * true. + */ + @Test + public void testBuilderOnce() { + testBuilder(); + } + + /** + * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE + */ + @Test + public void testLoggerContextAndBuilder() { + assertDoesNotThrow(() -> + { + try (LoggerContext ignored = Configurator.initialize(configuration)) { + testBuilder(); + } + }); + + } + + /** + * Tests LOG4J2-1740 Add CronTriggeringPolicy programmatically leads to NPE + */ + @Test + public void testRollingRandomAccessFileAppender() { + // @formatter:off + assertDoesNotThrow(() -> + RollingRandomAccessFileAppender.newBuilder() + .setName("test2") + .setFileName("target/testcmd2.log") + .setFilePattern("target/testcmd2.log.%d{yyyy-MM-dd}") + .setPolicy(createPolicy()) + .setStrategy(createStrategy()) + .setConfiguration(configuration) + .build()); + // @formatter:on + } + + /** + * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is + * true. + */ + @Test + public void testBuilderSequence() { + assertDoesNotThrow(this::testBuilder); + assertDoesNotThrow(this::testBuilder); + } + + private void testFactoryMethod() { + final CronTriggeringPolicy triggerPolicy = createPolicy(); + final DefaultRolloverStrategy rolloverStrategy = createStrategy(); + + try (RollingFileManager fileManager = RollingFileManager.getFileManager("target/testcmd3.log", + "target/testcmd3.log.%d{yyyy-MM-dd}", true, true, triggerPolicy, rolloverStrategy, null, + PatternLayout.createDefaultLayout(), 0, true, false, null, null, null, configuration)) { + assertNotNull(fileManager); + // trigger rollover + fileManager.initialize(); + fileManager.rollover(); + } + } + + /** + * Tests LOG4J2-1474 CronTriggeringPolicy raise exception and fail to rollover log file when evaluateOnStartup is + * true. + */ + @Test + public void testFactoryMethodOnce() { + assertDoesNotThrow(this::testFactoryMethod); + } + + @Test + public void testFactoryMethodSequence() { + assertDoesNotThrow(this::testFactoryMethod); + assertDoesNotThrow(this::testFactoryMethod); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/EligibleFilesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/EligibleFilesTest.java new file mode 100644 index 00000000000..537c27444d6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/EligibleFilesTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.nio.file.Path; +import java.util.Map; + +import org.apache.logging.log4j.core.pattern.NotANumber; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test getEligibleFiles method. + */ +public class EligibleFilesTest { + + @Test + public void runTest() throws Exception { + final String path = "target/test-classes/rolloverPath/log4j.txt.20170112_09-" + NotANumber.VALUE + ".gz"; + final TestRolloverStrategy strategy = new TestRolloverStrategy(); + final Map files = strategy.findFilesInPath(path); + assertTrue(files.size() > 0, "No files found"); + assertEquals(30, files.size(), "Incorrect number of files found. Should be 30, was " + files.size()); + } + + @Test + public void runTestWithPlusCharacter() throws Exception { + final String path = "target/test-classes/rolloverPath/log4j.20211028T194500+0200." + NotANumber.VALUE + ".log.gz"; + final TestRolloverStrategy strategy = new TestRolloverStrategy(); + final Map files = strategy.findFilesWithPlusInPath(path); + assertTrue(files.size() > 0, "No files found"); + assertEquals(30, files.size(), "Incorrect number of files found. Should be 30, was " + files.size()); + } + + private static class TestRolloverStrategy extends AbstractRolloverStrategy { + + public TestRolloverStrategy() { + super(null); + } + + @Override + public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException { + return null; + } + + public Map findFilesInPath(final String path) { + return getEligibleFiles(path, "log4j.txt.%d{yyyyMMdd}-%i.gz"); + } + + public Map findFilesWithPlusInPath(final String path) { + // timezone might expand to "+0200", because of '+' we have to be careful when working with regex + return getEligibleFiles(path, "log4j.txt.%d{yyyyMMdd'T'HHmmssZ}.%i.log.gz"); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/FileSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/FileSizeTest.java new file mode 100644 index 00000000000..9bed91c70bb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/FileSizeTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests {@link FileSize}. + */ +public class FileSizeTest { + + @ParameterizedTest(name = "[{index}] \"{0}\" -> {1}") + @CsvSource(delimiter = ':', value = { + "10:10", + "10KB:10240", + "10 KB:10240", + "10 kb:10240", + " 10 kb :10240", + "0.1 MB:104857", + "1 MB:1048576", + "10 MB:10485760", + "10.45 MB:10957619", + "10.75 MB:11272192", + "1,000 KB:1024000", + "1 GB:1073741824", + "0.51 GB:547608330", + "1 TB:1099511627776", + "1023 TB:1124800395214848", + }) + void testValidFileSizes(final String expr, final long expected) { + assertEquals(expected, FileSize.parse(expr, 0)); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java new file mode 100644 index 00000000000..f998c5dbfd2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.time.internal.format.FastDateFormat; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileTime; +import java.time.Duration; +import java.time.Instant; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests {@link OnStartupTriggeringPolicy}. + */ +public class OnStartupTriggeringPolicyTest { + + private static final String TARGET_PATTERN = "/test1-%d{MM-dd-yyyy}-%i.log"; + private static final String TEST_DATA = "Hello world!"; + private static final FastDateFormat formatter = FastDateFormat.getInstance("MM-dd-yyyy"); + + @TempDir + Path tempDir; + + @Test + public void testPolicy() throws Exception { + final Configuration configuration = new DefaultConfiguration(); + final Path target = tempDir.resolve("testfile"); + final long timeStamp = Instant.now().minus(Duration.ofDays(1)).toEpochMilli(); + final String expectedDate = formatter.format(timeStamp); + final Path rolled = tempDir.resolve("test1-" + expectedDate + "-1.log"); + final long copied; + try (final InputStream is = new ByteArrayInputStream(TEST_DATA.getBytes(StandardCharsets.UTF_8))) { + copied = Files.copy(is, target, StandardCopyOption.REPLACE_EXISTING); + } + final long size = Files.size(target); + assertTrue(size > 0); + assertEquals(copied, size); + + final FileTime fileTime = FileTime.fromMillis(timeStamp); + final BasicFileAttributeView attrs = Files.getFileAttributeView(target, BasicFileAttributeView.class); + attrs.setTimes(fileTime, fileTime, fileTime); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%msg").setConfiguration(configuration) + .build(); + final RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder().setCompressionLevelStr("0") + .setStopCustomActionsOnError(true).setConfig(configuration).build(); + final OnStartupTriggeringPolicy policy = OnStartupTriggeringPolicy.createPolicy(1); + + try (final RollingFileManager manager = RollingFileManager.getFileManager(target.toString(), tempDir.toString() + TARGET_PATTERN, true, + false, policy, strategy, null, layout, 8192, true, false, null, null, null, configuration)) { + manager.initialize(); + final String files; + try (Stream contents = Files.list(tempDir)) { + files = contents.map(Path::toString).collect(Collectors.joining(", ", "[", "]")); + } + assertTrue(Files.exists(target), target.toString() + ", files = " + files); + assertEquals(0, Files.size(target), target.toString()); + assertTrue(Files.exists(rolled), "Missing: " + rolled.toString() + ", files on disk = " + files); + assertEquals(size, Files.size(rolled), rolled.toString()); + } + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java similarity index 85% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java index 556268dfd86..4ba1bc1e92d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java @@ -22,9 +22,12 @@ import java.util.Date; import java.util.Locale; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the PatternProcessor class. @@ -36,11 +39,12 @@ private String format(final long time) { } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testDontInterpretBackslashAsEscape() { final PatternProcessor pp = new PatternProcessor("c:\\test\\new/app-%d{HH-mm-ss}.log"); final Calendar cal = Calendar.getInstance(); cal.set(Calendar.HOUR_OF_DAY, 16); - cal.set(Calendar.MINUTE, 02); + cal.set(Calendar.MINUTE, 2); cal.set(Calendar.SECOND, 15); final StringBuilder buf = new StringBuilder(); @@ -49,6 +53,7 @@ public void testDontInterpretBackslashAsEscape() { } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -57,12 +62,13 @@ public void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour() { // expect Wed, March 4, 2014, 11:00 final Calendar expected = Calendar.getInstance(); - expected.set(2014, Calendar.MARCH, 4, 11, 00, 00); + expected.set(2014, Calendar.MARCH, 4, 11, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour2() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -71,12 +77,13 @@ public void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour2() { // expect Wed, March 5, 2014, 00:00 final Calendar expected = Calendar.getInstance(); - expected.set(2014, Calendar.MARCH, 5, 00, 00, 00); + expected.set(2014, Calendar.MARCH, 5, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeHourlyReturnsFirstMinuteOfNextYear() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -90,6 +97,7 @@ public void testGetNextTimeHourlyReturnsFirstMinuteOfNextYear() { } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeMillisecondlyReturnsNextMillisec() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss.SSS}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -106,6 +114,7 @@ public void testGetNextTimeMillisecondlyReturnsNextMillisec() { } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeMinutelyReturnsFirstSecondOfNextMinute() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -116,12 +125,13 @@ public void testGetNextTimeMinutelyReturnsFirstSecondOfNextMinute() { // expect Tue, March 4, 2014, 10:32 final Calendar expected = Calendar.getInstance(); - expected.set(2014, Calendar.MARCH, 4, 10, 32, 00); + expected.set(2014, Calendar.MARCH, 4, 10, 32, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -130,12 +140,13 @@ public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth() { // We expect 1st day of next month final Calendar expected = Calendar.getInstance(); - expected.set(2014, Calendar.NOVEMBER, 1, 00, 00, 00); + expected.set(2014, Calendar.NOVEMBER, 1, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth2() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -144,12 +155,13 @@ public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth2() { // Expect 1st of next month: 2014 Feb 1st final Calendar expected = Calendar.getInstance(); - expected.set(2014, Calendar.FEBRUARY, 1, 00, 00, 00); + expected.set(2014, Calendar.FEBRUARY, 1, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth3() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -158,12 +170,13 @@ public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth3() { // Expect 1st of next month: 2015 Jan 1st final Calendar expected = Calendar.getInstance(); - expected.set(2015, Calendar.JANUARY, 1, 00, 00, 00); + expected.set(2015, Calendar.JANUARY, 1, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeMonthlyReturnsFirstDayOfNextYear() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -172,12 +185,13 @@ public void testGetNextTimeMonthlyReturnsFirstDayOfNextYear() { // We expect 1st day of next month final Calendar expected = Calendar.getInstance(); - expected.set(2016, Calendar.JANUARY, 1, 00, 00, 00); + expected.set(2016, Calendar.JANUARY, 1, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } @Test + @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ) public void testGetNextTimeSecondlyReturnsFirstMillisecOfNextSecond() { final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss}.log.gz"); final Calendar initial = Calendar.getInstance(); @@ -194,6 +208,7 @@ public void testGetNextTimeSecondlyReturnsFirstMillisecOfNextSecond() { } @Test + @ResourceLock(Resources.LOCALE) public void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_FRANCE() { final Locale old = Locale.getDefault(); Locale.setDefault(Locale.FRANCE); // force 1st day of the week to be Monday @@ -206,7 +221,7 @@ public void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_FRANCE() { // expect Monday, March 10, 2014 final Calendar expected = Calendar.getInstance(); - expected.set(2014, Calendar.MARCH, 10, 00, 00, 00); + expected.set(2014, Calendar.MARCH, 10, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } finally { @@ -215,6 +230,7 @@ public void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_FRANCE() { } @Test + @ResourceLock(Resources.LOCALE) public void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_US() { final Locale old = Locale.getDefault(); Locale.setDefault(Locale.US); // force 1st day of the week to be Sunday @@ -227,7 +243,7 @@ public void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_US() { // expect Sunday, March 9, 2014 final Calendar expected = Calendar.getInstance(); - expected.set(2014, Calendar.MARCH, 9, 00, 00, 00); + expected.set(2014, Calendar.MARCH, 9, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } finally { @@ -239,18 +255,19 @@ public void testGetNextTimeWeeklyReturnsFirstDayOfNextWeek_US() { * Tests https://issues.apache.org/jira/browse/LOG4J2-1232 */ @Test + @ResourceLock(Resources.LOCALE) public void testGetNextTimeWeeklyReturnsFirstWeekInYear_US() { final Locale old = Locale.getDefault(); Locale.setDefault(Locale.US); // force 1st day of the week to be Sunday try { final PatternProcessor pp = new PatternProcessor("logs/market_data_msg.log-%d{yyyy-MM-'W'W}"); final Calendar initial = Calendar.getInstance(); - initial.set(2015, Calendar.DECEMBER, 28, 00, 00, 00); // Monday, December 28, 2015 + initial.set(2015, Calendar.DECEMBER, 28, 0, 0, 0); // Monday, December 28, 2015 final long actual = pp.getNextTime(initial.getTimeInMillis(), 1, false); // expect Sunday January 3, 2016 final Calendar expected = Calendar.getInstance(); - expected.set(2016, Calendar.JANUARY, 3, 00, 00, 00); + expected.set(2016, Calendar.JANUARY, 3, 0, 0, 0); expected.set(Calendar.MILLISECOND, 0); assertEquals(format(expected.getTimeInMillis()), format(actual)); } finally { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RandomRollingAppenderOnStartupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RandomRollingAppenderOnStartupTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RandomRollingAppenderOnStartupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RandomRollingAppenderOnStartupTest.java index c24fa50cdba..7ca502aed93 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RandomRollingAppenderOnStartupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RandomRollingAppenderOnStartupTest.java @@ -23,7 +23,7 @@ import java.util.Arrays; import java.util.Collection; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -32,7 +32,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; /** * @@ -86,8 +86,7 @@ public static void afterClass() throws Exception { size = Files.size(path); } else { final long fileSize = Files.size(path); - assertTrue("Expected size: " + size + " Size of " + path.getFileName() + ": " + fileSize, - size == fileSize); + assertEquals("Expected size: " + size + " Size of " + path.getFileName() + ": " + fileSize, size, fileSize); } Files.delete(path); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCountTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCountTest.java new file mode 100644 index 00000000000..4f36297258d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCountTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Validate rolling with a file pattern that contains leading zeros for the increment. + */ +public class RollingAppenderCountTest extends AbstractRollingListenerTest { + + private static final String SOURCE = "src/test/resources/__files"; + private static final String DIR = "target/rolling_count"; + private static final String CONFIG = "log4j-rolling-count.xml"; + private static final String FILENAME = "onStartup.log"; + private static final String TARGET = "rolling_test.log."; + private final CountDownLatch rollover = new CountDownLatch(16); + + @BeforeAll + public static void beforeClass() throws Exception { + Path dir = Path.of(DIR); + if (Files.exists(dir)) { + try (DirectoryStream directoryStream = Files.newDirectoryStream(dir)) { + for (final Path path : directoryStream) { + Files.delete(path); + } + Files.delete(dir); + } + } + if (Files.notExists(dir)) { + Files.createDirectory(dir); + } + Path target = dir.resolve(TARGET + System.currentTimeMillis()); + Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.COPY_ATTRIBUTES); + } + + @Test + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testLog(final @Named("LogTest") Logger logger, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + for (long i = 0; i < 60; ++i) { + logger.info("Sequence: {}", i); + logger.debug(RandomStringUtils.randomAscii(512)); + logger.debug(RandomStringUtils.randomAscii(512)); + } + + rollover.await(); + + final File dir = new File(DIR); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir.listFiles()).hasSize(17); + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeTest.java new file mode 100644 index 00000000000..ff95f16f426 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * LOG4J2-1804. + */ +public class RollingAppenderCronAndSizeTest extends AbstractRollingListenerTest { + + private static final String CONFIG = "log4j-rolling-cron-and-size.xml"; + + private static final String DIR = "target/rolling-cron-size"; + // we'll probably roll over more than 30 times, but that's a sufficient amount of test data + private final CountDownLatch rollover = new CountDownLatch(30); + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + Random rand = new Random(currentTimeMillis.get()); + for (int j=0; j < 100; ++j) { + int count = rand.nextInt(100); + for (int i = 0; i < count; ++i) { + logger.debug("This is test message number {}", i); + } + currentTimeMillis.addAndGet(rand.nextInt(50)); + } + + rollover.await(); + + final File dir = new File(DIR); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir).isDirectoryContaining("glob:**.log"); + final File[] files = dir.listFiles(); + assertNotNull(files); + Arrays.sort(files); + int fileCounter = 0; + String previous = ""; + for (final File file: files) { + final String actual = file.getName(); + final String[] fileParts = actual.split("[_.]"); + fileCounter = previous.equals(fileParts[1]) ? ++fileCounter : 1; + previous = fileParts[1]; + assertEquals(Integer.toString(fileCounter), fileParts[2], + "Incorrect file name. Expected counter value of " + fileCounter + " in " + actual); + } + + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2DirectTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2DirectTest.java new file mode 100644 index 00000000000..c3f694dddda --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2DirectTest.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.Random; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class RollingAppenderCronEvery2DirectTest extends AbstractRollingListenerTest { + + private static final String CONFIG = "log4j-rolling-cron-every2-direct.xml"; + private static final String DIR = "target/rolling-cron-every2Direct"; + private final CountDownLatch rollover = new CountDownLatch(2); + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + final long end = currentTimeMillis.get() + 5000; + final Random rand = new Random(end); + int count = 1; + do { + logger.debug("Log Message {}", count++); + currentTimeMillis.addAndGet(10 * rand.nextInt(100)); + } while (currentTimeMillis.get() < end); + + rollover.await(); + final Path dir = Path.of(DIR); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir).isDirectoryContaining("glob:**.gz"); + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2Test.java new file mode 100644 index 00000000000..cdaf9e84620 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronEvery2Test.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.Random; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class RollingAppenderCronEvery2Test extends AbstractRollingListenerTest { + + private static final String CONFIG = "log4j-rolling-cron-every2.xml"; + private static final String DIR = "target/rolling-cron-every2"; + private static final String FILE = "target/rolling-cron-every2/rollingtest.log"; + private final CountDownLatch rollover = new CountDownLatch(3); + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + assertThat(Path.of(FILE)).exists(); + final long end = currentTimeMillis.get() + 5000; + final Random rand = new Random(end); + int count = 1; + do { + logger.debug("Log Message {}", count++); + currentTimeMillis.addAndGet(10 * rand.nextInt(100)); + } while (currentTimeMillis.get() < end); + + rollover.await(); + + final Path dir = Path.of(DIR); + assertThat(dir).exists(); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir).isDirectoryContaining("glob:**.gz"); + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnStartupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnStartupTest.java new file mode 100644 index 00000000000..f7a16875446 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnStartupTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * + */ +public class RollingAppenderCronOnStartupTest { + + private static final String CONFIG = "log4j-rolling-cron-onStartup.xml"; + private static final String DIR = "target/rolling-cron-onStartup"; + private static final String FILE = "target/rolling-cron-onStartup/rollingtest.log"; + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private LoggerContext context; + + @AfterAll + public static void after() { + File dir = new File(DIR); + if (dir.exists()) { + cleanDir(dir); + dir.delete(); + } + + } + + @AfterEach + public void afterEach() { + if (context != null) { + context.stop(); + } + } + + @Test + public void testAppender() throws Exception { + final File dir = new File(DIR); + if (dir.exists()) { + cleanDir(dir); + } else { + dir.mkdirs(); + } + final File file = new File(FILE); + String today = formatter.format(LocalDate.now()); + final File rolled = new File(DIR + "/test1-" + today + ".log"); + PrintStream ps = new PrintStream(new FileOutputStream(file)); + ps.println("This is a line2"); + ps.close(); + ps = new PrintStream(new FileOutputStream(rolled)); + ps.println("This is a line 1"); + ps.close(); + assertTrue("Log file does not exist", file.exists()); + assertTrue("Log file does not exist", rolled.exists()); + LoggerContext lc = Configurator.initialize("Test", CONFIG); + final Logger logger = lc.getLogger(RollingAppenderCronOnStartupTest.class); + logger.info("This is line 3"); + File[] files = dir.listFiles(); + assertNotNull("No files", files); + assertEquals("Unexpected number of files. Expected 2 but found " + files.length, 2, + files.length); + List lines = Files.readAllLines(file.toPath()); + assertEquals("Unexpected number of lines. Expected 2: Actual: " + lines.size(), 2, lines.size()); + lines = Files.readAllLines(rolled.toPath()); + assertEquals("Unexpected number of lines. Expected 1: Actual: " + lines.size(), 1, lines.size()); + } + + private static void cleanDir(File dir) { + File[] files = dir.listFiles(); + if (files != null) { + Arrays.stream(files).forEach(File::delete); + } + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnceADayTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnceADayTest.java new file mode 100644 index 00000000000..0f5f0039934 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronOnceADayTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.File; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.concurrent.CountDownLatch; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.CronExpression; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("flaky") +@Disabled("https://issues.apache.org/jira/browse/LOG4J2-3633") +public class RollingAppenderCronOnceADayTest extends AbstractRollingListenerTest { + + private static final int CRON_DELAY = 10; + private static final String CONFIG = "log4j-rolling-cron-once-a-day.xml"; + private static final String CONFIG_TARGET = "log4j-rolling-cron-once-a-day-target.xml"; + private static final String TARGET = "target"; + private static final String DIR = TARGET + "/rolling-cron-once-a-day"; + private static final String FILE = DIR + "/rollingtest.log"; + private static final String TARGET_TEST_CLASSES = TARGET + "/test-classes"; + + private static String cronExpression; + private static Duration remainingTime; + private final CountDownLatch rollover = new CountDownLatch(1); + + + @BeforeAll + public static void beforeClass() throws Exception { + final Path src = FileSystems.getDefault().getPath(TARGET_TEST_CLASSES, CONFIG); + String content = Files.readString(src); + final ZonedDateTime now = ZonedDateTime.now(); + final ZonedDateTime end = now.plusSeconds(CRON_DELAY); + remainingTime = Duration.between(now, end); + cronExpression = String.format("%d %d %d * * ?", end.getSecond(), end.getMinute(), end.getHour()); + content = content.replace("@CRON_EXPR@", cronExpression); + Files.writeString(Path.of(TARGET_TEST_CLASSES, CONFIG_TARGET), content); + StatusLogger.getLogger().debug("Cron expression will be " + cronExpression + " in " + remainingTime); + } + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG_TARGET, timeout = 10) + public void testAppender(final Logger logger, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + final File file = new File(FILE); + assertTrue(file.exists(), "Log file does not exist"); + logger.debug("This is test message number 1, waiting for rolling"); + + final TriggeringPolicy policy = manager.getTriggeringPolicy(); + assertThat(policy).isInstanceOf(CronTriggeringPolicy.class); + final CronExpression expression = ((CronTriggeringPolicy) policy).getCronExpression(); + assertEquals(cronExpression, expression.getCronExpression(), "Incorrect cron expression"); + logger.debug("Cron expression will be {}", expression.getCronExpression()); + + // force a rollover + final long delta = remainingTime.dividedBy(20).toMillis(); + for (int i = 1; i <= 20; ++i) { + logger.debug("Adding first event {}", i); + currentTimeMillis.addAndGet(delta); + } + + rollover.await(); + final File dir = new File(DIR); + assertThat(dir).isNotEmptyDirectory(); + + for (int i = 1; i < 5; i++) { + logger.debug("Adding some more event {}", i); + currentTimeMillis.addAndGet(1000); + } + assertThat(dir).isDirectoryContaining("glob:**.gz"); + assertThat(dir.listFiles(pathname -> pathname.getName().endsWith(".gz"))).hasSize(1); + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronTest.java new file mode 100644 index 00000000000..20b8bfb293a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.CronExpression; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Test; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + */ +public class RollingAppenderCronTest extends AbstractRollingListenerTest implements PropertyChangeListener { + + private static final String CONFIG = "log4j-rolling-cron.xml"; + private static final String DIR = "target/rolling-cron"; + private static final String FILE = "target/rolling-cron/rollingtest.log"; + private final CountDownLatch rollover = new CountDownLatch(2); + private final CountDownLatch reconfigured = new CountDownLatch(1); + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + final Logger logger = context.getLogger(getClass()); + final File file = new File(FILE); + assertThat(file).exists(); + logger.debug("This is test message number 1"); + currentTimeMillis.addAndGet(2500); + rollover.await(); + + final File dir = new File(DIR); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir).isDirectoryContaining("glob:**.gz"); + + final Path src = Path.of("target", "test-classes", "log4j-rolling-cron2.xml"); + context.addPropertyChangeListener(this); + try (OutputStream os = Files.newOutputStream(Path.of("target", "test-classes", "log4j-rolling-cron.xml"))) { + Files.copy(src, os); + } + currentTimeMillis.addAndGet(5000); + // force a reconfiguration + for (int i = 0; i < 20; ++i) { + logger.debug("Adding new event {}", i); + } + currentTimeMillis.addAndGet(1000); + reconfigured.await(); + final RollingFileAppender appender = context.getConfiguration().getAppender("RollingFile"); + final TriggeringPolicy policy = appender.getManager().getTriggeringPolicy(); + assertThat(policy).isNotNull(); + assertThat(policy).isInstanceOf(CronTriggeringPolicy.class); + final CronExpression expression = ((CronTriggeringPolicy) policy).getCronExpression(); + assertEquals("* * * ? * *", expression.getCronExpression(), "Incorrect triggering policy"); + + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } + + @Override + public void propertyChange(final PropertyChangeEvent evt) { + reconfigured.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCustomDeleteActionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCustomDeleteActionTest.java new file mode 100644 index 00000000000..5f676b03907 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCustomDeleteActionTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Arrays; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * + */ +@Tag("sleepy") +public class RollingAppenderCustomDeleteActionTest { + + private static final String CONFIG = "log4j-rolling-with-custom-delete.xml"; + private static final String DIR = "target/rolling-with-delete/test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + final Logger logger = context.getLogger(getClass()); + // Trigger the rollover + for (int i = 0; i < 10; ++i) { + // 30 chars per message: each message triggers a rollover + logger.debug("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + assertTrue(dir.listFiles().length > 0, "Dir " + DIR + " should contain files"); + + final int MAX_TRIES = 20; + for (int i = 0; i < MAX_TRIES; i++) { + final File[] files = dir.listFiles(); + for (final File file : files) { + System.out.println(file); + } + if (files.length == 3) { + for (final File file : files) { + assertTrue(Arrays.asList("test-1.log", "test-2.log", "test-3.log").contains(file.getName()), + "test-4.log should have been deleted"); + } + return; // test succeeded + } + logger.debug("Adding additional event " + i); + Thread.sleep(100); // Allow time for rollover to complete + } + fail("No rollover files found"); + } + + public static void main(final String[] args) { + final Pattern p = Pattern.compile("test-.?[2,4,6,8,0]\\.log\\.gz"); + for (int i = 0; i < 16; i++) { + final String str = "test-" + i + ".log.gz"; + final java.util.regex.Matcher m = p.matcher(str); + System.out.println(m.matches() + ": " + str); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount1Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount1Test.java new file mode 100644 index 00000000000..9ca24933b3f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount1Test.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests that sibling conditions are invoked in configured order. + * This does not work for properties configurations. Use nested conditions instead. + */ +@Tag("sleepy") +public class RollingAppenderDeleteAccumulatedCount1Test { + private static final String CONFIG = "log4j-rolling-with-custom-delete-accum-count1.xml"; + private static final String DIR = "target/rolling-with-delete-accum-count1/test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + final Path p1 = writeTextTo(DIR + "/my-1.log"); // glob="test-*.log" + final Path p2 = writeTextTo(DIR + "/my-2.log"); + final Path p3 = writeTextTo(DIR + "/my-3.log"); + final Path p4 = writeTextTo(DIR + "/my-4.log"); + final Path p5 = writeTextTo(DIR + "/my-5.log"); + + final Logger logger = context.getLogger(getClass()); + for (int i = 0; i < 10; ++i) { + updateLastModified(p1, p2, p3, p4, p5); // make my-*.log files most recent + + // 30 chars per message: each message triggers a rollover + logger.debug("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertTrue(files.length > 0, "Dir " + DIR + " should contain files"); + for (final File file : files) { + BasicFileAttributes fileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + System.out.println(file + " (" + fileAttributes.size() + "B) " + + FixedDateFormat.create(FixedFormat.ABSOLUTE).format(fileAttributes.lastModifiedTime().toMillis())); + } + final List expected = List.of("my-1.log", "my-2.log", "my-3.log", "my-4.log", "my-5.log"); + assertEquals(expected.size() + 6, files.length, Arrays.toString(files)); + for (final File file : files) { + if (!expected.contains(file.getName()) && !file.getName().startsWith("test-")) { + fail("unexpected file" + file); + } + } + } + + private void updateLastModified(final Path... paths) throws IOException { + for (final Path path : paths) { + Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis() + 2000)); + } + } + + private Path writeTextTo(final String location) throws IOException { + final Path path = Paths.get(location); + Files.createDirectories(path.getParent()); + try (BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { + buffy.write("some text"); + buffy.newLine(); + buffy.flush(); + } + return path; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount2Test.java new file mode 100644 index 00000000000..c86ba3dd4c9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedCount2Test.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests that sibling conditions are invoked in configured order. + * This does not work for properties configurations. Use nested conditions instead. + */ +@Tag("sleepy") +public class RollingAppenderDeleteAccumulatedCount2Test { + private static final String CONFIG = "log4j-rolling-with-custom-delete-accum-count2.xml"; + private static final String DIR = "target/rolling-with-delete-accum-count2/test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + final Path p1 = writeTextTo(DIR + "/my-1.log"); // glob="test-*.log" + final Path p2 = writeTextTo(DIR + "/my-2.log"); + final Path p3 = writeTextTo(DIR + "/my-3.log"); + final Path p4 = writeTextTo(DIR + "/my-4.log"); + final Path p5 = writeTextTo(DIR + "/my-5.log"); + + final Logger logger = context.getLogger(getClass()); + for (int i = 0; i < 10; ++i) { + updateLastModified(p1, p2, p3, p4, p5); // make my-*.log files most recent + + // 30 chars per message: each message triggers a rollover + logger.debug("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + + final File[] files = dir.listFiles(); + assertNotNull(files); + assertTrue(files.length > 0, "Dir " + DIR + " should contain files"); + for (final File file : files) { + BasicFileAttributes fileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + System.out.println(file + " (" + fileAttributes.size() + "B) " + + FixedDateFormat.create(FixedFormat.ABSOLUTE).format(fileAttributes.lastModifiedTime().toMillis())); + } + // sometimes "test-9.log", sometimes "test-10.log" remains + final List expected = List.of("my-1.log", "my-2.log", "my-3.log", "my-4.log", "my-5.log"); + assertEquals(expected.size() + 1, files.length, Arrays.toString(files)); + for (final File file : files) { + if (!expected.contains(file.getName()) && !file.getName().startsWith("test-")) { + fail("unexpected file" + file); + } + } + } + + private void updateLastModified(final Path... paths) throws IOException { + for (final Path path : paths) { + Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis() + 2000)); + } + } + + private Path writeTextTo(final String location) throws IOException { + final Path path = Paths.get(location); + Files.createDirectories(path.getParent()); + try (BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { + buffy.write("some text"); + buffy.newLine(); + buffy.flush(); + } + return path; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedSizeTest.java new file mode 100644 index 00000000000..3c521a115e2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteAccumulatedSizeTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +@Tag("sleepy") +public class RollingAppenderDeleteAccumulatedSizeTest { + private static final String CONFIG = "log4j-rolling-with-custom-delete-accum-size.xml"; + private static final String DIR = "target/rolling-with-delete-accum-size/test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + + final Logger logger = context.getLogger(getClass()); + for (int i = 0; i < 10; ++i) { + // 30 chars per message: each message triggers a rollover + logger.debug("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + + final File[] files = dir.listFiles(); + assertNotNull(files); + assertTrue(files.length > 0, "Dir " + DIR + " should contain files"); + for (final File file : files) { + BasicFileAttributes fileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + System.out.println(file + " (" + fileAttributes.size() + "B) " + + FixedDateFormat.create(FixedFormat.ABSOLUTE).format(fileAttributes.lastModifiedTime().toMillis())); + } + assertEquals(4, files.length, Arrays.toString(files)); + long total = 0; + for (final File file : files) { + // sometimes test-6.log remains + assertTrue(file.getName().startsWith("test-"), "unexpected file " + file); + total += file.length(); + } + assertTrue(total <= 500, "accumulatedSize=" + total); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteMaxDepthTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteMaxDepthTest.java new file mode 100644 index 00000000000..d52c591cb66 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteMaxDepthTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +@Tag("sleepy") +public class RollingAppenderDeleteMaxDepthTest { + + private static final String CONFIG = "log4j-rolling-with-custom-delete-maxdepth.xml"; + private static final String DIR = "target/rolling-with-delete-depth/test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + // create some files that match the glob but exceed maxDepth + final Path p1 = writeTextTo(DIR + "/1/test-4.log"); // glob="**/test-4.log" + final Path p2 = writeTextTo(DIR + "/2/test-4.log"); + final Path p3 = writeTextTo(DIR + "/1/2/test-4.log"); + final Path p4 = writeTextTo(DIR + "/1/2/3/test-4.log"); + + final Logger logger = context.getLogger(getClass()); + for (int i = 0; i < 10; ++i) { + // 30 chars per message: each message triggers a rollover + logger.debug("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + + final File[] files = dir.listFiles(); + final List expected = List.of("1", "2", "test-1.log", "test-2.log", "test-3.log"); + assertNotNull(files); + assertTrue(files.length > 0, "Dir " + DIR + " should contain files"); + assertEquals(expected.size(), files.length, Arrays.toString(files)); + for (final File file : files) { + assertTrue(expected.contains(file.getName()), "test-4.log should have been deleted"); + } + + assertTrue(Files.exists(p1), p1 + " should not have been deleted"); + assertTrue(Files.exists(p2), p2 + " should not have been deleted"); + assertTrue(Files.exists(p3), p3 + " should not have been deleted"); + assertTrue(Files.exists(p4), p4 + " should not have been deleted"); + } + + private Path writeTextTo(final String location) throws IOException { + final Path path = Paths.get(location); + Files.createDirectories(path.getParent()); + try (BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { + buffy.write("some text"); + buffy.newLine(); + buffy.flush(); + } + return path; + } + + public static void main(final String[] args) { + final Pattern p = Pattern.compile("test-.?[2,4,6,8,0]\\.log\\.gz"); + for (int i = 0; i < 16; i++) { + final String str = "test-" + i + ".log.gz"; + final java.util.regex.Matcher m = p.matcher(str); + System.out.println(m.matches() + ": " + str); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteNestedTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteNestedTest.java new file mode 100644 index 00000000000..014dec89abf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDeleteNestedTest.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +@Tag("sleepy") +public class RollingAppenderDeleteNestedTest { + private static final String CONFIG = "log4j-rolling-with-custom-delete-nested.xml"; + private static final String DIR = "target/rolling-with-delete-nested/test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + final Path p1 = writeTextTo(DIR + "/my-1.log"); // glob="test-*.log" + final Path p2 = writeTextTo(DIR + "/my-2.log"); + final Path p3 = writeTextTo(DIR + "/my-3.log"); + final Path p4 = writeTextTo(DIR + "/my-4.log"); + final Path p5 = writeTextTo(DIR + "/my-5.log"); + + final Logger logger = context.getLogger(getClass()); + for (int i = 0; i < 10; ++i) { + updateLastModified(p1, p2, p3, p4, p5); // make my-*.log files most recent + + // 30 chars per message: each message triggers a rollover + logger.debug("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + + final File[] files = dir.listFiles(); + assertNotNull(files); + assertTrue(files.length > 0, "Dir " + DIR + " should contain files"); + for (final File file : files) { + BasicFileAttributes fileAttributes = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + System.out.println(file + " (" + fileAttributes.size() + "B) " + + FixedDateFormat.create(FixedFormat.ABSOLUTE).format(fileAttributes.lastModifiedTime().toMillis())); + } + + final List expected = Arrays.asList("my-1.log", "my-2.log", "my-3.log", "my-4.log", "my-5.log"); + assertEquals(expected.size() + 3, files.length, Arrays.toString(files)); + for (final File file : files) { + if (!expected.contains(file.getName()) && !file.getName().startsWith("test-")) { + fail("unexpected file" + file); + } + } + } + + private void updateLastModified(final Path... paths) throws IOException { + for (final Path path : paths) { + Files.setLastModifiedTime(path, FileTime.fromMillis(System.currentTimeMillis() + 2000)); + } + } + + private Path writeTextTo(final String location) throws IOException { + final Path path = Paths.get(location); + Files.createDirectories(path.getParent()); + try (BufferedWriter buffy = Files.newBufferedWriter(path, Charset.defaultCharset())) { + buffy.write("some text"); + buffy.newLine(); + buffy.flush(); + } + return path; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectCronTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectCronTest.java new file mode 100644 index 00000000000..e700303f412 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectCronTest.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.RollingFileAppender; + +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * + */ +public class RollingAppenderDirectCronTest { + + private static final String CONFIG = "log4j-rolling-direct-cron.xml"; + private static final String DIR = "target/rolling-direct-cron"; + + private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + + private final Pattern filePattern = Pattern.compile(".*(\\d\\d-\\d\\d-\\d\\d-\\d\\d-\\d\\d-\\d\\d).*$"); + + @Test + public void testAppender() throws Exception { + // TODO Is there a better way to test than putting the thread to sleep all over the place? + RollingFileAppender app = loggerContextRule.getAppender("RollingFile"); + final Logger logger = loggerContextRule.getLogger(); + logger.debug("This is test message number 1"); + RolloverDelay delay = new RolloverDelay(app.getManager()); + delay.waitForRollover(); + final File dir = new File(DIR); + File[] files = dir.listFiles(); + assertTrue("Directory not created", dir.exists() && files != null && files.length > 0); + delay.reset(3); + + final int MAX_TRIES = 30; + for (int i = 0; i < MAX_TRIES; ++i) { + logger.debug("Adding new event {}", i); + Thread.sleep(100); + } + delay.waitForRollover(); + } + + + + private class RolloverDelay implements RolloverListener { + private volatile CountDownLatch latch; + + public RolloverDelay(RollingFileManager manager) { + latch = new CountDownLatch(1); + manager.addRolloverListener(this); + } + + public void waitForRollover() { + try { + if (!latch.await(3, TimeUnit.SECONDS)) { + fail("failed to rollover"); + } + } catch (InterruptedException ex) { + fail("failed to rollover"); + } + } + + public void reset(int count) { + latch = new CountDownLatch(count); + } + + @Override + public void rolloverTriggered(String fileName) { + + } + + @Override + public void rolloverComplete(String fileName) { + java.util.regex.Matcher matcher = filePattern.matcher(fileName); + assertTrue("Invalid file name: " + fileName, matcher.matches()); + Path path = new File(fileName).toPath(); + try { + List lines = Files.readAllLines(path); + assertTrue("Not enough lines in " + fileName + ":" + lines.size(), lines.size() > 0); + assertTrue("log and file times don't match. file: " + matcher.group(1) + ", log: " + + lines.get(0), lines.get(0).startsWith(matcher.group(1))); + } catch (IOException ex) { + fail("Unable to read file " + fileName + ": " + ex.getMessage()); + } + latch.countDown(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWrite1906Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWrite1906Test.java new file mode 100644 index 00000000000..789cbffb35a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWrite1906Test.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + */ +public class RollingAppenderDirectWrite1906Test implements RolloverListener { + + private static final String CONFIG = "log4j-rolling-direct-1906.xml"; + + private static final String DIR = "target/rolling-direct-1906"; + private final CountDownLatch rollover = new CountDownLatch(2); + + @BeforeAll + static void beforeAll() { + StatusLogger.getLogger().registerListener(new NoopStatusListener()); + } + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + final var logger = context.getLogger(getClass()); + int count = 100; + for (int i = 0; i < count; ++i) { + logger.debug("This is test message number " + i); + Thread.sleep(50); + } + rollover.await(); + final Path dir = Path.of(DIR); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir).isDirectoryContaining("glob:**.log"); + + try (final Stream files = Files.list(dir)) { + final AtomicInteger found = new AtomicInteger(); + assertThat(files).allSatisfy(file -> { + final String expected = file.getFileName().toString(); + try (final Stream stream = Files.lines(file)) { + final List lines = stream + .map(line -> String.format("rollingfile.%s.log", line.substring(0, line.indexOf(' ')))) + .collect(Collectors.toList()); + found.addAndGet(lines.size()); + assertThat(lines).allSatisfy(actual -> assertThat(actual).isEqualTo(expected)); + } + }); + assertEquals(count, found.get(), "Incorrect number of events read. Expected " + count + ", Actual " + found.get()); + } + } + + @Override + public void rolloverTriggered(String fileName) { + + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } + + private static class NoopStatusListener implements StatusListener { + @Override + public void log(StatusData data) { + + } + + @Override + public Level getStatusLevel() { + return Level.TRACE; + } + + @Override + public void close() throws IOException { + + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteStartupSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteStartupSizeTest.java new file mode 100644 index 00000000000..d93468ce89f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteStartupSizeTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.test.junit.CleanFolders; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test LOG4J2-2485. + */ +public class RollingAppenderDirectWriteStartupSizeTest { + + private static final String CONFIG = "log4j-rolling-direct-startup-size.xml"; + + private static final String DIR = "target/rolling-direct-startup-size"; + + private static final String FILE = "size-test.log"; + + private static final String MESSAGE = "test message"; + + @Rule + public LoggerContextRule loggerContextRule = LoggerContextRule + .createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public CleanFolders cleanFolders = new CleanFolders(false, true, 10, DIR); + + @BeforeClass + public static void beforeClass() throws Exception { + Path log = Paths.get(DIR, FILE); + if (Files.exists(log)) { + Files.delete(log); + } + + Files.createDirectories(log.getParent()); + Files.createFile(log); + Files.write(log, MESSAGE.getBytes()); + } + + @Test + public void testRollingFileAppenderWithReconfigure() throws Exception { + final RollingFileAppender rfAppender = loggerContextRule.getRequiredAppender("RollingFile", + RollingFileAppender.class); + final RollingFileManager manager = rfAppender.getManager(); + + Assert.assertNotNull(manager); + Assert.assertEquals("Existing file size not preserved on startup", MESSAGE.getBytes().length, manager.size); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTempCompressedFilePatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTempCompressedFilePatternTest.java new file mode 100644 index 00000000000..f9a768c635b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTempCompressedFilePatternTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.io.File; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.jupiter.api.Assertions.*; + +/** + * LOG4J2-1766. + */ +@Tag("sleepy") +@DisabledOnOs(value = OS.MAC, disabledReason = "FileWatcher is not fast enough on macOS for this test") +public class RollingAppenderDirectWriteTempCompressedFilePatternTest { + + private static final String CONFIG = "log4j-rolling-direct-tmp-compress-folder.xml"; + + private static final String DIR = "target/rolling-direct"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + final File dir = new File(DIR); + dir.mkdirs(); + final var logger = context.getLogger(getClass()); + try (final WatchService watcher = FileSystems.getDefault().newWatchService()) { + WatchKey key = dir.toPath().register(watcher, StandardWatchEventKinds.ENTRY_CREATE); + + for (int i = 0; i < 100; ++i) { + logger.debug("This is test message number " + i); + } + Thread.sleep(50); + assertTrue(dir.exists() && dir.listFiles().length > 0, "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".gz")))))); + + int temporaryFilesCreated = 0; + int compressedFiles = 0; + key = watcher.take(); + + for (final WatchEvent event : key.pollEvents()) { + final WatchEvent ev = (WatchEvent) event; + final Path filename = ev.context(); + if (filename.toString().endsWith(".tmp")) { + temporaryFilesCreated++; + } + if (filename.toString().endsWith(".gz")) { + compressedFiles++; + } + } + assertTrue(temporaryFilesCreated > 0, "No temporary file created during compression"); + assertEquals(compressedFiles, temporaryFilesCreated, + "Temporary files created not equals to compressed files"); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.java new file mode 100644 index 00000000000..448219291d7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.InputStreamReader; +import java.util.zip.GZIPInputStream; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +@Tag("sleepy") +public class RollingAppenderDirectWriteTest { + + private static final String CONFIG = "log4j-rolling-direct.xml"; + + private static final String DIR = "target/rolling-direct"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final LoggerContext context) throws Exception { + final var logger = context.getLogger(getClass()); + int count = 100; + for (int i=0; i < count; ++i) { + logger.debug("This is test message number " + i); + } + Thread.sleep(50); + final File dir = new File(DIR); + assertTrue(dir.exists() && dir.listFiles().length > 0, "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".gz")))))); + int found = 0; + for (File file: files) { + String actual = file.getName(); + BufferedReader reader; + if (file.getName().endsWith(".gz")) { + reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file)))); + } else { + reader = new BufferedReader(new FileReader(file)); + } + String line; + while ((line = reader.readLine()) != null) { + assertNotNull(line, "No log event in file " + actual); + String[] parts = line.split((" ")); + String expected = "test1-" + parts[0]; + assertTrue(actual.startsWith(expected), + "Incorrect file name. Expected file prefix: " + expected + " Actual: " + actual); + ++found; + } + reader.close(); + } + assertEquals(count, found, "Incorrect number of events read. Expected " + count + ", Actual " + found); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithFilenameTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithFilenameTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithFilenameTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithFilenameTest.java index 2f346c11227..fe443481faa 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithFilenameTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithFilenameTest.java @@ -19,7 +19,7 @@ import java.io.File; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java new file mode 100644 index 00000000000..786753f1397 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.layout.HtmlLayout; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.IOUtils; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for LOG4J2-2760 + */ +@Tag("sleepy") +@CleanUpDirectories("target/rolling-direct-htmlLayout") +@LoggerContextSource(value = "") +public class RollingAppenderDirectWriteWithHtmlLayoutTest { + + private static final String DIR = "target/rolling-direct-htmlLayout"; + + @Test + public void testRollingFileAppenderWithHtmlLayout(final Configuration config) throws Exception { + checkAppenderWithHtmlLayout(true, config); + } + + @Test + public void testRollingFileAppenderWithHtmlLayoutNoAppend(final Configuration config) throws Exception { + checkAppenderWithHtmlLayout(false, config); + } + + private void checkAppenderWithHtmlLayout(boolean append, final Configuration config) throws InterruptedException, IOException { + String prefix = "testHtml_" + (append ? "append_" : "noAppend_"); + RollingFileAppender appender = RollingFileAppender.newBuilder() + .setName("RollingHtml") + .setFilePattern(DIR + "/" + prefix + "_-%d{MM-dd-yy-HH-mm}-%i.html") + .setPolicy(new SizeBasedTriggeringPolicy(500)) + .setStrategy(DirectWriteRolloverStrategy.newBuilder() + .setConfig(config) + .build()) + .setLayout(HtmlLayout.createDefaultLayout()) + .setAppend(append) + .build(); + boolean stopped = false; + try { + int count = 100; + for (int i = 0; i < count; ++i) { + appender.append(Log4jLogEvent.newBuilder() + .setMessage(new SimpleMessage("This is test message number " + i)) + .build() + ); + } + appender.getManager().flush(); + appender.stop(); + stopped = true; + Thread.sleep(50); + final File dir = new File(DIR); + assertTrue(dir.exists(), "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".html")))))); + + int foundEvents = 0; + final Pattern eventMatcher = Pattern.compile("title=\"Message\""); + for (File file : files) { + if (!file.getName().startsWith(prefix)) + continue; + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String data = IOUtils.toString(reader).trim(); + // check that every file starts with the header + assertThat("header in file " + file, data, Matchers.startsWith("")); + final Matcher matcher = eventMatcher.matcher(data); + while (matcher.find()) { + foundEvents++; + } + } + } + assertEquals(count, foundEvents, "Incorrect number of events read."); + } finally { + if (!stopped) { + appender.stop(); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithReconfigureTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithReconfigureTest.java new file mode 100644 index 00000000000..95ce08e4fbb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithReconfigureTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE + * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file + * to You under the Apache license, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the license for the specific language + * governing permissions and limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.net.URI; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + */ +public class RollingAppenderDirectWriteWithReconfigureTest extends AbstractRollingListenerTest { + + private static final String CONFIG = "log4j-rolling-direct-reconfigure.xml"; + + private static final String DIR = "target/rolling-direct-reconfigure"; + + private final CountDownLatch rollover = new CountDownLatch(1); + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testRollingFileAppenderWithReconfigure(final LoggerContext context) throws Exception { + final var logger = context.getLogger(getClass()); + logger.debug("Before reconfigure"); + + context.setConfigLocation(new URI(CONFIG)); + context.reconfigure(); + Configuration config = context.getConfiguration(); + final RollingFileAppender appender = config.getAppender("RollingFile"); + appender.getManager().addRolloverListener(this); + logger.debug("Force a rollover"); + rollover.await(); + final File dir = new File(DIR); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir.listFiles()).hasSize(2); + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderNoUnconditionalDeleteTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderNoUnconditionalDeleteTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderNoUnconditionalDeleteTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderNoUnconditionalDeleteTest.java index 0d34be6ccc1..3e376fc8781 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderNoUnconditionalDeleteTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderNoUnconditionalDeleteTest.java @@ -16,27 +16,30 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import java.io.File; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.jupiter.api.Tag; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static org.junit.Assert.*; +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * */ @RunWith(Parameterized.class) +@Tag("sleepy") public class RollingAppenderNoUnconditionalDeleteTest { private final File directory; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupDirectTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupDirectTest.java new file mode 100644 index 00000000000..1cb9efd2936 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupDirectTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileTime; +import java.time.Instant; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * + */ +public class RollingAppenderOnStartupDirectTest { + + private static final String SOURCE = "src/test/resources/__files"; + private static final String DIR = "target/onStartup"; + private static final String CONFIG = "log4j-rollOnStartupDirect.xml"; + private static final String FILENAME = "onStartup.log"; + private static final String PREFIX = "This is test message number "; + private static final String ROLLED = "onStartup-"; + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd-yyyy"); + + private static LoggerContext loggerContext; + + @BeforeClass + public static void beforeClass() throws Exception { + if (Files.exists(Paths.get("target/onStartup"))) { + try (DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + Files.delete(path); + } + Files.delete(Paths.get(DIR)); + } + } + Files.createDirectory(new File(DIR).toPath()); + String fileName = ROLLED + formatter.format(LocalDate.now()) + "-1.log"; + Path target = Paths.get(DIR, fileName); + Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.COPY_ATTRIBUTES); + FileTime newTime = FileTime.from(Instant.now().minus(1, ChronoUnit.DAYS)); + Files.getFileAttributeView(target, BasicFileAttributeView.class).setTimes(newTime, newTime, newTime); + } + + @Test + public void performTest() throws Exception { + loggerContext = Configurator.initialize("Test", CONFIG); + final Logger logger = loggerContext.getLogger(RollingAppenderOnStartupDirectTest.class); + for (int i = 3; i < 10; ++i) { + logger.debug(PREFIX + i); + } + int fileCount = 0; + try (DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + ++fileCount; + if (path.toFile().getName().startsWith(ROLLED)) { + List lines = Files.readAllLines(path); + assertTrue("No messages in " + path.toFile().getName(), lines.size() > 0); + assertTrue("Missing message for " + path.toFile().getName(), + lines.get(0).startsWith(PREFIX)); + } + } + } + assertEquals("File did not roll", 2, fileCount); + } + + @AfterClass + public static void afterClass() throws Exception { + Configurator.shutdown(loggerContext); + try (DirectoryStream directoryStream = Files.newDirectoryStream(Paths.get(DIR))) { + for (final Path path : directoryStream) { + Files.delete(path); + } + } + Files.delete(Paths.get(DIR)); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java new file mode 100644 index 00000000000..f755a159f8b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileTime; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import org.apache.commons.io.file.PathUtils; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class RollingAppenderOnStartupTest { + + private static final Path SOURCE = Path.of("src", "test", "resources", "__files"); + private static final Path DIR = Path.of("target", "onStartup"); + private static final String FILENAME = "onStartup.log"; + private static final String PREFIX = "This is test message number "; + private static final String ROLLED = "onStartup-"; + + @BeforeAll + static void setUp() throws IOException { + if (Files.isDirectory(DIR)) { + PathUtils.deleteDirectory(DIR); + } + Files.createDirectory(DIR); + final Path target = Files.copy(SOURCE.resolve(FILENAME), DIR.resolve(FILENAME), StandardCopyOption.COPY_ATTRIBUTES); + final FileTime newTime = FileTime.from(Instant.now().minus(1, ChronoUnit.DAYS)); + Files.getFileAttributeView(target, BasicFileAttributeView.class).setTimes(newTime, newTime, newTime); + } + + @AfterAll + static void tearDown() throws IOException { + PathUtils.deleteDirectory(DIR); + } + + @Test + @LoggerContextSource("log4j-rollOnStartup.xml") + public void performTest(final LoggerContext loggerContext) throws Exception { + boolean rolled = false; + final Logger logger = loggerContext.getLogger(RollingAppenderOnStartupTest.class); + for (int i = 3; i < 10; ++i) { + logger.debug(PREFIX + i); + } + try (DirectoryStream directoryStream = Files.newDirectoryStream(DIR)) { + for (final Path path : directoryStream) { + if (path.toFile().getName().startsWith(ROLLED)) { + rolled = true; + List lines = Files.readAllLines(path); + assertTrue(lines.size() > 0, "No messages in " + path.toFile().getName()); + assertTrue(lines.get(0).startsWith(PREFIX + "1"), "Missing message for " + path.toFile().getName()); + } + } + } + assertTrue(rolled, "File did not roll"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.java index 15961d4494f..dfe8bbade94 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderReconfigureTest.java @@ -14,8 +14,8 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import static org.apache.logging.log4j.hamcrest.Descriptors.that; -import static org.apache.logging.log4j.hamcrest.FileMatchers.hasName; +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.hasItemInArray; import static org.junit.Assert.assertThat; @@ -25,7 +25,7 @@ import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderRestartTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderRestartTest.java new file mode 100644 index 00000000000..2ff82d1a549 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderRestartTest.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.commons.io.file.PathUtils; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.hamcrest.Matcher; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.jupiter.api.Tag; +import org.junit.rules.RuleChain; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileTime; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@Tag("sleepy") +public class RollingAppenderRestartTest implements RolloverListener { + + private static final String CONFIG = "log4j-rolling-restart.xml"; + + // Note that both paths are hardcoded in the configuration! + private static final Path DIR = Paths.get("target/rolling-restart"); + private static final Path FILE = DIR.resolve("test.log"); + private static final CountDownLatch latch = new CountDownLatch(1); + + private final LoggerContextRule loggerContextRule = + LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); + + @Rule + public RuleChain chain = + loggerContextRule.withCleanFoldersRule( + false, true, 5, DIR.toAbsolutePath().toString()); + + @BeforeClass + public static void setup() throws Exception { + tearDown(); + Files.createDirectories(DIR); + Files.write(FILE, "Hello, world".getBytes(), StandardOpenOption.CREATE); + FileTime newTime = FileTime.from(Instant.now().minus(2, ChronoUnit.DAYS)); + Files + .getFileAttributeView(FILE, BasicFileAttributeView.class) + .setTimes(newTime, newTime, newTime); + } + + @AfterClass + public static void tearDown() throws IOException { + if (DIR.toFile().exists()) { + PathUtils.deleteDirectory(DIR); + } + } + + @Test + public void testAppender() throws Exception { + final Logger logger = loggerContextRule.getLogger(); + final RollingFileAppender appender = loggerContextRule.getAppender("RollingFile"); + assertNotNull("No RollingFile Appender", appender); + appender.getManager().addRolloverListener(this); + logger.info("This is test message number 1"); + latch.await(100, TimeUnit.MILLISECONDS); + // Delay to allow asynchronous gzip to complete. + Thread.sleep(200); + final Matcher hasGzippedFile = hasItemInArray(that(hasName(that(endsWith(".gz"))))); + final File[] files = DIR.toFile().listFiles(); + assertTrue( + "was expecting files with '.gz' suffix, found: " + Arrays.toString(files), + hasGzippedFile.matches(files)); + } + + @Override + public void rolloverTriggered(String fileName) { + + } + + @Override + public void rolloverComplete(String fileName) { + latch.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeCompressPermissionsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeCompressPermissionsTest.java new file mode 100644 index 00000000000..01f888ccc91 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeCompressPermissionsTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.FileUtils; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * LOG4J2-1699. + */ +@Tag("sleepy") +public class RollingAppenderSizeCompressPermissionsTest { + + private static final String CONFIG = "log4j-rolling-gz-posix.xml"; + + private static final String DIR = "target/rollingpermissions1"; + + @BeforeAll + public static void beforeClass() { + Assumptions.assumeTrue(FileUtils.isFilePosixAttributeViewSupported()); + } + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(CONFIG) + public void testAppenderCompressPermissions(final Logger logger, final LoggerContext context) throws Exception { + for (int i = 0; i < 500; ++i) { + final String message = "This is test message number " + i; + logger.debug(message); + if (i % 100 == 0) { + Thread.sleep(500); + } + } + assertTrue(context.stop(30, TimeUnit.SECONDS), () -> "Could not stop cleanly " + context + " for " + this); + final File dir = new File(DIR); + assertTrue(dir.exists(), "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + int gzippedFiles1 = 0; + int gzippedFiles2 = 0; + for (final File file : files) { + final FileExtension ext = FileExtension.lookupForFile(file.getName()); + if (ext != null) { + if (file.getName().startsWith("test1")) { + gzippedFiles1++; + assertEquals("rw-------", + PosixFilePermissions.toString(Files.getPosixFilePermissions(file.toPath()))); + } else { + gzippedFiles2++; + assertEquals("r--r--r--", + PosixFilePermissions.toString(Files.getPosixFilePermissions(file.toPath()))); + } + } else { + if (file.getName().startsWith("test1")) { + assertEquals("rw-------", + PosixFilePermissions.toString(Files.getPosixFilePermissions(file.toPath()))); + } else { + assertEquals("rwx------", + PosixFilePermissions.toString(Files.getPosixFilePermissions(file.toPath()))); + } + } + } + assertTrue(files.length > 2, "Files not rolled : " + files.length); + assertTrue(gzippedFiles1 > 0, "Files 1 gzipped not rolled : " + gzippedFiles1); + assertTrue(gzippedFiles2 > 0, "Files 2 gzipped not rolled : " + gzippedFiles2); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeMaxWidthTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeMaxWidthTest.java new file mode 100644 index 00000000000..5d1843b241b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeMaxWidthTest.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.IntStream; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.pattern.ArrayPatternConverter; +import org.apache.logging.log4j.core.pattern.FormattingInfo; +import org.apache.logging.log4j.core.pattern.IntegerPatternConverter; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * + */ +@RunWith(Parameterized.class) +public class RollingAppenderSizeMaxWidthTest implements RolloverListener { + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + return Arrays.asList(new Object[][] { + { new LoggerContextRule("log4j-rolling-size-max-width-1.xml"), }, + { new LoggerContextRule("log4j-rolling-size-max-width-2.xml"), }, + { new LoggerContextRule("log4j-rolling-size-max-width-3.xml"), }, + { new LoggerContextRule("log4j-rolling-size-max-width-4.xml"), }, + }); + } + private static final String DIR = "target/rolling-max-width/archive"; + private static final String MESSAGE = "This is test message number "; + private static final int COUNT = 10000; + private static final int[] POWERS_OF_10 = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, + 1000000000}; + public LoggerContextRule loggerContextRule; + @Rule + public RuleChain chain; + List rolledFileNames = new ArrayList<>(); + int min; + int max; + boolean isZeroPad; + int minWidth; + int maxWidth; + long rolloverSize; + private Logger logger; + private int rolloverCount = 0; + + private static int powerOfTen(int pow) { + if (pow > POWERS_OF_10.length) { + throw new IllegalArgumentException("Max width is too large"); + } + return POWERS_OF_10[pow]; + } + + public RollingAppenderSizeMaxWidthTest(final LoggerContextRule loggerContextRule) { + this.loggerContextRule = loggerContextRule; + this.chain = loggerContextRule.withCleanFoldersRule(DIR); + } + + @Before + public void setUp() throws Exception { + this.logger = loggerContextRule.getLogger(RollingAppenderSizeMaxWidthTest.class.getName()); + RollingFileAppender app = (RollingFileAppender) loggerContextRule.getRequiredAppender("RollingFile"); + app.getManager().addRolloverListener(this); + ArrayPatternConverter[] patternConverters = app.getManager().getPatternProcessor().getPatternConverters(); + int index = IntStream.range(0, patternConverters.length) + .filter(i -> patternConverters[i] instanceof IntegerPatternConverter).findFirst().orElse(-1); + if (index < 0) { + fail("Could not find integer pattern converter in " + app.getFilePattern()); + } + FormattingInfo formattingInfo = app.getManager().getPatternProcessor().getPatternFields()[index]; + minWidth = formattingInfo.getMinLength(); + maxWidth = formattingInfo.getMaxLength(); + isZeroPad = formattingInfo.isZeroPad(); + DefaultRolloverStrategy strategy = (DefaultRolloverStrategy) app.getManager().getRolloverStrategy(); + min = strategy.getMinIndex(); + max = strategy.getMaxIndex(); + SizeBasedTriggeringPolicy policy; + if (app.getTriggeringPolicy() instanceof CompositeTriggeringPolicy) { + policy = (SizeBasedTriggeringPolicy) Arrays.stream(((CompositeTriggeringPolicy) app.getTriggeringPolicy()) + .getTriggeringPolicies()).filter((p) -> p instanceof SizeBasedTriggeringPolicy).findFirst() + .orElse(null); + } else { + policy = app.getTriggeringPolicy(); + } + assertNotNull("No SizeBasedTriggeringPolicy", policy); + rolloverSize = policy.getMaxFileSize(); + } + + @Test + public void testAppender() throws Exception { + if (minWidth > 0) { + assertTrue("min must be greater than or equal to the minimum width", + min > -powerOfTen(minWidth)); + } + if (maxWidth < Integer.MAX_VALUE) { + assertTrue("max must be less than or equal to the maximum width", + max <= powerOfTen(maxWidth)); + } + long bytes = 0; + for (int i = 0; i < 10000; ++i) { + String message = MESSAGE + i; + logger.debug(message); + bytes += message.length() + 1; + } + final long minExpected = ((bytes / rolloverSize) * 95) / 100; + final long maxExpected = ((bytes / rolloverSize) * 105) / 100; + final File dir = new File(DIR); + assertTrue("Directory not created", dir.exists()); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertTrue("Not enough rollovers: expected: " + minExpected + ", actual: " + rolloverCount, + rolloverCount + 1 >= minExpected); + assertTrue("Too many rollovers: expected: " + maxExpected + ", actual: " + rolloverCount, + rolloverCount <= maxExpected); + int maxFiles = max - min + 1; + int maxExpectedFiles = Math.min(maxFiles, rolloverCount); + assertEquals("More files than expected. expected: " + maxExpectedFiles + ", actual: " + files.length, + maxExpectedFiles, files.length); + } + + @Override + public void rolloverTriggered(String fileName) { + ++rolloverCount; + } + + @Override + public void rolloverComplete(String fileName) { + rolledFileNames.add(fileName); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeNoCompressTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeNoCompressTest.java new file mode 100644 index 00000000000..8a4666e94a9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeNoCompressTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.commons.compress.utils.IOUtils; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * LOG4J2-1804. + */ +@Tag("sleepy") +public class RollingAppenderSizeNoCompressTest { + + private static final String CONFIG = "log4j-rolling-size.xml"; + + private static final String DIR = "target/rolling1"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(CONFIG) + public void testAppender(final Logger logger, final LoggerContext context) throws Exception { + final List messages = new ArrayList<>(); + for (int i=0; i < 1000; ++i) { + final String message = "This is test message number " + i; + messages.add(message); + logger.debug(message); + if (i % 100 == 0) { + Thread.sleep(500); + } + } + assertTrue(context.stop(30, TimeUnit.SECONDS), () -> "Could not stop cleanly " + context + " for " + this); + final File dir = new File(DIR); + assertTrue(dir.exists(), "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + for (final File file : files) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (FileInputStream fis = new FileInputStream(file)) { + try { + IOUtils.copy(fis, baos); + } catch (final Exception ex) { + ex.printStackTrace(); + Assertions.fail("Unable to read " + file.getAbsolutePath()); + } + } + final String text = baos.toString(Charset.defaultCharset()); + final String[] lines = text.split("[\\r\\n]+"); + for (final String line : lines) { + messages.remove(line); + } + } + assertTrue(messages.isEmpty(), "Log messages lost : " + messages.size()); + assertTrue(files.length > 2, "Files not rolled : " + files.length); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java index 103a001c81c..f07e2bc23d7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java @@ -16,46 +16,45 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import static org.apache.logging.log4j.hamcrest.Descriptors.that; -import static org.apache.logging.log4j.hamcrest.FileMatchers.hasName; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.hasItemInArray; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.TimeUnit; - import org.apache.commons.compress.compressors.CompressorException; import org.apache.commons.compress.compressors.CompressorInputStream; import org.apache.commons.compress.compressors.CompressorStreamFactory; import org.apache.commons.compress.utils.IOUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.junit.LoggerContextRule; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.jupiter.api.Tag; import org.junit.rules.RuleChain; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.Assert.*; + /** * */ @RunWith(Parameterized.class) +@Tag("sleepy") public class RollingAppenderSizeTest { @Rule @@ -75,6 +74,7 @@ public static Collection data() { // @formatter:off {"log4j-rolling-gz-lazy.xml", ".gz", true}, {"log4j-rolling-gz.xml", ".gz", false}, + {"log4j-rolling-numbered-gz.xml", ".gz", false}, {"log4j-rolling-zip-lazy.xml", ".zip", true}, {"log4j-rolling-zip.xml", ".zip", false}, // Apache Commons Compress @@ -150,7 +150,7 @@ public void testAppender() throws Exception { in = new CompressorStreamFactory().createCompressorInputStream(ext.name().toLowerCase(), fis); } catch (final CompressorException ce) { ce.printStackTrace(); - fail("Error creating intput stream from " + file.toString() + ": " + ce.getMessage()); + fail("Error creating input stream from " + file.toString() + ": " + ce.getMessage()); } final ByteArrayOutputStream baos = new ByteArrayOutputStream(); assertNotNull("No input stream for " + file.getName(), in); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeWithTimeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeWithTimeTest.java new file mode 100644 index 00000000000..b332127cd4f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeWithTimeTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.commons.compress.utils.IOUtils; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * LOG4J2-2602. + */ +@Tag("sleepy") +public class RollingAppenderSizeWithTimeTest { + + private static final String CONFIG = "log4j-rolling-size-with-time.xml"; + + private static final String DIR = "target/rolling-size-test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(CONFIG) + public void testAppender(final Logger logger, final LoggerContext context) throws Exception { + final List messages = new ArrayList<>(); + for (int i = 0; i < 5000; ++i) { + final String message = "This is test message number " + i; + messages.add(message); + logger.debug(message); + if (i % 100 == 0) { + Thread.sleep(10); + } + } + assertTrue(context.stop(30, TimeUnit.SECONDS), () -> "Could not stop cleanly " + context + " for " + this); + final File dir = new File(DIR); + assertTrue(dir.exists(), "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + for (final File file : files) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (FileInputStream fis = new FileInputStream(file)) { + try { + IOUtils.copy(fis, baos); + } catch (final Exception ex) { + ex.printStackTrace(); + fail("Unable to read " + file.getAbsolutePath()); + } + } + final String text = baos.toString(Charset.defaultCharset()); + final String[] lines = text.split("[\\r\\n]+"); + for (final String line : lines) { + messages.remove(line); + } + } + assertTrue(messages.isEmpty(), "Log messages lost : " + messages.size()); + assertTrue(files.length > 2, "Files not rolled : " + files.length); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTempCompressedFilePatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTempCompressedFilePatternTest.java new file mode 100644 index 00000000000..c5da2aa5ae8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTempCompressedFilePatternTest.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.commons.compress.compressors.CompressorException; +import org.apache.commons.compress.compressors.CompressorStreamFactory; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * LOG4J2-1766. + */ +@DisabledOnOs(value = OS.MAC, disabledReason = "FileWatcher is not fast enough on macOS for this test") +@Tag("sleepy") +public class RollingAppenderTempCompressedFilePatternTest { + + private static final String CONFIG = "log4j-rolling-gz-tmp-compress.xml"; + + private static final String DIR = "target/rolling2"; + private static final String DIR_TMP = "target/rolling-tmp"; + + @Test + @CleanUpDirectories({ DIR, DIR_TMP }) + @LoggerContextSource(CONFIG) + public void testAppender(final Logger logger, final LoggerContext context) throws Exception { + final File dirTmp = new File(DIR_TMP); + dirTmp.mkdirs(); + try (final WatchService watcher = FileSystems.getDefault().newWatchService()) { + WatchKey key = dirTmp.toPath().register(watcher, StandardWatchEventKinds.ENTRY_CREATE); + + final List messages = new ArrayList<>(); + for (int i = 0; i < 500; ++i) { + final String message = "This is test message number " + i; + messages.add(message); + logger.debug(message); + if (i % 100 == 0) { + Thread.sleep(500); + } + } + assertTrue(context.stop(30, TimeUnit.SECONDS), () -> "Could not stop cleanly " + context + " for " + this); + final File dir = new File(DIR); + assertTrue(dir.exists(), "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + int gzippedFiles = 0; + for (final File file : files) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream in = null; + final FileExtension ext = FileExtension.lookupForFile(file.getName()); + try { + try (FileInputStream fis = new FileInputStream(file)) { + if (ext != null) { + gzippedFiles++; + try { + in = new CompressorStreamFactory().createCompressorInputStream(ext.name().toLowerCase(), + fis); + } catch (final CompressorException ce) { + ce.printStackTrace(); + fail( + "Error creating intput stream from " + file.toString() + ": " + ce.getMessage()); + } + } else { + in = new FileInputStream(file); + } + assertNotNull(in, "No input stream for " + file.getName()); + try { + IOUtils.copy(in, baos); + } catch (final Exception ex) { + ex.printStackTrace(); + fail("Unable to decompress " + file.getAbsolutePath()); + } + } + } finally { + Closer.close(in); + } + final String text = baos.toString(Charset.defaultCharset()); + final String[] lines = text.split("[\\r\\n]+"); + for (final String line : lines) { + messages.remove(line); + } + } + assertTrue(messages.isEmpty(), "Log messages lost : " + messages.size()); + assertTrue(files.length > 2, "Files not rolled : " + files.length); + assertTrue(gzippedFiles > 0, "Files gzipped not rolled : " + gzippedFiles); + + int temporaryFilesCreated = 0; + key = watcher.take(); + + for (final WatchEvent event : key.pollEvents()) { + final WatchEvent ev = (WatchEvent) event; + final Path filename = ev.context(); + if (filename.toString().endsWith(".tmp")) { + temporaryFilesCreated++; + } + } + assertTrue(temporaryFilesCreated > 0, "No temporary file created during compression"); + assertEquals(gzippedFiles, temporaryFilesCreated, + "Temporarys file created not equals to compressed files " + temporaryFilesCreated + "/" + + gzippedFiles); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeDirectTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeDirectTest.java new file mode 100644 index 00000000000..c22de50e5dd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeDirectTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@Tag("sleepy") +public class RollingAppenderTimeAndSizeDirectTest { + + private static final String CONFIG = "log4j-rolling3-direct.xml"; + + private static final String DIR = "target/rolling3Direct"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger) throws Exception { + for (int i=0; i < 100; ++i) { + logger.debug("This is test message number " + i); + Thread.sleep(10); + } + Thread.sleep(50); + final File dir = new File(DIR); + assertTrue(dir.exists() && dir.listFiles().length > 0, "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".gz")))))); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.java new file mode 100644 index 00000000000..16c3c653a82 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.attribute.FileTime; +import java.util.Arrays; +import java.util.Random; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +@Tag("sleepy") +public class RollingAppenderTimeAndSizeTest { + + private static final String CONFIG = "log4j-rolling3.xml"; + + private static final String DIR = "target/rolling3/test"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger) throws Exception { + Random rand = new Random(); + final File logFile = new File("target/rolling3/rollingtest.log"); + assertTrue(logFile.exists(), "target/rolling3/rollingtest.log does not exist"); + FileTime time = (FileTime) Files.getAttribute(logFile.toPath(), "creationTime"); + for (int j = 0; j < 100; ++j) { + int count = rand.nextInt(50); + for (int i = 0; i < count; ++i) { + logger.debug("This is test message number " + i); + } + Thread.sleep(rand.nextInt(50)); + } + Thread.sleep(50); + final File dir = new File(DIR); + assertTrue(dir.exists() && dir.listFiles().length > 0, "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + Arrays.sort(files); + assertNotNull(files); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".log")))))); + int fileCounter = 0; + String previous = ""; + for (final File file : files) { + final String actual = file.getName(); + final String[] fileParts = actual.split("[_.]"); + fileCounter = previous.equals(fileParts[1]) ? ++fileCounter : 1; + previous = fileParts[1]; + assertEquals(Integer.toString(fileCounter), fileParts[2], + "Incorrect file name. Expected counter value of " + fileCounter + " in " + actual); + } + FileTime endTime = (FileTime) Files.getAttribute(logFile.toPath(), "creationTime"); + assertNotEquals(time, endTime, "Creation times are equal"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeTest.java index 089908da46c..3233341d435 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeTest.java @@ -16,22 +16,22 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import static org.apache.logging.log4j.hamcrest.Descriptors.that; -import static org.apache.logging.log4j.hamcrest.FileMatchers.hasName; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.hasItemInArray; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.io.File; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.hamcrest.Matcher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * */ diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderUncompressedTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderUncompressedTest.java new file mode 100644 index 00000000000..389a80a822a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderUncompressedTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class RollingAppenderUncompressedTest { + + private static final String CONFIG = "log4j-rolling4.xml"; + private static final String DIR = "target/rolling4"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(CONFIG) + public void testAppender(final Logger logger) throws Exception { + for (int i=0; i < 100; ++i) { + logger.debug("This is test message number " + i); + } + final File dir = new File(DIR); + assertTrue(dir.exists() && dir.listFiles().length > 0, "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + boolean found = false; + for (final File file : files) { + final String name = file.getName(); + if (name.startsWith("test1") && name.endsWith(".log")) { + found = true; + break; + } + } + assertTrue(found, "No archived files found"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSize3490Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSize3490Test.java new file mode 100644 index 00000000000..1d9ab8b1c8f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSize3490Test.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * This test attempts to validate that logging rolls after the max files are already written + * will succeed on a restart. + */ +public class RollingDirectSize3490Test implements RolloverListener { + + private static final String CONFIG = "log4j-rolling-3490.xml"; + private static final String[] set1 = {"This is file 1"}; + private static final String LINE_2 = "This is file 2\n"; + + // Note that the path is hardcoded in the configuration! + private static final String DIR = "target/rolling-3490"; + + private boolean rolloverTriggered = false; + + @BeforeAll + public static void clean() throws Exception { + File dir = new File(DIR); + if (dir.exists()) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + file.delete(); + } + } + dir.delete(); + } + } + + @Test + public void rolloverTest() throws Exception { + File parent = new File(DIR); + parent.mkdirs(); + Path app1 = new File(parent, "app-21.log").toPath(); + Files.write(app1, Arrays.asList(set1), StandardOpenOption.CREATE_NEW); + List lines = new ArrayList<>(); + for (int count = 0; count < 1024; count += LINE_2.length()) { + lines.add(LINE_2); + } + File file2 = new File(parent, "app-22.log"); + Path app2 = file2.toPath(); + Files.write(app2, lines, StandardOpenOption.CREATE_NEW); + LoggerContext context = Configurator.initialize("TestConfig", this.getClass().getClassLoader(), + CONFIG); + RollingFileAppender app = context.getConfiguration().getAppender("RollingFile"); + app.getManager().addRolloverListener(this); + Logger logger = context.getLogger("Test"); + logger.info("Trigger rollover"); + assertTrue(rolloverTriggered, "Rollover was not triggered"); + } + + @Override + public void rolloverTriggered(String fileName) { + + } + + @Override + public void rolloverComplete(String fileName) { + assertTrue(fileName.endsWith("app-22.log"), "File does not end with correct suffix"); + rolloverTriggered = true; + } +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.java new file mode 100644 index 00000000000..eb3ed629b88 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Phaser; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * This test attempts to validate that logging rolls when the file size exceeds 5KB or every second. + * When the file rolls by time it should create a new directory. When rolling by size it should + * create multiple files per directory. + */ +@Disabled("https://issues.apache.org/jira/browse/LOG4J2-3449") +public class RollingDirectSizeTimeNewDirectoryTest implements RolloverListener { + + private static final String CONFIG = "log4j-rolling-size-time-new-directory.xml"; + + // Note that the path is hardcoded in the configuration! + private static final String DIR = "target/rolling-size-time-new-directory"; + + private final Map rolloverFiles = new HashMap<>(); + private final AtomicLong currentTimeMillis = new AtomicLong(System.currentTimeMillis()); + private final Phaser phaser = new Phaser(1); + + @Factory + Clock clock() { + return currentTimeMillis::get; + } + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 15) + public void streamClosedError(final LoggerContext context, @Named("RollingFile") final RollingFileAppender appender) throws Exception { + appender.getManager().addRolloverListener(this); + final Logger logger = context.getLogger(RollingDirectSizeTimeNewDirectoryTest.class); + + for (int i = 0; i < 1000; i++) { + currentTimeMillis.incrementAndGet(); + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + currentTimeMillis.addAndGet(500); + for (int i = 0; i < 1000; i++) { + currentTimeMillis.incrementAndGet(); + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + + phaser.arriveAndAwaitAdvance(); + + assertTrue(rolloverFiles.size() > 1, "A time based rollover did not occur"); + int maxFiles = Collections.max(rolloverFiles.values(), Comparator.comparing(AtomicInteger::get)).get(); + assertTrue(maxFiles > 1, "No size based rollovers occurred"); + } + + @Override + public void rolloverTriggered(String fileName) { + phaser.register(); + } + + @Override + public void rolloverComplete(String fileName) { + File file = new File(fileName); + String logDir = file.getParentFile().getName(); + AtomicInteger fileCount = rolloverFiles.computeIfAbsent(logDir, k -> new AtomicInteger(0)); + fileCount.incrementAndGet(); + phaser.arriveAndDeregister(); + } +} + + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.java new file mode 100644 index 00000000000..f4416fe0132 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectTimeNewDirectoryTest.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.TrueFileFilter; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class RollingDirectTimeNewDirectoryTest implements RolloverListener { + + private static final String CONFIG = "log4j-rolling-folder-direct.xml"; + + // Note that the path is hardcoded in the configuration! + private static final String DIR = "target/rolling-folder-direct"; + private final CountDownLatch rollover = new CountDownLatch(2); + private boolean isFirst = true; + private String ignored = null; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 15) + public void streamClosedError(final LoggerContext context, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + + final Logger logger = context.getLogger(RollingDirectTimeNewDirectoryTest.class.getName()); + + for (int count = 0; count < 2; ++count) { + long start = System.currentTimeMillis(); + for (int i = 0; i < 50; i++) { + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + long end = System.currentTimeMillis(); + if (end < start + 1000) { + Thread.sleep(start + 1000 - end); + } + for (int i = 0; i < 50; i++) { + logger.info("nHq6p9kgfvWfjzDRYbZp"); + } + } + + rollover.await(); + + File logDir = new File(DIR); + assertThat(logDir).isNotEmptyDirectory(); + File[] logFolders = logDir.listFiles(); + assertNotNull(logFolders); + Arrays.sort(logFolders); + + try { + + final int minExpectedLogFolderCount = 2; + assertThat(logFolders).hasSizeGreaterThanOrEqualTo(minExpectedLogFolderCount); + + for (File logFolder : logFolders) { + // It is possible a log file is created at startup and by he time we get here it + // has rolled but has no data and so was deleted. + if (ignored != null && logFolder.getAbsolutePath().equals(ignored)) { + continue; + } + File[] logFiles = logFolder.listFiles(); + if (logFiles != null) { + Arrays.sort(logFiles); + } + assertThat(logFiles).isNotEmpty(); + } + + } catch (AssertionError error) { + StringBuilder sb = new StringBuilder(error.getMessage()).append(" log directory (").append(DIR).append(") contents: ["); + final Iterator fileIterator = + FileUtils.iterateFilesAndDirs( + logDir, TrueFileFilter.TRUE, TrueFileFilter.TRUE); + int totalFileCount = 0; + while (fileIterator.hasNext()) { + totalFileCount++; + final File file = fileIterator.next(); + sb.append("-> ").append(file).append(" (").append(file.length()).append(')'); + if (fileIterator.hasNext()) { + sb.append(", "); + } + } + sb.append("] total file count: ").append(totalFileCount); + throw new AssertionFailedError(sb.toString(), error); + } + + } + + @Override + public void rolloverTriggered(String fileName) { + } + + @Override + public void rolloverComplete(final String fileName) { + File file = new File(fileName); + if (isFirst && file.length() == 0) { + isFirst = false; + ignored = file.getParentFile().getAbsolutePath(); + return; + } + isFirst = false; + rollover.countDown(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java index 8af78b248a0..4a1e9dc42b7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java @@ -40,10 +40,10 @@ public void testAccessManagerWithBuilder() throws IOException { file.deleteOnExit(); // @formatter:off final RollingFileAppender appender = RollingFileAppender.newBuilder() - .withFileName(file.getCanonicalPath()) - .withFilePattern("FilePattern") - .withName("Name") - .withPolicy(OnStartupTriggeringPolicy.createPolicy(1)) + .setFileName(file.getCanonicalPath()) + .setFilePattern("FilePattern") + .setName("Name") + .setPolicy(OnStartupTriggeringPolicy.createPolicy(1)) .setConfiguration(config) .build(); // @formatter:on @@ -66,9 +66,15 @@ public void testAccessManagerWithStrings() throws IOException { final Configuration config = ctx.getConfiguration(); final File file = File.createTempFile("RollingFileAppenderAccessTest", ".tmp"); file.deleteOnExit(); - final RollingFileAppender appender = RollingFileAppender.createAppender(file.getCanonicalPath(), - "FilePattern", null, "Name", null, null, null, OnStartupTriggeringPolicy.createPolicy(1), null, - null, null, null, null, null, config); + // @formatter:off + final RollingFileAppender appender = RollingFileAppender.newBuilder() + .setFileName(file.getCanonicalPath()) + .setFilePattern("FilePattern") + .setName("Name") + .setPolicy(OnStartupTriggeringPolicy.createPolicy(1)) + .setConfiguration(config) + .build(); + // @formatter:on final RollingFileManager manager = appender.getManager(); // Since the RolloverStrategy and TriggeringPolicy are immutable, we could also use generics to type their // access. diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderBuilderTest.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderBuilderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderBuilderTest.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderInterruptedThreadTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderInterruptedThreadTest.java new file mode 100644 index 00000000000..7a7afded499 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderInterruptedThreadTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.test.junit.CleanFolders; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; + +/** + * Tests https://issues.apache.org/jira/browse/LOG4J2-1798 + */ +public class RollingFileAppenderInterruptedThreadTest { + + private static final String ROLLING_APPENDER_FILES_DIR = + "target/" + RollingFileAppenderInterruptedThreadTest.class.getSimpleName(); + + @Rule + public CleanFolders cleanFolders = new CleanFolders(true, false, 3, ROLLING_APPENDER_FILES_DIR); + + LoggerContext loggerContext; + + @Before + public void setUp() { + ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setConfigurationName("LOG4J2-1798 test"); + + builder.add(builder.newAppender("consoleLog", "Console") + .addAttribute("target", ConsoleAppender.Target.SYSTEM_ERR)); + + builder.add(builder.newAppender("fileAppender", "RollingFile") + .addAttribute("filePattern", ROLLING_APPENDER_FILES_DIR + "/file-%i.log") + .add(builder.newLayout("PatternLayout").addAttribute("pattern", "%msg%n")) + .addComponent(builder.newComponent("SizeBasedTriggeringPolicy") + .addAttribute("size", "20B"))); // relatively small amount to trigger rotation quickly + + builder.add(builder.newRootLogger(Level.INFO) + .add(builder.newAppenderRef("consoleLog")) + .add(builder.newAppenderRef("fileAppender"))); + + loggerContext = Configurator.initialize(builder.build()); + } + + @After + public void tearDown() { + Configurator.shutdown(loggerContext); + loggerContext = null; + } + + @Test + public void testRolloverInInterruptedThread() { + Logger logger = loggerContext.getLogger(getClass().getName()); + + Assert.assertThat(logger.getAppenders().values(), hasItem(instanceOf(RollingFileAppender.class))); + + logger.info("Sending logging event 1"); // send first event to initialize rollover system + + Thread.currentThread().interrupt(); // mark thread as interrupted + logger.info("Sending logging event 2"); // send second event to trigger rotation, expecting 2 files in result + + Assert.assertTrue(new File(ROLLING_APPENDER_FILES_DIR, "file-1.log").exists()); + Assert.assertTrue(new File(ROLLING_APPENDER_FILES_DIR, "file-2.log").exists()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java index 5fe9b68f41a..98c64f3fa69 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java @@ -27,12 +27,12 @@ public class RollingFileAppenderLayoutTest { public void testDefaultLayout() throws Exception { // @formatter:off Assert.assertNotNull(RollingFileAppender.newBuilder() - .withName(RollingFileAppenderLayoutTest.class.getName()) + .setName(RollingFileAppenderLayoutTest.class.getName()) .setConfiguration(new DefaultConfiguration()) - .withFileName("log.txt") - .withFilePattern("FilePattern") - .withPolicy(OnStartupTriggeringPolicy.createPolicy(1)) - .withCreateOnDemand(true) // no need to clutter up test folder with another file + .setFileName("log.txt") + .setFilePattern("FilePattern") + .setPolicy(OnStartupTriggeringPolicy.createPolicy(1)) + .setCreateOnDemand(true) // no need to clutter up test folder with another file .build().getLayout()); // @formatter:on } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureTest.java similarity index 90% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureTest.java index 8c4657a1a5f..55190e147a1 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureTest.java @@ -17,7 +17,7 @@ package org.apache.logging.log4j.core.appender.rolling; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; @@ -27,8 +27,8 @@ public class RollingFileAppenderReconfigureTest { @Rule - public final LoggerContextRule loggerContextRule = new LoggerContextRule("src/test/rolling-file-appender-reconfigure.xml"); - + public final LoggerContextRule loggerContextRule = new LoggerContextRule("rolling-file-appender-reconfigure.xml"); + @Test public void testReconfigure() { loggerContextRule.reconfigure(); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureUndefinedSystemPropertyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureUndefinedSystemPropertyTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureUndefinedSystemPropertyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureUndefinedSystemPropertyTest.java index 31a21b17da2..6ede72e0c20 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureUndefinedSystemPropertyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderReconfigureUndefinedSystemPropertyTest.java @@ -17,7 +17,7 @@ package org.apache.logging.log4j.core.appender.rolling; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; @@ -27,8 +27,8 @@ public class RollingFileAppenderReconfigureUndefinedSystemPropertyTest { @Rule - public final LoggerContextRule loggerContextRule = new LoggerContextRule("src/test/rolling-file-appender-reconfigure.original.xml"); - + public final LoggerContextRule loggerContextRule = new LoggerContextRule("src/test/rolling-file-appender-reconfigure.original.xml"); + @Test public void testReconfigure() { loggerContextRule.reconfigure(); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java index b73b6b1bf8b..ac1f552bda5 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java @@ -26,8 +26,8 @@ import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.junit.After; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; /** @@ -63,6 +63,21 @@ private ConfigurationBuilder buildConfigurationBuilder(final return builder; } + private LoggerContext loggerContext1 = null; + private LoggerContext loggerContext2 = null; + + @After + public void after() { + if (loggerContext1 != null) { + loggerContext1.close(); + loggerContext1 = null; + } + if (loggerContext2 != null) { + loggerContext2.close(); + loggerContext2 = null; + } + } + @Test public void testClosingLoggerContext() { // initial config with indexed rollover @@ -77,17 +92,29 @@ public void testClosingLoggerContext() { } @Test - @Ignore public void testNotClosingLoggerContext() { // initial config with indexed rollover - final LoggerContext loggerContext1 = Configurator.initialize(buildConfigA().build()); - validateAppender(loggerContext1, "target-rolling-update-date/foo.log.%i"); + loggerContext1 = Configurator.initialize(buildConfigA().build()); + validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i"); // rebuild config with date based rollover - final LoggerContext loggerContext2 = Configurator.initialize(buildConfigB().build()); - validateAppender(loggerContext2, "target/rolling-update-date/foo.log.%d{yyyy-MM-dd-HH:mm:ss}.%i"); + loggerContext2 = Configurator.initialize(buildConfigB().build()); + Assert.assertNotNull("No LoggerContext", loggerContext2); + Assert.assertSame("Expected same logger context to be returned", loggerContext1, loggerContext2); + validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i"); } + @Test + public void testReconfigure() { + // initial config with indexed rollover + loggerContext1 = Configurator.initialize(buildConfigA().build()); + validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i"); + + // rebuild config with date based rollover + loggerContext1.setConfiguration(buildConfigB().build()); + validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%d{yyyy-MM-dd-HH:mm:ss}.%i"); + } + private void validateAppender(final LoggerContext loggerContext, final String expectedFilePattern) { final RollingFileAppender appender = loggerContext.getConfiguration().getAppender("fooAppender"); Assert.assertNotNull(appender); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java new file mode 100644 index 00000000000..59206538a03 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.util.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class RollingFileManagerTest { + + /** + * Test the RollingFileManager with a custom DirectFileRolloverStrategy + * + * @throws IOException + */ + @Test + public void testCustomDirectFileRolloverStrategy() throws IOException { + class CustomDirectFileRolloverStrategy extends AbstractRolloverStrategy implements DirectFileRolloverStrategy { + final File file; + + CustomDirectFileRolloverStrategy(File file, StrSubstitutor strSubstitutor) { + super(strSubstitutor); + this.file = file; + } + + @Override + public String getCurrentFileName(RollingFileManager manager) { + return file.getAbsolutePath(); + } + + @Override + public void clearCurrentFileName() { + // do nothing + } + + @Override + public RolloverDescription rollover(RollingFileManager manager) throws SecurityException { + return null; // do nothing + } + } + + try (final LoggerContext ctx = LoggerContext.getContext(false)) { + final Configuration config = ctx.getConfiguration(); + final File file = File.createTempFile("RollingFileAppenderAccessTest", ".tmp"); + file.deleteOnExit(); + + final RollingFileAppender appender = RollingFileAppender.newBuilder() + .setFilePattern("FilePattern") + .setName("RollingFileAppender") + .setConfiguration(config) + .setStrategy(new CustomDirectFileRolloverStrategy(file, config.getConfigurationStrSubstitutor())) + .setPolicy(new SizeBasedTriggeringPolicy(100)) + .build(); + + Assert.assertNotNull(appender); + final String testContent = "Test"; + try(final RollingFileManager manager = appender.getManager()) { + Assert.assertEquals(file.getAbsolutePath(), manager.getFileName()); + manager.writeToDestination(testContent.getBytes(StandardCharsets.US_ASCII), 0, testContent.length()); + } + try (final Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.US_ASCII)) { + Assert.assertEquals(testContent, IOUtils.toString(reader)); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingNewDirectoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingNewDirectoryTest.java new file mode 100644 index 00000000000..d1317e7e12b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingNewDirectoryTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.io.File; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Tests + */ +public class RollingNewDirectoryTest implements RolloverListener { + private static final String CONFIG = "log4j-rolling-new-directory.xml"; + + private static final String DIR = "target/rolling-new-directory"; + private final CountDownLatch rollover = new CountDownLatch(3); + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void streamClosedError(final Logger logger, @Named("RollingFile") final RollingFileManager manager) throws Exception { + manager.addRolloverListener(this); + for (int i = 0; i <= 10; ++i) { + logger.info("AAA"); + Thread.sleep(300); + } + try { + if (!rollover.await(5, TimeUnit.SECONDS)) { + fail("Timer expired before rollover"); + } + } catch (InterruptedException ie) { + fail("Thread was interrupted"); + } + final File dir = new File(DIR); + assertThat(dir).isNotEmptyDirectory(); + assertThat(dir.listFiles()).hasSizeGreaterThan(2); + } + + @Override + public void rolloverTriggered(final String fileName) { + } + + @Override + public void rolloverComplete(final String fileName) { + rollover.countDown(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerHeaderFooterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerHeaderFooterTest.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerHeaderFooterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerHeaderFooterTest.java index 6734d6be39a..6191606dd73 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerHeaderFooterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerHeaderFooterTest.java @@ -27,7 +27,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.layout.HtmlLayout; import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java new file mode 100644 index 00000000000..c47d2ecb477 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.core.util.FileUtils; +import org.apache.logging.log4j.core.util.NullOutputStream; +import org.apache.logging.log4j.util.Strings; +import org.junit.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.concurrent.locks.LockSupport; + +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.beforeNow; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasLength; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.isEmpty; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.lastModified; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests the RollingRandomAccessFileManager class. + */ +public class RollingRandomAccessFileManagerTest { + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.rolling.RollingRandomAccessFileManager#writeBytes(byte[], int, int)} + */ + @Test + public void testWrite_multiplesOfBufferSize() throws IOException { + final File file = File.createTempFile("log4j2", "test"); + file.deleteOnExit(); + try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + final OutputStream os = NullOutputStream.getInstance(); + final boolean append = false; + final boolean flushNow = false; + final long triggerSize = Long.MAX_VALUE; + final long initialTime = System.currentTimeMillis(); + final TriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(triggerSize); + final RolloverStrategy rolloverStrategy = null; + final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(null, raf, + file.getName(), Strings.EMPTY, os, append, flushNow, + RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, initialTime, triggerPolicy, rolloverStrategy, + null, null, null, null, null, true); + + final int size = RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3; + final byte[] data = new byte[size]; + manager.write(data, 0, data.length, flushNow); // no buffer overflow exception + + // buffer is full but not flushed yet + assertEquals(RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3, raf.length()); + } + } + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.rolling.RollingRandomAccessFileManager#writeBytes(byte[], int, int)} . + */ + @Test + public void testWrite_dataExceedingBufferSize() throws IOException { + final File file = File.createTempFile("log4j2", "test"); + file.deleteOnExit(); + try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + final OutputStream os = NullOutputStream.getInstance(); + final boolean append = false; + final boolean flushNow = false; + final long triggerSize = 0; + final long time = System.currentTimeMillis(); + final TriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(triggerSize); + final RolloverStrategy rolloverStrategy = null; + final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(null, raf, + file.getName(), Strings.EMPTY, os, append, flushNow, + RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, time, triggerPolicy, rolloverStrategy, + null, null, null, null, null, true); + + final int size = RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3 + 1; + final byte[] data = new byte[size]; + manager.write(data, 0, data.length, flushNow); // no exception + assertEquals(RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3 + 1, raf.length()); + + manager.flush(); + assertEquals(size, raf.length()); // all data written to file now + } + } + + @Test + public void testConfigurableBufferSize() throws IOException { + final File file = File.createTempFile("log4j2", "test"); + file.deleteOnExit(); + try (final RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + final OutputStream os = NullOutputStream.getInstance(); + final boolean append = false; + final boolean flushNow = false; + final long triggerSize = 0; + final long time = System.currentTimeMillis(); + final TriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(triggerSize); + final int bufferSize = 4 * 1024; + assertNotEquals(bufferSize, RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE); + final RolloverStrategy rolloverStrategy = null; + final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(null, raf, + file.getName(), Strings.EMPTY, os, append, flushNow, bufferSize, triggerSize, time, triggerPolicy, + rolloverStrategy, null, null, null, null, null, true); + + // check the resulting buffer size is what was requested + assertEquals(bufferSize, manager.getBufferSize()); + } + } + + @Test + public void testAppendDoesNotOverwriteExistingFile() throws IOException { + final boolean isAppend = true; + final File file = File.createTempFile("log4j2", "test"); + file.deleteOnExit(); + assertThat(file, isEmpty()); + + final byte[] bytes = new byte[4 * 1024]; + + // create existing file + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + fos.write(bytes, 0, bytes.length); + fos.flush(); + } finally { + Closer.closeSilently(fos); + } + assertThat("all flushed to disk", file, hasLength(bytes.length)); + + final boolean immediateFlush = true; + final RollingRandomAccessFileManager manager = RollingRandomAccessFileManager.getRollingRandomAccessFileManager( + // + file.getAbsolutePath(), Strings.EMPTY, isAppend, immediateFlush, + RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, new SizeBasedTriggeringPolicy(Long.MAX_VALUE), // + null, null, null, null, null, null, null); + manager.write(bytes, 0, bytes.length, immediateFlush); + final int expected = bytes.length * 2; + assertThat("appended, not overwritten", file, hasLength(expected)); + } + + @Test + public void testFileTimeBasedOnSystemClockWhenAppendIsFalse() throws IOException { + final File file = File.createTempFile("log4j2", "test"); + file.deleteOnExit(); + LockSupport.parkNanos(1000000); // 1 millisec + + // append is false deletes the file if it exists + final boolean isAppend = false; + final long expectedMin = System.currentTimeMillis(); + final long expectedMax = expectedMin + 500; + assertThat(file, lastModified(lessThanOrEqualTo(expectedMin))); + + final RollingRandomAccessFileManager manager = RollingRandomAccessFileManager.getRollingRandomAccessFileManager( + // + file.getAbsolutePath(), Strings.EMPTY, isAppend, true, + RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, new SizeBasedTriggeringPolicy(Long.MAX_VALUE), // + null, null, null, null, null, null, null); + assertTrue(manager.getFileTime() < expectedMax); + assertTrue(manager.getFileTime() >= expectedMin); + } + + @Test + public void testFileTimeBasedOnFileModifiedTimeWhenAppendIsTrue() throws IOException { + final File file = File.createTempFile("log4j2", "test"); + file.deleteOnExit(); + LockSupport.parkNanos(1000000); // 1 millisec + + final boolean isAppend = true; + assertThat(file, lastModified(beforeNow())); + + final RollingRandomAccessFileManager manager = RollingRandomAccessFileManager.getRollingRandomAccessFileManager( + // + file.getAbsolutePath(), Strings.EMPTY, isAppend, true, + RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, new SizeBasedTriggeringPolicy(Long.MAX_VALUE), // + null, null, null, null, null, null, null); + assertThat(file, lastModified(equalTo(manager.getFileTime()))); + } + + @Test + public void testRolloverRetainsFileAttributes() throws Exception { + + // Short-circuit if host doesn't support file attributes. + if (!FileUtils.isFilePosixAttributeViewSupported()) { + return; + } + + // Create the initial file. + final File file = File.createTempFile("log4j2", "test"); + LockSupport.parkNanos(1000000); // 1 millisec + + // Set the initial file attributes. + final String filePermissionsString = "rwxrwxrwx"; + final Set filePermissions = + PosixFilePermissions.fromString(filePermissionsString); + FileUtils.defineFilePosixAttributeView(file.toPath(), filePermissions, null, null); + + // Create the manager. + final RolloverStrategy rolloverStrategy = DefaultRolloverStrategy + .newBuilder() + .setMax("7") + .setMin("1") + .setFileIndex("max") + .setStopCustomActionsOnError(false) + .setConfig(new DefaultConfiguration()) + .build(); + final RollingRandomAccessFileManager manager = + RollingRandomAccessFileManager.getRollingRandomAccessFileManager( + file.getAbsolutePath(), + Strings.EMPTY, + true, + true, + RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, + new SizeBasedTriggeringPolicy(Long.MAX_VALUE), + rolloverStrategy, + null, + null, + filePermissionsString, + null, + null, + null); + assertNotNull(manager); + manager.initialize(); + + // Trigger a rollover. + manager.rollover(); + + // Verify the rolled over file attributes. + final Set actualFilePermissions = Files + .getFileAttributeView( + Paths.get(manager.getFileName()), + PosixFileAttributeView.class) + .readAttributes() + .permissions(); + assertEquals(filePermissions, actualFilePermissions); + + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteTest.java new file mode 100644 index 00000000000..3644a2b3859 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.apache.logging.log4j.core.test.hamcrest.Descriptors.that; +import static org.apache.logging.log4j.core.test.hamcrest.FileMatchers.hasName; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@Tag("sleepy") +public class RollingRandomAppenderDirectWriteTest { + + private static final String CONFIG = "log4j-rolling-random-direct.xml"; + + private static final String DIR = "target/rolling-random-direct"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger) throws Exception { + for (int i=0; i < 100; ++i) { + logger.debug("This is test message number " + i); + } + Thread.sleep(50); + final File dir = new File(DIR); + assertTrue(dir.exists(), "Directory not created"); + final File[] files = dir.listFiles(); + assertNotNull(files); + assertThat(files.length, greaterThan(0)); + assertThat(files, hasItemInArray(that(hasName(that(endsWith(".gz")))))); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.java index b2706714f5b..2e362fe421a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAppenderDirectWriteWithFilenameTest.java @@ -19,7 +19,7 @@ import java.io.File; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java new file mode 100644 index 00000000000..7d7ba91d367 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import java.util.regex.Matcher; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test getEligibleFiles method. + */ +public class RolloverFilePatternTest { + + @Test + public void testFilePatternWithoutPadding() throws Exception { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%i.log.gz"); + assertTrue(matcher.matches()); + assertNull(matcher.group("ZEROPAD")); + assertNull(matcher.group("PADDING")); + } + + @Test + public void testFilePatternWithSpacePadding() throws Exception { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%3i.log.gz"); + assertTrue(matcher.matches()); + assertNull(matcher.group("ZEROPAD")); + assertEquals("3", matcher.group("PADDING")); + } + + @Test + public void testFilePatternWithZeroPadding() throws Exception { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%03i.log.gz"); + assertTrue(matcher.matches()); + assertEquals("0", matcher.group("ZEROPAD")); + assertEquals("3", matcher.group("PADDING")); + } + + @Test + public void testFilePatternUnmatched() throws Exception { + final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%n.log.gz"); + assertFalse(matcher.matches()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithDeletedOldFileTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithDeletedOldFileTest.java new file mode 100644 index 00000000000..90e4b2fc82e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithDeletedOldFileTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests that files were rolled correctly if an old log file was deleted from the directory. + */ +@Tag("sleepy") +public class RolloverWithDeletedOldFileTest { + private static final String CONFIG = "log4j-rolling-with-padding.xml"; + private static final String DIR = "target/rolling-with-padding"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger) throws Exception { + for (int i = 0; i < 10; ++i) { + // 30 chars per message: each message triggers a rollover + logger.fatal("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + + File[] files = dir.listFiles(); + assertNotNull(files); + final List expected = Arrays.asList("rollingtest.log", "test-001.log", "test-002.log", "test-003.log", "test-004.log", "test-005.log"); + assertEquals(expected.size(), files.length, "Unexpected number of files"); + File fileToRemove = null; + for (final File file : files) { + if (!expected.contains(file.getName())) { + fail("unexpected file" + file); + } + if (file.getName().equals("test-001.log")) { + fileToRemove = file; + } + } + fileToRemove.delete(); + for (int i = 0; i < 10; ++i) { + // 30 chars per message: each message triggers a rollover + logger.fatal("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete again + files = dir.listFiles(); + assertEquals(expected.size(), files.length, "Unexpected number of files"); + for (final File file : files) { + if (!expected.contains(file.getName())) { + fail("unexpected file" + file); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java new file mode 100644 index 00000000000..0e9c23cbdb4 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.CleanUpDirectories; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests that zero-padding in rolled files works correctly. + */ +@Tag("sleepy") +public class RolloverWithPaddingTest { + private static final String CONFIG = "log4j-rolling-with-padding.xml"; + private static final String DIR = "target/rolling-with-padding"; + + @Test + @CleanUpDirectories(DIR) + @LoggerContextSource(value = CONFIG, timeout = 10) + public void testAppender(final Logger logger) throws Exception { + for (int i = 0; i < 10; ++i) { + // 30 chars per message: each message triggers a rollover + logger.fatal("This is a test message number " + i); // 30 chars: + } + Thread.sleep(100); // Allow time for rollover to complete + + final File dir = new File(DIR); + assertTrue(dir.exists(), "Dir " + DIR + " should exist"); + assertEquals(6, dir.listFiles().length, "Dir " + DIR + " should contain files"); + + final File[] files = dir.listFiles(); + assertNotNull(files); + final List expected = List.of("rollingtest.log", "test-001.log", "test-002.log", "test-003.log", "test-004.log", "test-005.log"); + assertEquals(expected.size(), files.length, "Unexpected number of files"); + for (final File file : files) { + if (!expected.contains(file.getName())) { + fail("unexpected file" + file); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java new file mode 100644 index 00000000000..18cb2866b3b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling.action; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@StatusLoggerLevel("WARN") +public class AbstractActionTest { + + // Test for LOG4J2-2658 + @Test + public void testExceptionsAreLoggedToStatusLogger() { + StatusLogger statusLogger = StatusLogger.getLogger(); + statusLogger.clear(); + new TestAction().run(); + List statusDataList = statusLogger.getStatusData(); + assertThat(statusDataList, hasSize(1)); + StatusData statusData = statusDataList.get(0); + assertEquals(Level.WARN, statusData.getLevel()); + String formattedMessage = statusData.getFormattedStatus(); + assertThat(formattedMessage, containsString("Exception reported by action 'class org.apache." + + "logging.log4j.core.appender.rolling.action.AbstractActionTest$TestAction' java.io.IOException: " + + "failed" + System.lineSeparator() + + "\tat org.apache.logging.log4j.core.appender.rolling.action.AbstractActionTest" + + "$TestAction.execute(AbstractActionTest.java:")); + } + + @Test + public void testRuntimeExceptionsAreLoggedToStatusLogger() { + StatusLogger statusLogger = StatusLogger.getLogger(); + statusLogger.clear(); + new AbstractAction() { + @Override + public boolean execute() { + throw new IllegalStateException(); + } + }.run(); + List statusDataList = statusLogger.getStatusData(); + assertThat(statusDataList, hasSize(1)); + StatusData statusData = statusDataList.get(0); + assertEquals(Level.WARN, statusData.getLevel()); + String formattedMessage = statusData.getFormattedStatus(); + assertThat(formattedMessage, containsString("Exception reported by action")); + } + + @Test + public void testErrorsAreLoggedToStatusLogger() { + StatusLogger statusLogger = StatusLogger.getLogger(); + statusLogger.clear(); + new AbstractAction() { + @Override + public boolean execute() { + throw new AssertionError(); + } + }.run(); + List statusDataList = statusLogger.getStatusData(); + assertThat(statusDataList, hasSize(1)); + StatusData statusData = statusDataList.get(0); + assertEquals(Level.WARN, statusData.getLevel()); + String formattedMessage = statusData.getFormattedStatus(); + assertThat(formattedMessage, containsString("Exception reported by action")); + } + + private static final class TestAction extends AbstractAction { + @Override + public boolean execute() throws IOException { + throw new IOException("failed"); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/Bzip2CompressActionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/Bzip2CompressActionTest.java similarity index 82% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/Bzip2CompressActionTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/Bzip2CompressActionTest.java index 8178adc7155..bde1b75732f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/Bzip2CompressActionTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/Bzip2CompressActionTest.java @@ -17,30 +17,33 @@ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; -import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; -import org.junit.Test; - -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests Bzip2CompressAction. */ public class Bzip2CompressActionTest { - @Test(expected = NullPointerException.class) + @Test public void testConstructorDisallowsNullSource() { - new CommonsCompressAction("bzip2", null, new File("any"), true); + assertThrows(NullPointerException.class, + () -> new CommonsCompressAction("bzip2", null, new File("any"), true)); } - @Test(expected = NullPointerException.class) + @Test public void testConstructorDisallowsNullDestination() { - new CommonsCompressAction("bzip2", new File("any"), null, true); + assertThrows(NullPointerException.class, + () -> new CommonsCompressAction("bzip2", new File("any"), null, true)); } @Test @@ -50,29 +53,28 @@ public void testExecuteReturnsFalseIfSourceDoesNotExist() throws IOException { source = new File(source.getName() + Math.random()); } final boolean actual = CommonsCompressAction.execute("bzip2", source, new File("any2"), true); - assertEquals("Cannot compress non-existing file", false, actual); + assertFalse(actual, "Cannot compress non-existing file"); } @Test - public void testExecuteCompressesSourceFileToDestinationFile() throws IOException { + public void testExecuteCompressesSourceFileToDestinationFile(@TempDir final File tempDir) throws IOException { final String LINE1 = "Here is line 1. Random text: ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n"; final String LINE2 = "Here is line 2. Random text: ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n"; final String LINE3 = "Here is line 3. Random text: ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n"; - final File source = new File("target/compressme"); + final File source = new File(tempDir, "compressme"); try (FileWriter fw = new FileWriter(source, false)) { fw.write(LINE1); fw.write(LINE2); fw.write(LINE3); fw.flush(); } - final File destination = new File("target/compressme.bz2"); - destination.delete(); // just in case - assertFalse("Destination should not exist yet", destination.exists()); + final File destination = new File(tempDir, "compressme.bz2"); + assertFalse(destination.exists(), "Destination should not exist yet"); final boolean actual = CommonsCompressAction.execute("bzip2", source, destination, true); - assertEquals("Bzip2CompressAction should have succeeded", true, actual); - assertTrue("Destination should exist after Bzip2CompressAction", destination.exists()); - assertFalse("Source should have been deleted", source.exists()); + assertTrue(actual, "Bzip2CompressAction should have succeeded"); + assertTrue(destination.exists(), "Destination should exist after Bzip2CompressAction"); + assertFalse(source.exists(), "Source should have been deleted"); final byte[] bz2 = new byte[] { (byte) 0x42, (byte) 0x5A, (byte) 0x68, (byte) 0x39, (byte) 0x31, (byte) 0x41, (byte) 0x59, (byte) 0x26, (byte) 0x53, (byte) 0x59, (byte) 0x9C, (byte) 0xE1, (byte) 0xE8, (byte) 0x2D, @@ -102,9 +104,8 @@ public void testExecuteCompressesSourceFileToDestinationFile() throws IOExceptio n = fis.read(actualBz2, offset, actualBz2.length - offset); offset += n; } while (offset < actualBz2.length); - assertArrayEquals("Compressed data corrupt", bz2, actualBz2); + assertArrayEquals(bz2, actualBz2, "Compressed data corrupt"); } - destination.delete(); // uncompress try (BZip2CompressorInputStream bzin = new BZip2CompressorInputStream(new ByteArrayInputStream(bz2))) { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/CountingCondition.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/CountingCondition.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/CountingCondition.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/CountingCondition.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java index 8f13ae2aaed..856e835d031 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteActionTest.java @@ -25,14 +25,13 @@ import java.util.Collections; import java.util.EnumSet; -import org.apache.logging.log4j.core.BasicConfigurationFactory; -import org.apache.logging.log4j.core.appender.rolling.action.DeleteAction; -import org.apache.logging.log4j.core.appender.rolling.action.DeletingVisitor; -import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; import org.apache.logging.log4j.core.config.Configuration; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the {@code DeleteAction} class. @@ -46,10 +45,9 @@ private static DeleteAction createAnyFilter(final String path, final boolean fol private static DeleteAction create(final String path, final boolean followLinks, final int maxDepth, final boolean testMode, final PathCondition[] conditions) { - final Configuration config = new BasicConfigurationFactory().new BasicConfiguration(); - final DeleteAction delete = DeleteAction.createDeleteAction(path, followLinks, maxDepth, testMode, null, conditions, + final Configuration config = new BasicConfigurationFactory.BasicConfiguration(); + return DeleteAction.createDeleteAction(path, followLinks, maxDepth, testMode, null, conditions, null, config); - return delete; } @Test @@ -98,7 +96,7 @@ public void testGetFiltersReturnsConstructorValue() { public void testCreateFileVisitorReturnsDeletingVisitor() { final DeleteAction delete = createAnyFilter("any", true, 0, false); final FileVisitor visitor = delete.createFileVisitor(delete.getBasePath(), delete.getPathConditions()); - assertTrue(visitor instanceof DeletingVisitor); + assertThat(visitor, instanceOf(DeletingVisitor.class)); } @Test @@ -106,14 +104,14 @@ public void testCreateFileVisitorTestModeIsActionTestMode() { final DeleteAction delete = createAnyFilter("any", true, 0, false); assertFalse(delete.isTestMode()); final FileVisitor visitor = delete.createFileVisitor(delete.getBasePath(), delete.getPathConditions()); - assertTrue(visitor instanceof DeletingVisitor); + assertThat(visitor, instanceOf(DeletingVisitor.class)); assertFalse(((DeletingVisitor) visitor).isTestMode()); final DeleteAction deleteTestMode = createAnyFilter("any", true, 0, true); assertTrue(deleteTestMode.isTestMode()); final FileVisitor testVisitor = deleteTestMode.createFileVisitor(delete.getBasePath(), delete.getPathConditions()); - assertTrue(testVisitor instanceof DeletingVisitor); + assertThat(testVisitor, instanceOf(DeletingVisitor.class)); assertTrue(((DeletingVisitor) testVisitor).isTestMode()); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java index 7258c236202..98cd8b8a314 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java @@ -18,16 +18,19 @@ package org.apache.logging.log4j.core.appender.rolling.action; import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the {@code DeletingVisitor} class. @@ -55,7 +58,7 @@ protected void delete(final Path file) throws IOException { public void testAcceptedFilesAreDeleted() throws IOException { final Path base = Paths.get("/a/b/c"); final FixedCondition ACCEPT_ALL = new FixedCondition(true); - final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, Arrays.asList(ACCEPT_ALL), false); + final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, Collections.singletonList(ACCEPT_ALL), false); final Path any = Paths.get("/a/b/c/any"); visitor.visitFile(any, null); @@ -66,7 +69,7 @@ public void testAcceptedFilesAreDeleted() throws IOException { public void testRejectedFilesAreNotDeleted() throws IOException { final Path base = Paths.get("/a/b/c"); final FixedCondition REJECT_ALL = new FixedCondition(false); - final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, Arrays.asList(REJECT_ALL), false); + final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, Collections.singletonList(REJECT_ALL), false); final Path any = Paths.get("/a/b/c/any"); visitor.visitFile(any, null); @@ -127,9 +130,31 @@ public void beforeFileTreeWalk() { } }; final Path base = Paths.get("/a/b/c"); - final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, Arrays.asList(filter), false); + final DeletingVisitorHelper visitor = new DeletingVisitorHelper(base, Collections.singletonList(filter), false); final Path child = Paths.get("/a/b/c/relative"); visitor.visitFile(child, null); } + + @Test + public void testNoSuchFileFailure() throws IOException { + final DeletingVisitorHelper visitor = + new DeletingVisitorHelper(Paths.get("/a/b/c"), Collections.emptyList(), true); + assertEquals( + FileVisitResult.CONTINUE, + visitor.visitFileFailed(Paths.get("doesNotExist"), new NoSuchFileException("doesNotExist"))); + } + + @Test + public void testIOException() { + final DeletingVisitorHelper visitor = + new DeletingVisitorHelper(Paths.get("/a/b/c"), Collections.emptyList(), true); + IOException exception = new IOException(); + try { + visitor.visitFileFailed(Paths.get("doesNotExist"), exception); + fail(); + } catch (IOException e) { + assertSame(exception, e); + } + } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DurationTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DurationTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DurationTest.java index 456f95bd438..c156dede72d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DurationTest.java @@ -17,34 +17,33 @@ package org.apache.logging.log4j.core.appender.rolling.action; -import org.apache.logging.log4j.core.appender.rolling.action.Duration; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the Duration class. */ public class DurationTest { - @Test(expected = NullPointerException.class) + @Test public void testParseFailsIfNullText() { - Duration.parse(null); + assertThrows(NullPointerException.class, () -> Duration.parse(null)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testParseFailsIfInvalidPattern() { - Duration.parse("abc"); + assertThrows(IllegalArgumentException.class, () -> Duration.parse("abc")); } - @Test(expected = IllegalArgumentException.class) + @Test public void testParseFailsIfSectionsOutOfOrder() { - Duration.parse("P4DT2M1S3H"); + assertThrows(IllegalArgumentException.class, () -> Duration.parse("P4DT2M1S3H")); } - @Test(expected = IllegalArgumentException.class) + @Test public void testParseFailsIfTButMissingTime() { - Duration.parse("P1dT"); + assertThrows(IllegalArgumentException.class, () -> Duration.parse("P1dT")); } @Test diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameActionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameActionTest.java new file mode 100644 index 00000000000..faf017df23b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameActionTest.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling.action; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FileRenameActionTest { + + static File tempDir = new File("./target"); + + @AfterEach + public void cleanup() { + File file = new File(tempDir, "newFile.log"); + file.delete(); + } + + @Test + public void testRename1() throws Exception { + final File file = new File(tempDir, "fileRename.log"); + try (final PrintStream pos = new PrintStream(file)) { + for (int i = 0; i < 100; ++i) { + pos.println("This is line " + i); + } + } + + final File dest = new File(tempDir, "newFile.log"); + final FileRenameAction action = new FileRenameAction(file, dest, false); + action.execute(); + assertTrue(dest.exists(), "Renamed file does not exist"); + assertFalse(file.exists(), "Old file exists"); + } + + @Test + public void testEmpty() throws Exception { + final File file = new File(tempDir, "fileRename.log"); + try (final PrintStream pos = new PrintStream(file)) { + // do nothing + } + assertTrue(file.exists(), "File to rename does not exist"); + final File dest = new File(tempDir, "newFile.log"); + final FileRenameAction action = new FileRenameAction(file, dest, false); + action.execute(); + assertFalse(dest.exists(), "Renamed file should not exist"); + assertFalse(file.exists(), "Old file still exists"); + } + + @Test + public void testRenameEmpty() throws Exception { + final File file = new File(tempDir, "fileRename.log"); + try (final PrintStream pos = new PrintStream(file)) { + // do nothing + } + assertTrue(file.exists(), "File to rename does not exist"); + final File dest = new File(tempDir, "newFile.log"); + final FileRenameAction action = new FileRenameAction(file, dest, true); + action.execute(); + assertTrue(dest.exists(), "Renamed file should exist"); + assertFalse(file.exists(), "Old file still exists"); + } + + + @Test + public void testNoParent() throws Exception { + final File file = new File("fileRename.log"); + try (final PrintStream pos = new PrintStream(file)) { + for (int i = 0; i < 100; ++i) { + pos.println("This is line " + i); + } + } + + final File dest = new File(tempDir, "newFile.log"); + final FileRenameAction action = new FileRenameAction(file, dest, false); + action.execute(); + assertTrue(dest.exists(), "Renamed file does not exist"); + assertFalse(file.exists(), "Old file exists"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileSizeTest.java new file mode 100644 index 00000000000..6c1dc37d20c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileSizeTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling.action; + +import org.apache.logging.log4j.core.appender.rolling.FileSize; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; + +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FileSizeTest { + + @Test + public void testParse() { + assertEquals(5 * 1024, FileSize.parse("5k", 0)); + } + + @Test + @ResourceLock(Resources.LOCALE) + public void testParseInEurope() { + // Caveat: Breaks the ability for this test to run in parallel with other tests :( + Locale previousDefault = Locale.getDefault(); + try { + Locale.setDefault(new Locale("de", "DE")); + assertEquals(1000, FileSize.parse("1,000", 0)); + } finally { + Locale.setDefault(previousDefault); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FixedCondition.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FixedCondition.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FixedCondition.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FixedCondition.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCountTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCountTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCountTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCountTest.java index 0022b4007d4..af5ab262175 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCountTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCountTest.java @@ -17,9 +17,9 @@ package org.apache.logging.log4j.core.appender.rolling.action; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the IfAccumulatedFileCount class. @@ -54,7 +54,7 @@ public void testAcceptCallsNestedConditionsOnlyIfPathAccepted() { for (int i = 1; i < 10; i++) { if (i <= 3) { - assertFalse("i=" + i, condition.accept(null, null, null)); + assertFalse(condition.accept(null, null, null), "i=" + i); assertEquals(0, counter.getAcceptCount()); } else { assertTrue(condition.accept(null, null, null)); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSizeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSizeTest.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSizeTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSizeTest.java index 740c99851fc..82a1b924bba 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSizeTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSizeTest.java @@ -17,9 +17,10 @@ package org.apache.logging.log4j.core.appender.rolling.action; -import org.junit.Test; +import org.apache.logging.log4j.core.test.appender.rolling.action.DummyFileAttributes; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the IfAccumulatedFileSize class. @@ -36,6 +37,8 @@ public void testGetThresholdBytes() { assertEquals(3 * 1024 * 1024, create("3 MB").getThresholdBytes()); assertEquals(2L * 1024 * 1024 * 1024, create("2GB").getThresholdBytes()); assertEquals(3L * 1024 * 1024 * 1024, create("3 GB").getThresholdBytes()); + assertEquals(2L * 1024 * 1024 * 1024 * 1024, create("2TB").getThresholdBytes()); + assertEquals(3L * 1024 * 1024 * 1024 * 1024, create("3 TB").getThresholdBytes()); } private static IfAccumulatedFileSize create(final String size) { @@ -44,7 +47,7 @@ private static IfAccumulatedFileSize create(final String size) { @Test public void testNotAcceptOnExactMatch() { - final String[] sizes = {"2KB", "3MB", "4GB"}; + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; for (final String size : sizes) { final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); final DummyFileAttributes attribs = new DummyFileAttributes(); @@ -55,7 +58,7 @@ public void testNotAcceptOnExactMatch() { @Test public void testAcceptIfExceedThreshold() { - final String[] sizes = {"2KB", "3MB", "4GB"}; + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; for (final String size : sizes) { final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); final DummyFileAttributes attribs = new DummyFileAttributes(); @@ -66,7 +69,7 @@ public void testAcceptIfExceedThreshold() { @Test public void testNotAcceptIfBelowThreshold() { - final String[] sizes = {"2KB", "3MB", "4GB"}; + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; for (final String size : sizes) { final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); final DummyFileAttributes attribs = new DummyFileAttributes(); @@ -78,7 +81,7 @@ public void testNotAcceptIfBelowThreshold() { @Test public void testAcceptOnceThresholdExceeded() { final DummyFileAttributes attribs = new DummyFileAttributes(); - final String[] sizes = {"2KB", "3MB", "4GB"}; + final String[] sizes = {"2KB", "3MB", "4GB", "5TB"}; for (final String size : sizes) { final IfAccumulatedFileSize condition = IfAccumulatedFileSize.createFileSizeCondition(size); final long quarter = condition.getThresholdBytes() / 4; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java similarity index 87% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java index 12d5a0559cc..60d1b90dd2d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAllTest.java @@ -1,54 +1,52 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.rolling.action; - -import org.apache.logging.log4j.core.appender.rolling.action.IfAll; -import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the And composite condition. - */ -public class IfAllTest { - - @Test - public void testAccept() { - final PathCondition TRUE = new FixedCondition(true); - final PathCondition FALSE = new FixedCondition(false); - assertTrue(IfAll.createAndCondition(TRUE, TRUE).accept(null, null, null)); - assertFalse(IfAll.createAndCondition(FALSE, TRUE).accept(null, null, null)); - assertFalse(IfAll.createAndCondition(TRUE, FALSE).accept(null, null, null)); - assertFalse(IfAll.createAndCondition(FALSE, FALSE).accept(null, null, null)); - } - - @Test - public void testEmptyIsFalse() { - assertFalse(IfAll.createAndCondition().accept(null, null, null)); - } - - @Test - public void testBeforeTreeWalk() { - final CountingCondition counter = new CountingCondition(true); - final IfAll and = IfAll.createAndCondition(counter, counter, counter); - and.beforeFileTreeWalk(); - assertEquals(3, counter.getBeforeFileTreeWalkCount()); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling.action; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the And composite condition. + */ +public class IfAllTest { + + @Test + public void testAccept() { + final PathCondition TRUE = new FixedCondition(true); + final PathCondition FALSE = new FixedCondition(false); + assertTrue(IfAll.createAndCondition(TRUE, TRUE).accept(null, null, null)); + assertFalse(IfAll.createAndCondition(FALSE, TRUE).accept(null, null, null)); + assertFalse(IfAll.createAndCondition(TRUE, FALSE).accept(null, null, null)); + assertFalse(IfAll.createAndCondition(FALSE, FALSE).accept(null, null, null)); + } + + @Test + public void testEmptyIsFalse() { + assertFalse(IfAll.createAndCondition().accept(null, null, null)); + } + + @Test + public void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfAll and = IfAll.createAndCondition(counter, counter, counter); + and.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java index 52a8b27d540..46cdf59e786 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfAnyTest.java @@ -1,52 +1,52 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.rolling.action; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the Or composite condition. - */ -public class IfAnyTest { - - @Test - public void test() { - final PathCondition TRUE = new FixedCondition(true); - final PathCondition FALSE = new FixedCondition(false); - assertTrue(IfAny.createOrCondition(TRUE, TRUE).accept(null, null, null)); - assertTrue(IfAny.createOrCondition(FALSE, TRUE).accept(null, null, null)); - assertTrue(IfAny.createOrCondition(TRUE, FALSE).accept(null, null, null)); - assertFalse(IfAny.createOrCondition(FALSE, FALSE).accept(null, null, null)); - } - - @Test - public void testEmptyIsFalse() { - assertFalse(IfAny.createOrCondition().accept(null, null, null)); - } - - @Test - public void testBeforeTreeWalk() { - final CountingCondition counter = new CountingCondition(true); - final IfAny or = IfAny.createOrCondition(counter, counter, counter); - or.beforeFileTreeWalk(); - assertEquals(3, counter.getBeforeFileTreeWalkCount()); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling.action; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the Or composite condition. + */ +public class IfAnyTest { + + @Test + public void test() { + final PathCondition TRUE = new FixedCondition(true); + final PathCondition FALSE = new FixedCondition(false); + assertTrue(IfAny.createOrCondition(TRUE, TRUE).accept(null, null, null)); + assertTrue(IfAny.createOrCondition(FALSE, TRUE).accept(null, null, null)); + assertTrue(IfAny.createOrCondition(TRUE, FALSE).accept(null, null, null)); + assertFalse(IfAny.createOrCondition(FALSE, FALSE).accept(null, null, null)); + } + + @Test + public void testEmptyIsFalse() { + assertFalse(IfAny.createOrCondition().accept(null, null, null)); + } + + @Test + public void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfAny or = IfAny.createOrCondition(counter, counter, counter); + or.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileNameTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileNameTest.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileNameTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileNameTest.java index 2e84aa996a0..acc685c6500 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileNameTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileNameTest.java @@ -1,131 +1,131 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.rolling.action; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.Test; - -import static org.junit.Assert.*; - -public class IfFileNameTest { - - @Test(expected = IllegalArgumentException.class) - public void testCreateNameConditionFailsIfBothRegexAndPathAreNull() { - IfFileName.createNameCondition(null, null); - } - - @Test() - public void testCreateNameConditionAcceptsIfEitherRegexOrPathOrBothAreNonNull() { - IfFileName.createNameCondition("bar", null); - IfFileName.createNameCondition(null, "foo"); - IfFileName.createNameCondition("bar", "foo"); - } - - @Test - public void testGetSyntaxAndPattern() { - assertEquals("glob:path", IfFileName.createNameCondition("path", null).getSyntaxAndPattern()); - assertEquals("glob:path", IfFileName.createNameCondition("glob:path", null).getSyntaxAndPattern()); - assertEquals("regex:bar", IfFileName.createNameCondition(null, "bar").getSyntaxAndPattern()); - assertEquals("regex:bar", IfFileName.createNameCondition(null, "regex:bar").getSyntaxAndPattern()); - } - - @Test - public void testAcceptUsesPathPatternIfExists() { - final IfFileName filter = IfFileName.createNameCondition("path", "regex"); - final Path relativePath = Paths.get("path"); - assertTrue(filter.accept(null, relativePath, null)); - - final Path pathMatchingRegex = Paths.get("regex"); - assertFalse(filter.accept(null, pathMatchingRegex, null)); - } - - @Test - public void testAcceptUsesRegexIfNoPathPatternExists() { - final IfFileName regexFilter = IfFileName.createNameCondition(null, "regex"); - final Path pathMatchingRegex = Paths.get("regex"); - assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); - - final Path noMatch = Paths.get("nomatch"); - assertFalse(regexFilter.accept(null, noMatch, null)); - } - - @Test - public void testAcceptIgnoresBasePathAndAttributes() { - final IfFileName pathFilter = IfFileName.createNameCondition("path", null); - final Path relativePath = Paths.get("path"); - assertTrue(pathFilter.accept(null, relativePath, null)); - - final IfFileName regexFilter = IfFileName.createNameCondition(null, "regex"); - final Path pathMatchingRegex = Paths.get("regex"); - assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); - } - - @Test - public void testAcceptCallsNestedConditionsOnlyIfPathAccepted1() { - final CountingCondition counter = new CountingCondition(true); - final IfFileName regexFilter = IfFileName.createNameCondition(null, "regex", counter); - final Path pathMatchingRegex = Paths.get("regex"); - - assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); - assertEquals(1, counter.getAcceptCount()); - assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); - assertEquals(2, counter.getAcceptCount()); - assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); - assertEquals(3, counter.getAcceptCount()); - - final Path noMatch = Paths.get("nomatch"); - assertFalse(regexFilter.accept(null, noMatch, null)); - assertEquals(3, counter.getAcceptCount()); // no increase - assertFalse(regexFilter.accept(null, noMatch, null)); - assertEquals(3, counter.getAcceptCount()); - assertFalse(regexFilter.accept(null, noMatch, null)); - assertEquals(3, counter.getAcceptCount()); - } - - @Test - public void testAcceptCallsNestedConditionsOnlyIfPathAccepted2() { - final CountingCondition counter = new CountingCondition(true); - final IfFileName globFilter = IfFileName.createNameCondition("glob", null, counter); - final Path pathMatchingGlob = Paths.get("glob"); - - assertTrue(globFilter.accept(null, pathMatchingGlob, null)); - assertEquals(1, counter.getAcceptCount()); - assertTrue(globFilter.accept(null, pathMatchingGlob, null)); - assertEquals(2, counter.getAcceptCount()); - assertTrue(globFilter.accept(null, pathMatchingGlob, null)); - assertEquals(3, counter.getAcceptCount()); - - final Path noMatch = Paths.get("nomatch"); - assertFalse(globFilter.accept(null, noMatch, null)); - assertEquals(3, counter.getAcceptCount()); // no increase - assertFalse(globFilter.accept(null, noMatch, null)); - assertEquals(3, counter.getAcceptCount()); - assertFalse(globFilter.accept(null, noMatch, null)); - assertEquals(3, counter.getAcceptCount()); - } - - @Test - public void testBeforeTreeWalk() { - final CountingCondition counter = new CountingCondition(true); - final IfFileName pathFilter = IfFileName.createNameCondition("path", null, counter, counter, counter); - pathFilter.beforeFileTreeWalk(); - assertEquals(3, counter.getBeforeFileTreeWalkCount()); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class IfFileNameTest { + + @Test + public void testCreateNameConditionFailsIfBothRegexAndPathAreNull() { + assertThrows(IllegalArgumentException.class, () -> IfFileName.createNameCondition(null, null)); + } + + @Test + public void testCreateNameConditionAcceptsIfEitherRegexOrPathOrBothAreNonNull() { + IfFileName.createNameCondition("bar", null); + IfFileName.createNameCondition(null, "foo"); + IfFileName.createNameCondition("bar", "foo"); + } + + @Test + public void testGetSyntaxAndPattern() { + assertEquals("glob:path", IfFileName.createNameCondition("path", null).getSyntaxAndPattern()); + assertEquals("glob:path", IfFileName.createNameCondition("glob:path", null).getSyntaxAndPattern()); + assertEquals("regex:bar", IfFileName.createNameCondition(null, "bar").getSyntaxAndPattern()); + assertEquals("regex:bar", IfFileName.createNameCondition(null, "regex:bar").getSyntaxAndPattern()); + } + + @Test + public void testAcceptUsesPathPatternIfExists() { + final IfFileName filter = IfFileName.createNameCondition("path", "regex"); + final Path relativePath = Paths.get("path"); + assertTrue(filter.accept(null, relativePath, null)); + + final Path pathMatchingRegex = Paths.get("regex"); + assertFalse(filter.accept(null, pathMatchingRegex, null)); + } + + @Test + public void testAcceptUsesRegexIfNoPathPatternExists() { + final IfFileName regexFilter = IfFileName.createNameCondition(null, "regex"); + final Path pathMatchingRegex = Paths.get("regex"); + assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); + + final Path noMatch = Paths.get("nomatch"); + assertFalse(regexFilter.accept(null, noMatch, null)); + } + + @Test + public void testAcceptIgnoresBasePathAndAttributes() { + final IfFileName pathFilter = IfFileName.createNameCondition("path", null); + final Path relativePath = Paths.get("path"); + assertTrue(pathFilter.accept(null, relativePath, null)); + + final IfFileName regexFilter = IfFileName.createNameCondition(null, "regex"); + final Path pathMatchingRegex = Paths.get("regex"); + assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); + } + + @Test + public void testAcceptCallsNestedConditionsOnlyIfPathAccepted1() { + final CountingCondition counter = new CountingCondition(true); + final IfFileName regexFilter = IfFileName.createNameCondition(null, "regex", counter); + final Path pathMatchingRegex = Paths.get("regex"); + + assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); + assertEquals(1, counter.getAcceptCount()); + assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); + assertEquals(2, counter.getAcceptCount()); + assertTrue(regexFilter.accept(null, pathMatchingRegex, null)); + assertEquals(3, counter.getAcceptCount()); + + final Path noMatch = Paths.get("nomatch"); + assertFalse(regexFilter.accept(null, noMatch, null)); + assertEquals(3, counter.getAcceptCount()); // no increase + assertFalse(regexFilter.accept(null, noMatch, null)); + assertEquals(3, counter.getAcceptCount()); + assertFalse(regexFilter.accept(null, noMatch, null)); + assertEquals(3, counter.getAcceptCount()); + } + + @Test + public void testAcceptCallsNestedConditionsOnlyIfPathAccepted2() { + final CountingCondition counter = new CountingCondition(true); + final IfFileName globFilter = IfFileName.createNameCondition("glob", null, counter); + final Path pathMatchingGlob = Paths.get("glob"); + + assertTrue(globFilter.accept(null, pathMatchingGlob, null)); + assertEquals(1, counter.getAcceptCount()); + assertTrue(globFilter.accept(null, pathMatchingGlob, null)); + assertEquals(2, counter.getAcceptCount()); + assertTrue(globFilter.accept(null, pathMatchingGlob, null)); + assertEquals(3, counter.getAcceptCount()); + + final Path noMatch = Paths.get("nomatch"); + assertFalse(globFilter.accept(null, noMatch, null)); + assertEquals(3, counter.getAcceptCount()); // no increase + assertFalse(globFilter.accept(null, noMatch, null)); + assertEquals(3, counter.getAcceptCount()); + assertFalse(globFilter.accept(null, noMatch, null)); + assertEquals(3, counter.getAcceptCount()); + } + + @Test + public void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfFileName pathFilter = IfFileName.createNameCondition("path", null, counter, counter, counter); + pathFilter.beforeFileTreeWalk(); + assertEquals(3, counter.getBeforeFileTreeWalkCount()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java index 48d2a107f50..32471d6be13 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModifiedTest.java @@ -17,26 +17,27 @@ package org.apache.logging.log4j.core.appender.rolling.action; -import java.nio.file.attribute.FileTime; +import org.apache.logging.log4j.core.test.appender.rolling.action.DummyFileAttributes; +import org.junit.jupiter.api.Test; -import org.junit.Test; +import java.nio.file.attribute.FileTime; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** - * Tests the FileAgeFilter class. + * Tests the IfLastModified class. */ public class IfLastModifiedTest { @Test public void testGetDurationReturnsConstructorValue() { - final IfLastModified filter = IfLastModified.createAgeCondition(Duration.parse("P7D")); + final IfLastModified filter = IfLastModified.newBuilder().setAge(Duration.parse("P7D")).get(); assertEquals(0, filter.getAge().compareTo(Duration.parse("P7D"))); } @Test public void testAcceptsIfFileAgeEqualToDuration() { - final IfLastModified filter = IfLastModified.createAgeCondition(Duration.parse("PT33S")); + final IfLastModified filter = IfLastModified.newBuilder().setAge(Duration.parse("PT33S")).get(); final DummyFileAttributes attrs = new DummyFileAttributes(); final long age = 33 * 1000; attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - age); @@ -45,7 +46,7 @@ public void testAcceptsIfFileAgeEqualToDuration() { @Test public void testAcceptsIfFileAgeExceedsDuration() { - final IfLastModified filter = IfLastModified.createAgeCondition(Duration.parse("PT33S")); + final IfLastModified filter = IfLastModified.newBuilder().setAge(Duration.parse("PT33S")).get(); final DummyFileAttributes attrs = new DummyFileAttributes(); final long age = 33 * 1000 + 5; attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - age); @@ -54,7 +55,7 @@ public void testAcceptsIfFileAgeExceedsDuration() { @Test public void testDoesNotAcceptIfFileAgeLessThanDuration() { - final IfLastModified filter = IfLastModified.createAgeCondition(Duration.parse("PT33S")); + final IfLastModified filter = IfLastModified.newBuilder().setAge(Duration.parse("PT33S")).get(); final DummyFileAttributes attrs = new DummyFileAttributes(); final long age = 33 * 1000 - 5; attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - age); @@ -64,7 +65,10 @@ public void testDoesNotAcceptIfFileAgeLessThanDuration() { @Test public void testAcceptCallsNestedConditionsOnlyIfPathAccepted() { final CountingCondition counter = new CountingCondition(true); - final IfLastModified filter = IfLastModified.createAgeCondition(Duration.parse("PT33S"), counter); + final IfLastModified filter = IfLastModified.newBuilder() + .setAge(Duration.parse("PT33S")) + .setNestedConditions(counter) + .get(); final DummyFileAttributes attrs = new DummyFileAttributes(); final long oldEnough = 33 * 1000 + 5; attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - oldEnough); @@ -75,7 +79,7 @@ public void testAcceptCallsNestedConditionsOnlyIfPathAccepted() { assertEquals(2, counter.getAcceptCount()); assertTrue(filter.accept(null, null, attrs)); assertEquals(3, counter.getAcceptCount()); - + final long tooYoung = 33 * 1000 - 5; attrs.lastModified = FileTime.fromMillis(System.currentTimeMillis() - tooYoung); assertFalse(filter.accept(null, null, attrs)); @@ -89,8 +93,10 @@ public void testAcceptCallsNestedConditionsOnlyIfPathAccepted() { @Test public void testBeforeTreeWalk() { final CountingCondition counter = new CountingCondition(true); - final IfLastModified filter = IfLastModified.createAgeCondition(Duration.parse("PT33S"), counter, counter, - counter); + final IfLastModified filter = IfLastModified.newBuilder() + .setAge(Duration.parse("PT33S")) + .setNestedConditions(counter, counter, counter) + .get(); filter.beforeFileTreeWalk(); assertEquals(3, counter.getBeforeFileTreeWalkCount()); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java index c5abfb9e62b..69069089891 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/IfNotTest.java @@ -1,52 +1,52 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.rolling.action; - -import org.apache.logging.log4j.core.appender.rolling.action.IfNot; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the Not composite condition. - */ -public class IfNotTest { - - @Test - public void test() { - assertTrue(new FixedCondition(true).accept(null, null, null)); - assertFalse(IfNot.createNotCondition(new FixedCondition(true)).accept(null, null, null)); - - assertFalse(new FixedCondition(false).accept(null, null, null)); - assertTrue(IfNot.createNotCondition(new FixedCondition(false)).accept(null, null, null)); - } - - @Test(expected = NullPointerException.class) - public void testEmptyIsFalse() { - assertFalse(IfNot.createNotCondition(null).accept(null, null, null)); - } - - @Test - public void testBeforeTreeWalk() { - final CountingCondition counter = new CountingCondition(true); - final IfNot not = IfNot.createNotCondition(counter); - not.beforeFileTreeWalk(); - assertEquals(1, counter.getBeforeFileTreeWalkCount()); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling.action; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the Not composite condition. + */ +public class IfNotTest { + + @Test + public void test() { + assertTrue(new FixedCondition(true).accept(null, null, null)); + assertFalse(IfNot.createNotCondition(new FixedCondition(true)).accept(null, null, null)); + + assertFalse(new FixedCondition(false).accept(null, null, null)); + assertTrue(IfNot.createNotCondition(new FixedCondition(false)).accept(null, null, null)); + } + + @Test + public void testEmptyIsFalse() { + assertThrows(NullPointerException.class, + () -> IfNot.createNotCondition(null).accept(null, null, null)); + } + + @Test + public void testBeforeTreeWalk() { + final CountingCondition counter = new CountingCondition(true); + final IfNot not = IfNot.createNotCondition(counter); + not.beforeFileTreeWalk(); + assertEquals(1, counter.getBeforeFileTreeWalkCount()); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathConditionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathConditionTest.java new file mode 100644 index 00000000000..caa5262f25c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathConditionTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling.action; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +import org.junit.jupiter.api.Test; + +public class PathConditionTest { + + private static final PathCondition[] EMPTY_FIXTURE = {}; + private static final PathCondition[] NULL_FIXTURE = null; + + @Test + public void testCopy() { + assertArrayEquals(EMPTY_FIXTURE, PathCondition.copy(NULL_FIXTURE)); + assertArrayEquals(EMPTY_FIXTURE, PathCondition.copy(EMPTY_FIXTURE)); + assertArrayEquals(EMPTY_FIXTURE, PathCondition.copy(PathCondition.EMPTY_ARRAY)); + assertSame(PathCondition.EMPTY_ARRAY, PathCondition.copy(PathCondition.EMPTY_ARRAY)); + assertSame(PathCondition.EMPTY_ARRAY, PathCondition.copy(NULL_FIXTURE)); + assertSame(PathCondition.EMPTY_ARRAY, PathCondition.copy(EMPTY_FIXTURE)); + // + CountingCondition cc = new CountingCondition(true); + assertNotSame(cc, PathCondition.copy(cc)); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTimeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTimeTest.java new file mode 100644 index 00000000000..84afb62a1fb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTimeTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; + +import org.apache.logging.log4j.core.test.appender.rolling.action.DummyFileAttributes; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the {@code PathSortByModificationTime} class. + */ +public class PathSortByModificationTimeTest { + + /** + * Test method for + * {@link org.apache.logging.log4j.core.appender.rolling.action.PathSortByModificationTime#isRecentFirst()}. + */ + @Test + public void testIsRecentFirstReturnsConstructorValue() { + assertTrue(((PathSortByModificationTime) PathSortByModificationTime.createSorter(true)).isRecentFirst()); + assertFalse(((PathSortByModificationTime) PathSortByModificationTime.createSorter(false)).isRecentFirst()); + } + + @Test + public void testCompareRecentFirst() { + final PathSorter sorter = PathSortByModificationTime.createSorter(true); + final Path p1 = Paths.get("aaa"); + final Path p2 = Paths.get("bbb"); + final DummyFileAttributes a1 = new DummyFileAttributes(); + final DummyFileAttributes a2 = new DummyFileAttributes(); + a1.lastModified = FileTime.fromMillis(100); + a2.lastModified = FileTime.fromMillis(222); + + assertEquals(1, sorter.compare(path(p1, a1), path(p1, a2)), "same path, 2nd more recent"); + assertEquals(1, sorter.compare(path(p1, a1), path(p2, a2)), "path ignored, 2nd more recent"); + assertEquals(1, sorter.compare(path(p2, a1), path(p1, a2)), "path ignored, 2nd more recent"); + + assertEquals(-1, sorter.compare(path(p1, a2), path(p1, a1)), "same path, 1st more recent"); + assertEquals(-1, sorter.compare(path(p1, a2), path(p2, a1)), "path ignored, 1st more recent"); + assertEquals(-1, sorter.compare(path(p2, a2), path(p1, a1)), "path ignored, 1st more recent"); + + assertEquals(0, sorter.compare(path(p1, a1), path(p1, a1)), "same path, same time"); + assertEquals(1, sorter.compare(path(p1, a1), path(p2, a1)), "p2 < p1, same time"); + assertEquals(-1, sorter.compare(path(p2, a1), path(p1, a1)), "p2 < p1, same time"); + } + + @Test + public void testCompareRecentLast() { + final PathSorter sorter = PathSortByModificationTime.createSorter(false); + final Path p1 = Paths.get("aaa"); + final Path p2 = Paths.get("bbb"); + final DummyFileAttributes a1 = new DummyFileAttributes(); + final DummyFileAttributes a2 = new DummyFileAttributes(); + a1.lastModified = FileTime.fromMillis(100); + a2.lastModified = FileTime.fromMillis(222); + + assertEquals(-1, sorter.compare(path(p1, a1), path(p1, a2)), "same path, 2nd more recent"); + assertEquals(-1, sorter.compare(path(p1, a1), path(p2, a2)), "path ignored, 2nd more recent"); + assertEquals(-1, sorter.compare(path(p2, a1), path(p1, a2)), "path ignored, 2nd more recent"); + + assertEquals(1, sorter.compare(path(p1, a2), path(p1, a1)), "same path, 1st more recent"); + assertEquals(1, sorter.compare(path(p1, a2), path(p2, a1)), "path ignored, 1st more recent"); + assertEquals(1, sorter.compare(path(p2, a2), path(p1, a1)), "path ignored, 1st more recent"); + + assertEquals(0, sorter.compare(path(p1, a1), path(p1, a1)), "same path, same time"); + assertEquals(-1, sorter.compare(path(p1, a1), path(p2, a1)), "p1 < p2, same time"); + assertEquals(1, sorter.compare(path(p2, a1), path(p1, a1)), "p1 < p2, same time"); + } + + private PathWithAttributes path(final Path path, final DummyFileAttributes attributes) { + return new PathWithAttributes(path, attributes); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.java new file mode 100644 index 00000000000..9df7ab8a8aa --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling.action; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the SortingVisitor class. + */ +public class SortingVisitorTest { + + @TempDir + Path base; + private Path aaa; + private Path bbb; + private Path ccc; + + @BeforeEach + public void setUp() throws Exception { + aaa = Files.createFile(base.resolve("aaa")); + bbb = Files.createFile(base.resolve("bbb")); + ccc = Files.createFile(base.resolve("ccc")); + + // lastModified granularity is 1 sec(!) on some file systems... + final long now = System.currentTimeMillis(); + Files.setLastModifiedTime(aaa, FileTime.fromMillis(now)); + Files.setLastModifiedTime(bbb, FileTime.fromMillis(now + 1000)); + Files.setLastModifiedTime(ccc, FileTime.fromMillis(now + 2000)); + } + + @Test + public void testRecentFirst() throws Exception { + final SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(true)); + final Set options = Collections.emptySet(); + Files.walkFileTree(base, options, 1, visitor); + + final List found = visitor.getSortedPaths(); + assertNotNull(found); + assertEquals(3, found.size(), "file count"); + assertEquals(ccc, found.get(0).getPath(), "1st: most recent; sorted=" + found); + assertEquals(bbb, found.get(1).getPath(), "2nd; sorted=" + found); + assertEquals(aaa, found.get(2).getPath(), "3rd: oldest; sorted=" + found); + } + + @Test + public void testRecentLast() throws Exception { + final SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(false)); + final Set options = Collections.emptySet(); + Files.walkFileTree(base, options, 1, visitor); + + final List found = visitor.getSortedPaths(); + assertNotNull(found); + assertEquals(3, found.size(), "file count"); + assertEquals(aaa, found.get(0).getPath(), "1st: oldest first; sorted=" + found); + assertEquals(bbb, found.get(1).getPath(), "2nd; sorted=" + found); + assertEquals(ccc, found.get(2).getPath(), "3rd: most recent sorted; list=" + found); + } + + @Test + public void testNoSuchFileFailure() throws IOException { + SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(false)); + assertSame( + FileVisitResult.CONTINUE, + visitor.visitFileFailed(Paths.get("doesNotExist"), new NoSuchFileException("doesNotExist"))); + } + + @Test + public void testIOException() { + SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(false)); + IOException exception = new IOException(); + assertSame(exception, + assertThrows(IOException.class, () -> visitor.visitFileFailed(Paths.get("doesNotExist"), exception))); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppender2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppender2Test.java new file mode 100644 index 00000000000..b7680ad21ab --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppender2Test.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.routing; + +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class JsonRoutingAppender2Test { + private static final String CONFIG = "log4j-routing2.json"; + private static final String LOG_FILENAME = "target/rolling1/rollingtest-Unknown.log"; + + @Test + @CleanUpFiles(LOG_FILENAME) + @LoggerContextSource(CONFIG) + public void routingTest(@Named("List") final ListAppender appender) { + StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + EventLogger.logEvent(msg); + final List list = appender.getEvents(); + assertNotNull(list, "No events generated"); + assertEquals(1, list.size(), "Incorrect number of events. Expected 1, got " + list.size()); + msg = new StructuredDataMessage("Test", "This is a test", "Unknown"); + EventLogger.logEvent(msg); + final File file = new File(LOG_FILENAME); + assertTrue(file.exists(), "File was not created"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppenderTest.java new file mode 100644 index 00000000000..b752f6d8533 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/JsonRoutingAppenderTest.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.routing; + +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class JsonRoutingAppenderTest { + private static final String CONFIG = "log4j-routing.json"; + private static final String LOG_FILENAME = "target/rolling1/rollingtest-Unknown.log"; + + @Test + @CleanUpFiles(LOG_FILENAME) + @LoggerContextSource(CONFIG) + public void routingTest(@Named("List") final ListAppender appender) { + StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + EventLogger.logEvent(msg); + final List list = appender.getEvents(); + assertNotNull(list, "No events generated"); + assertEquals(1, list.size(), "Incorrect number of events. Expected 1, got " + list.size()); + msg = new StructuredDataMessage("Test", "This is a test", "Unknown"); + EventLogger.logEvent(msg); + final File file = new File(LOG_FILENAME); + assertTrue(file.exists(), "File was not created"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/PropertiesRoutingAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/PropertiesRoutingAppenderTest.java new file mode 100644 index 00000000000..8536edc2b2e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/PropertiesRoutingAppenderTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.routing; + +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class PropertiesRoutingAppenderTest { + private static final String CONFIG = "log4j-routing.properties"; + private static final String UNKNOWN_LOG_FILE = "target/rolling1/rollingtestProps-Unknown.log"; + private static final String ALERT_LOG_FILE = "target/routing1/routingtestProps-Alert.log"; + private static final String ACTIVITY_LOG_FILE = "target/routing1/routingtestProps-Activity.log"; + + @Test + @CleanUpFiles({ UNKNOWN_LOG_FILE, ALERT_LOG_FILE, ACTIVITY_LOG_FILE }) + @LoggerContextSource(CONFIG) + public void routingTest(@Named("List") final ListAppender app) { + StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + EventLogger.logEvent(msg); + final List list = app.getEvents(); + assertNotNull(list, "No events generated"); + assertEquals(1, list.size(), "Incorrect number of events. Expected 1, got " + list.size()); + msg = new StructuredDataMessage("Test", "This is a test", "Alert"); + EventLogger.logEvent(msg); + File file = new File(ALERT_LOG_FILE); + assertTrue(file.exists(), "Alert file was not created"); + msg = new StructuredDataMessage("Test", "This is a test", "Activity"); + EventLogger.logEvent(msg); + file = new File(ACTIVITY_LOG_FILE); + assertTrue(file.exists(), "Activity file was not created"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender2767Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender2767Test.java new file mode 100644 index 00000000000..67acf774f3e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender2767Test.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.routing; + +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +public class RoutingAppender2767Test { + private static final String CONFIG = "log4j-routing-2767.xml"; + private static final String ACTIVITY_LOG_FILE = "target/routing1/routingtest-Service.log"; + + @Test + @CleanUpFiles(ACTIVITY_LOG_FILE) + @LoggerContextSource(CONFIG) + public void routingTest() throws Exception { + StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + EventLogger.logEvent(msg); + File file = new File(ACTIVITY_LOG_FILE); + assertTrue(file.exists(), "Activity file was not created"); + List lines = Files.lines(file.toPath()).collect(Collectors.toList()); + assertEquals(1, lines.size(), "Incorrect number of lines"); + assertTrue(lines.get(0).contains("This is a test"), "Incorrect content"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java new file mode 100644 index 00000000000..b342be0ac3c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderKeyLookupEvaluationTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.routing; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@LoggerContextSource("log4j-routing-lookup.xml") +@UsingThreadContextMap +public class RoutingAppenderKeyLookupEvaluationTest { + + private static final String KEY = "user"; + + private final LoggerContext context; + private final ListAppender app; + + public RoutingAppenderKeyLookupEvaluationTest( + LoggerContext context, + @Named("List") ListAppender app) { + this.context = context; + this.app = app.clear(); + } + + @BeforeEach + public void setUp() throws Exception { + ThreadContext.remove(KEY); + } + + @AfterEach + public void tearDown() throws Exception { + this.app.clear(); + ThreadContext.remove(KEY); + } + + @Test + public void testRoutingNoUser() { + Logger logger = context.getLogger(getClass()); + logger.warn("no user"); + assertThat(app.getMessages()).contains("WARN ${ctx:user} no user"); + } + + @Test + public void testRoutingDoesNotMatchRoute() { + Logger logger = context.getLogger(getClass()); + ThreadContext.put(KEY, "noRouteExists"); + logger.warn("unmatched user"); + assertThat(app.getMessages()).isEmpty(); + } + + @Test + public void testRoutingContainsLookup() { + Logger logger = context.getLogger(getClass()); + ThreadContext.put(KEY, "${java:version}"); + logger.warn("naughty user"); + assertThat(app.getMessages()).contains("WARN ${java:version} naughty user"); + } + + @Test + public void testRoutingMatchesEscapedLookup() { + Logger logger = context.getLogger(getClass()); + ThreadContext.put(KEY, "${upper:name}"); + logger.warn("naughty user"); + assertThat(app.getMessages()).contains("WARN ${upper:name} naughty user"); + } + + @Test + public void testRoutesThemselvesNotEvaluated() { + Logger logger = context.getLogger(getClass()); + ThreadContext.put(KEY, "NAME"); + logger.warn("unmatched user"); + assertThat(app.getMessages()).isEmpty(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderTest.java new file mode 100644 index 00000000000..8b43ece317a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.routing; + +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class RoutingAppenderTest { + private static final String CONFIG = "log4j-routing.xml"; + private static final String UNKNOWN_LOG_FILE = "target/rolling1/rollingtest-Unknown.log"; + private static final String ALERT_LOG_FILE = "target/routing1/routingtest-Alert.log"; + private static final String ACTIVITY_LOG_FILE = "target/routing1/routingtest-Activity.log"; + + @Test + @CleanUpFiles({ UNKNOWN_LOG_FILE, ALERT_LOG_FILE, ACTIVITY_LOG_FILE }) + @LoggerContextSource(CONFIG) + public void routingTest(@Named("List") final ListAppender app) { + StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + EventLogger.logEvent(msg); + final List list = app.getEvents(); + assertNotNull(list, "No events generated"); + assertEquals(1, list.size(), "Incorrect number of events. Expected 1, got " + list.size()); + msg = new StructuredDataMessage("Test", "This is a test", "Alert"); + EventLogger.logEvent(msg); + File file = new File(ALERT_LOG_FILE); + assertTrue(file.exists(), "Alert file was not created"); + msg = new StructuredDataMessage("Test", "This is a test", "Activity"); + EventLogger.logEvent(msg); + file = new File(ACTIVITY_LOG_FILE); + assertTrue(file.exists(), "Activity file was not created"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java index 7dce22d9ae4..7330559ef0f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java @@ -16,27 +16,29 @@ */ package org.apache.logging.log4j.core.appender.routing; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.util.List; - import org.apache.logging.log4j.EventLogger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.apache.logging.log4j.test.appender.ListAppender; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.jupiter.api.Tag; import org.junit.rules.RuleChain; +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.*; + /** * Tests Routing appender purge facilities */ +@Tag("sleepy") public class RoutingAppenderWithPurgingTest { private static final String CONFIG = "log4j-routing-purge.xml"; private static final String IDLE_LOG_FILE1 = "target/routing-purge-idle/routingtest-1.log"; @@ -79,24 +81,31 @@ public void routingTest() throws InterruptedException { EventLogger.logEvent(msg); final List list = app.getEvents(); assertNotNull("No events generated", list); - assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1); + assertEquals("Incorrect number of events. Expected 1, got " + list.size(), 1, list.size()); msg = new StructuredDataMessage("2", "This is a test 2", "Service"); EventLogger.logEvent(msg); msg = new StructuredDataMessage("3", "This is a test 3", "Service"); EventLogger.logEvent(msg); - final String[] files = {IDLE_LOG_FILE1, IDLE_LOG_FILE2, IDLE_LOG_FILE3, MANUAL_LOG_FILE1, MANUAL_LOG_FILE2, MANUAL_LOG_FILE3}; + // '2' is a referenced list appender + final String[] files = {IDLE_LOG_FILE1, IDLE_LOG_FILE3, MANUAL_LOG_FILE1, MANUAL_LOG_FILE3}; assertFileExistance(files); + Set expectedAppenderKeys = new HashSet<>(2); + expectedAppenderKeys.add("1"); + expectedAppenderKeys.add("3"); + assertEquals(expectedAppenderKeys, routingAppenderManual.getAppenders().keySet()); - assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 3, routingAppenderIdle.getAppenders().size()); + assertFalse(((ListAppender) loggerContextRule.getAppender("ReferencedList")).getEvents().isEmpty()); + + assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 2, routingAppenderIdle.getAppenders().size()); assertEquals("Incorrect number of appenders with IdlePurgePolicy with HangingAppender.", - 3, routingAppenderIdleWithHangingAppender.getAppenders().size()); - assertEquals("Incorrect number of appenders manual purge.", 3, routingAppenderManual.getAppenders().size()); + 2, routingAppenderIdleWithHangingAppender.getAppenders().size()); + assertEquals("Incorrect number of appenders manual purge.", 2, routingAppenderManual.getAppenders().size()); Thread.sleep(3000); EventLogger.logEvent(msg); assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 1, routingAppenderIdle.getAppenders().size()); - assertEquals("Incorrect number of appenders with manual purge.", 3, routingAppenderManual.getAppenders().size()); + assertEquals("Incorrect number of appenders with manual purge.", 2, routingAppenderManual.getAppenders().size()); routingAppenderManual.deleteAppender("1"); routingAppenderManual.deleteAppender("2"); @@ -105,6 +114,9 @@ public void routingTest() throws InterruptedException { assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 1, routingAppenderIdle.getAppenders().size()); assertEquals("Incorrect number of appenders with manual purge.", 0, routingAppenderManual.getAppenders().size()); + assertFalse("Reference based routes should not be stoppable", + loggerContextRule.getAppender("ReferencedList").isStopped()); + msg = new StructuredDataMessage("5", "This is a test 5", "Service"); EventLogger.logEvent(msg); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingDefaultAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingDefaultAppenderTest.java new file mode 100644 index 00000000000..2f139b03f47 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingDefaultAppenderTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.routing; + +import org.apache.logging.log4j.EventLogger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class RoutingDefaultAppenderTest { + private static final String LOG_FILE = "target/routing1/routingtest.log"; + + @Test + @CleanUpFiles(LOG_FILE) + @LoggerContextSource("log4j-routing3.xml") + public void routingTest(@Named("List") final ListAppender app) { + StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service"); + EventLogger.logEvent(msg); + final List list = app.getEvents(); + assertNotNull(list, "No events generated"); + assertEquals(1, list.size(), "Incorrect number of events. Expected 1, got " + list.size()); + msg = new StructuredDataMessage("Test", "This is a test", "Alert"); + EventLogger.logEvent(msg); + final File file = new File(LOG_FILE); + assertTrue(file.exists(), "Alert file was not created"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderConfigTest_LOG4J2_2032.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderConfigTest_LOG4J2_2032.java new file mode 100644 index 00000000000..8cbee4e4cbc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderConfigTest_LOG4J2_2032.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.LOG_EVENT_FACTORY_CLASS_NAME, value = "org.apache.logging.log4j.core.impl.ReusableLogEventFactory") +@SetSystemProperty(key = LoggingSystemProperties.LOGGER_MESSAGE_FACTORY_CLASS, value = "org.apache.logging.log4j.message.ReusableMessageFactory") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncAppenderConfigTest-LOG4J2-2032.xml") +public class AsyncAppenderConfigTest_LOG4J2_2032 { + + @Test + @CleanUpFiles("target/AsyncAppenderConfigTest-LOG4J2-2032.log") + public void doNotProcessPlaceholdersTwice() throws Exception { + final File file = new File("target", "AsyncAppenderConfigTest-LOG4J2-2032.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + log.info("Text containing curly braces: {}", "Curly{}"); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + final String line1 = reader.readLine(); + System.out.println(line1); + assertTrue(line1.contains(" Text containing curly braces: Curly{} "), "line1 correct"); + } + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.java new file mode 100644 index 00000000000..f2fb9042f4f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncAppenderExceptionHandlingTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.config.AppenderControl; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.core.test.appender.FailOnceAppender; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Verifies {@link AsyncAppender} works after certain type of {@link Appender} + * failures. + *

+ * {@code AsyncAppender} thread is known to get killed due to + * {@link AppenderControl} leaking exceptions in the past. This class is more + * of an end-to-end test to verify that {@code AsyncAppender} still works even + * if the background thread gets killed. + */ +class AsyncAppenderExceptionHandlingTest { + + @ParameterizedTest + @ValueSource(strings = { + FailOnceAppender.ThrowableClassName.RUNTIME_EXCEPTION, + FailOnceAppender.ThrowableClassName.LOGGING_EXCEPTION, + FailOnceAppender.ThrowableClassName.EXCEPTION, + FailOnceAppender.ThrowableClassName.ERROR, + FailOnceAppender.ThrowableClassName.THROWABLE, + FailOnceAppender.ThrowableClassName.THREAD_DEATH + }) + void AsyncAppender_should_not_stop_on_appender_failures(String throwableClassName) { + + // Create the logger. + final String throwableClassNamePropertyName = "throwableClassName"; + System.setProperty(throwableClassNamePropertyName, throwableClassName); + try (final LoggerContext loggerContext = + Configurator.initialize("Test", "AsyncAppenderExceptionHandlingTest.xml")) { + final Logger logger = loggerContext.getRootLogger(); + + // Log the 1st message, which should fail due to the FailOnceAppender. + logger.info("message #1"); + + // Log the 2nd message, which should succeed. + final String lastLogMessage = "message #2"; + logger.info(lastLogMessage); + + // Stop the AsyncAppender to drain the queued events. + Configuration configuration = loggerContext.getConfiguration(); + AsyncAppender asyncAppender = configuration.getAppender("Async"); + Assertions.assertNotNull(asyncAppender, "couldn't obtain the FailOnceAppender"); + asyncAppender.stop(); + + // Verify the logged message. + final FailOnceAppender failOnceAppender = configuration.getAppender("FailOnce"); + Assertions.assertNotNull(failOnceAppender, "couldn't obtain the FailOnceAppender"); + Assertions.assertTrue(failOnceAppender.isFailed(), "FailOnceAppender hasn't failed yet"); + final List accumulatedMessages = failOnceAppender + .drainEvents() + .stream() + .map(LogEvent::getMessage) + .map(Message::getFormattedMessage) + .collect(Collectors.toList()); + Assertions.assertEquals(Collections.singletonList(lastLogMessage), accumulatedMessages); + + } finally { + System.setProperty(throwableClassNamePropertyName, Strings.EMPTY); + } + + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlock.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlock.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlock.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlock.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlockTest.java new file mode 100644 index 00000000000..b213db03837 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerClassLoadDeadlockTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Test class loading deadlock condition from the LOG4J2-1457 + */ +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector") +@SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, value = "128") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConsoleTest.xml") +public class AsyncLoggerClassLoadDeadlockTest { + + static final int RING_BUFFER_SIZE = 128; + + @Test + @Timeout(value = 30) + public void testClassLoaderDeadlock() throws Exception { + //touch the class so static init will be called + final AsyncLoggerClassLoadDeadlock temp = new AsyncLoggerClassLoadDeadlock(); + assertNotNull(temp); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig4Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig4Test.java new file mode 100644 index 00000000000..1e031762114 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig4Test.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConfigTest4.xml") +public class AsyncLoggerConfig4Test { + + @Test + public void testParameters() throws Exception { + final File file = new File("target", "AsyncLoggerConfigTest4.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + log.info("Additive logging: {} for the price of {}!", 2, 1); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + final String line3 = reader.readLine(); + final String line4 = reader.readLine(); + final String line5 = reader.readLine(); + reader.close(); + file.delete(); + + assertThat(line1, + containsString("Additive logging: {} for the price of {}! [2,1] Additive logging: 2 for the price of 1!")); + assertThat(line2, + containsString("Additive logging: {} for the price of {}! [2,1] Additive logging: 2 for the price of 1!")); + assertThat(line3, + containsString("Additive logging: {} for the price of {}! [2,1] Additive logging: 2 for the price of 1!")); + assertThat(line4, + containsString("Additive logging: {} for the price of {}! [2,1] Additive logging: 2 for the price of 1!")); + assertNull(line5, "Expected only two lines to be logged"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAutoFlushTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAutoFlushTest.java new file mode 100644 index 00000000000..5acd476b9cf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigAutoFlushTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.File; +import java.nio.file.Files; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConfigAutoFlushTest.xml") +public class AsyncLoggerConfigAutoFlushTest { + + @Test + @CleanUpFiles("target/AsyncLoggerConfigAutoFlushTest.log") + public void testFlushAtEndOfBatch() throws Exception { + final File file = new File("target", "AsyncLoggerConfigAutoFlushTest.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Message flushed with immediate flush=false"; + log.info(msg); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + final String contents = Files.readString(file.toPath()); + assertTrue(contents.contains(msg), "line1 correct"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigErrorOnFormat.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigErrorOnFormat.java new file mode 100644 index 00000000000..a1d640bdf8b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigErrorOnFormat.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.File; +import java.nio.file.Files; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.message.AsynchronouslyFormattable; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConfigErrorOnFormat.xml") +@SetSystemProperty(key = Log4jProperties.LOG_EVENT_FACTORY_CLASS_NAME, value = "org.apache.logging.log4j.core.impl.DefaultLogEventFactory") +public class AsyncLoggerConfigErrorOnFormat { + + @Test + @CleanUpFiles("target/AsyncLoggerConfigErrorOnFormat.log") + public void testError() throws Exception { + final File file = new File("target", "AsyncLoggerConfigErrorOnFormat.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + log.info(new ThrowsErrorOnFormatMessage()); + log.info("Second message"); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + final List lines = Files.readAllLines(file.toPath()); + final String line1 = lines.get(0); + + assertThat(line1, containsString("Second message")); + assertThat(lines, hasSize(1)); + } + + @AsynchronouslyFormattable // Shouldn't call getFormattedMessage early + private static final class ThrowsErrorOnFormatMessage implements Message { + + @Override + public String getFormattedMessage() { + throw new Error("getFormattedMessage invoked on " + Thread.currentThread().getName()); + } + + @Override + public String getFormat() { + return null; + } + + @Override + public Object[] getParameters() { + return null; + } + + @Override + public Throwable getThrowable() { + return null; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java new file mode 100644 index 00000000000..eddb6233aae --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConfigTest.xml") +public class AsyncLoggerConfigTest { + + @Test + public void testAdditivity() throws Exception { + final File file = new File("target", "AsyncLoggerConfigTest.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Additive logging: 2 for the price of 1!"; + log.info(msg); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + reader.close(); + file.delete(); + assertNotNull(line1, "line1"); + assertNotNull(line2, "line2"); + assertTrue(line1.contains(msg), "line1 correct"); + assertTrue(line2.contains(msg), "line2 correct"); + + final String location = "testAdditivity"; + assertTrue(line1.contains(location) || line2.contains(location), "location"); + } + + @Test + public void testIncludeLocationDefaultsToFalse() { + final LoggerConfig rootLoggerConfig = + AsyncLoggerConfig.RootLogger.createLogger( + null, Level.INFO, null, new AppenderRef[0], null, new DefaultConfiguration(), null); + assertFalse(rootLoggerConfig.isIncludeLocation(), "Include location should default to false for async loggers"); + + final LoggerConfig loggerConfig = + AsyncLoggerConfig.createLogger( + false, Level.INFO, "com.foo.Bar", null, new AppenderRef[0], null, new DefaultConfiguration(), + null); + assertFalse(loggerConfig.isIncludeLocation(), "Include location should default to false for async loggers"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest2.java new file mode 100644 index 00000000000..0ee021162e7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest2.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConfigTest2.xml") +public class AsyncLoggerConfigTest2 { + + @Test + public void testConsecutiveReconfigure() throws Exception { + final File file = new File("target", "AsyncLoggerConfigTest2.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Message before reconfig"; + log.info(msg); + + final LoggerContext ctx = LoggerContext.getContext(false); + ctx.reconfigure(); + ctx.reconfigure(); + + final String msg2 = "Message after reconfig"; + log.info(msg2); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + reader.close(); + file.delete(); + assertNotNull(line1, "line1"); + assertNotNull(line2, "line2"); + assertTrue(line1.contains(msg), "line1 " + line1); + assertTrue(line2.contains(msg2), "line2 " + line2); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest3.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest3.java new file mode 100644 index 00000000000..282651c2d1f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest3.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +@Tag("async") +@Tag("sleepy") +public class AsyncLoggerConfigTest3 { + + @Test + @CleanUpFiles("target/AsyncLoggerConfigTest2.log") + @LoggerContextSource("AsyncLoggerConfigTest2.xml") + public void testNoConcurrentModificationException(final Logger log) throws Exception { + log.info("initial message"); + Thread.sleep(500); + + final Map map = new HashMap<>(); + for (int j = 0; j < 3000; j++) { + map.put(String.valueOf(j), String.valueOf(System.nanoTime())); + } + + final Message msg = new ParameterizedMessage("{}", map); + Log4jLogEvent event = Log4jLogEvent.newBuilder() + .setLevel(Level.WARN) + .setLoggerName(getClass().getName()) + .setMessage(msg) + .setTimeMillis(0).build(); + + for (int i = 0; i < 100; i++) { + ((AsyncLoggerConfig)((org.apache.logging.log4j.core.Logger) log).get()).callAppenders(event); + for (int j = 0; j < 3000; j++) { + map.remove(String.valueOf(j)); + } + for (int j = 0; j < 3000; j++) { + map.put(String.valueOf(j), String.valueOf(System.nanoTime())); + } + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.java index e7dcf829e65..cec05f536ed 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigUseAfterShutdownTest.java @@ -19,30 +19,25 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.apache.logging.log4j.core.CoreLoggerContexts; -import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.spi.AbstractLogger; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; -@Category(AsyncLoggers.class) +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConfigTest.xml") public class AsyncLoggerConfigUseAfterShutdownTest { - @BeforeClass - public static void beforeClass() { - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "AsyncLoggerConfigTest.xml"); - } - @Test public void testNoErrorIfLogAfterShutdown() throws Exception { final Logger log = LogManager.getLogger("com.foo.Bar"); log.info("some message"); CoreLoggerContexts.stopLoggerContext(); // stop async thread - // call the #logMessage() method to bypass the isEnabled check: + // call the #logMessage() method to bypass the isEnabled check: // before the LOG4J2-639 fix this would throw a NPE ((AbstractLogger) log).logMessage("com.foo.Bar", Level.INFO, null, new SimpleMessage("msg"), null); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.java new file mode 100644 index 00000000000..65e6af56d4b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigWithAsyncEnabledTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerConfigTest4.xml") +public class AsyncLoggerConfigWithAsyncEnabledTest { + + @Test + public void testParametersAreAvailableToLayout() throws Exception { + final File file = new File("target", "AsyncLoggerConfigTest4.log"); + assertTrue(!file.exists() || file.delete(), "Deleted old file before test"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + String format = "Additive logging: {} for the price of {}!"; + log.info(format, 2, 1); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + reader.close(); + file.delete(); + + String expected = "Additive logging: {} for the price of {}! [2,1] Additive logging: 2 for the price of 1!"; + assertThat(line1, containsString(expected)); + assertThat(line2, containsString(expected)); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorInitialStateTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorInitialStateTest.java similarity index 75% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorInitialStateTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorInitialStateTest.java index e7b59d6f5e5..70a5214954e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorInitialStateTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorInitialStateTest.java @@ -16,18 +16,21 @@ */ package org.apache.logging.log4j.core.async; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; -@Category(AsyncLoggers.class) +@Tag("async") public class AsyncLoggerContextSelectorInitialStateTest { @Test public void testLoggerContextsListInitiallyEmpty() { - final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + final Injector injector = DI.createInjector(); + injector.init(); + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(injector); assertTrue(selector.getLoggerContexts().isEmpty()); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java similarity index 75% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java index fc5cb632733..1a41e3f17e9 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelectorTest.java @@ -16,23 +16,32 @@ */ package org.apache.logging.log4j.core.async; -import java.util.List; - -import org.apache.logging.log4j.categories.AsyncLoggers; import org.apache.logging.log4j.core.LoggerContext; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import java.util.List; -@Category(AsyncLoggers.class) +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") public class AsyncLoggerContextSelectorTest { - + private static final String FQCN = AsyncLoggerContextSelectorTest.class.getName(); + private final Injector injector = DI.createInjector(); + + @BeforeEach + void setUp() { + injector.init(); + } @Test public void testContextReturnsAsyncLoggerContext() { - final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(injector); final LoggerContext context = selector.getContext(FQCN, null, false); assertTrue(context instanceof AsyncLoggerContext); @@ -40,7 +49,7 @@ public void testContextReturnsAsyncLoggerContext() { @Test public void testContext2ReturnsAsyncLoggerContext() { - final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(injector); final LoggerContext context = selector.getContext(FQCN, null, false, null); assertTrue(context instanceof AsyncLoggerContext); @@ -48,7 +57,7 @@ public void testContext2ReturnsAsyncLoggerContext() { @Test public void testLoggerContextsReturnsAsyncLoggerContext() { - final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(injector); selector.getContext(FQCN, null, false); final List list = selector.getLoggerContexts(); @@ -58,11 +67,16 @@ public void testLoggerContextsReturnsAsyncLoggerContext() { @Test public void testContextNameIsAsyncLoggerContextWithClassHashCode() { - final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(injector); final LoggerContext context = selector.getContext(FQCN, null, false); final int hash = getClass().getClassLoader().hashCode(); final String expectedContextName = "AsyncContext@" + Integer.toHexString(hash); assertEquals(expectedContextName, context.getName()); } + @Test + public void testDependentOnClassLoader() { + final AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(injector); + assertTrue(selector.isClassLoaderDependent()); + } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.java similarity index 91% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.java index 875eb4c12be..c7f03311602 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerContextTest.java @@ -17,8 +17,8 @@ package org.apache.logging.log4j.core.async; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.apache.logging.log4j.core.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; import org.apache.logging.log4j.core.LoggerContext; import org.junit.Test; import org.junit.experimental.categories.Category; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.java new file mode 100644 index 00000000000..d2a3acac6ce --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerCustomSelectorLocationTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.net.URI; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; + +@Tag("async") +@ContextSelectorType(AsyncLoggerCustomSelectorLocationTest.CustomAsyncContextSelector.class) +@CleanUpFiles("target/AsyncLoggerCustomSelectorLocationTest.log") +@SetSystemProperty(key = ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, value = "AsyncLoggerCustomSelectorLocationTest.xml") +public class AsyncLoggerCustomSelectorLocationTest { + + @Test + public void testCustomAsyncSelectorLocation() throws Exception { + final File file = new File("target", "AsyncLoggerCustomSelectorLocationTest.log"); + final Logger log = LogManager.getLogger("com.foo.Bar"); + final Logger logIncludingLocation = LogManager.getLogger("com.include.location.Bar"); + final String msg = "Async logger msg with location"; + log.info(msg); + logIncludingLocation.info(msg); + CoreLoggerContexts.stopLoggerContext(false, file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String firstLine = reader.readLine(); + final String secondLine = reader.readLine(); + final String thirdLine = reader.readLine(); + reader.close(); + file.delete(); + // By default we expect location to be disabled + assertThat(firstLine, containsString(msg)); + assertThat(firstLine, not(containsString("testCustomAsyncSelectorLocation"))); + // Configuration allows us to retain location + assertThat(secondLine, containsString(msg)); + assertThat(secondLine, containsString("testCustomAsyncSelectorLocation")); + assertThat(thirdLine, nullValue()); + } + + @Singleton + public static final class CustomAsyncContextSelector implements ContextSelector { + private static final LoggerContext CONTEXT = new AsyncLoggerContext("AsyncDefault"); + @Override + public LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext) { + return CONTEXT; + } + + @Override + public LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext, URI configLocation) { + return CONTEXT; + } + + @Override + public List getLoggerContexts() { + return Collections.singletonList(CONTEXT); + } + + @Override + public void removeContext(LoggerContext context) { + // does not remove anything + } + + @Override + public boolean isClassLoaderDependent() { + return false; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultLocationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultLocationTest.java new file mode 100644 index 00000000000..3d24bf331da --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultLocationTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerDefaultLocationTest.xml") +public class AsyncLoggerDefaultLocationTest { + @Test + public void testAsyncLogWritesToLog() throws Exception { + LoggerContext context = (LoggerContext) LogManager.getContext(false); + ListAppender app = context.getConfiguration().getAppender("List"); + assertNotNull(app); + final Logger log = context.getLogger("com.foo.Bar"); + final String msg = "Async logger msg with no location by default"; + log.info(msg); + context.stop(); + assertEquals(1, app.getEvents().size()); + LogEvent event = app.getEvents().get(0); + assertFalse(event.isIncludeLocation(), "includeLocation should be false"); + assertNull(event.getSource(), "Location data should not be present"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerEventTranslationExceptionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerEventTranslationExceptionTest.java new file mode 100644 index 00000000000..42e38f8ae2d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerEventTranslationExceptionTest.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ReusableSimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import com.lmax.disruptor.ExceptionHandler; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test an exception thrown in {@link RingBufferLogEventTranslator#translateTo(RingBufferLogEvent, long)} + * does not cause another exception to be thrown later in the background thread + * in {@link RingBufferLogEventHandler#onEvent(RingBufferLogEvent, long, boolean)}. + * + * @see LOG4J2-2816 + */ +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_EXCEPTION_HANDLER_CLASS_NAME, value = "org.apache.logging.log4j.core.async.AsyncLoggerEventTranslationExceptionTest$TestExceptionHandler") +class AsyncLoggerEventTranslationExceptionTest { + + @Test + void testEventTranslationExceptionDoesNotCauseAsyncEventException() { + + final Logger log = LogManager.getLogger("com.foo.Bar"); + + assertTrue( + TestExceptionHandler.INSTANTIATED, + "TestExceptionHandler was not configured properly"); + + final Message exceptionThrowingMessage = new ExceptionThrowingMessage(); + assertThrows( + TestMessageException.class, + () -> ((AbstractLogger) log).logMessage( + "com.foo.Bar", + Level.INFO, + null, + exceptionThrowingMessage, + null)); + + CoreLoggerContexts.stopLoggerContext(); // stop async thread + + assertFalse( + TestExceptionHandler.EVENT_EXCEPTION_ENCOUNTERED, + "ExceptionHandler encountered an event exception"); + + } + + public static final class TestExceptionHandler implements ExceptionHandler { + + private static boolean INSTANTIATED = false; + + private static boolean EVENT_EXCEPTION_ENCOUNTERED = false; + + public TestExceptionHandler() { + INSTANTIATED = true; + } + + @Override + public void handleEventException(final Throwable error, final long sequence, final RingBufferLogEvent event) { + EVENT_EXCEPTION_ENCOUNTERED = true; + } + + @Override + public void handleOnStartException(final Throwable error) { + fail("Unexpected start exception: " + error.getMessage()); + } + + @Override + public void handleOnShutdownException(final Throwable error) { + fail("Unexpected shutdown exception: " + error.getMessage()); + } + + } + + private static class TestMessageException extends RuntimeException {} + + private static final class ExceptionThrowingMessage extends ReusableSimpleMessage { + + @Override + public String getFormattedMessage() { + throw new TestMessageException(); + } + + @Override + public String getFormat() { + throw new TestMessageException(); + } + + @Override + public Object[] getParameters() { + throw new TestMessageException(); + } + + @Override + public void formatTo(final StringBuilder buffer) { + throw new TestMessageException(); + } + + @Override + public Object[] swapParameters(final Object[] emptyReplacement) { + throw new TestMessageException(); + } + + @Override + public short getParameterCount() { + throw new TestMessageException(); + } + + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerLocationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerLocationTest.java new file mode 100644 index 00000000000..2091586c227 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerLocationTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerLocationTest.xml") +@CleanUpFiles("target/AsyncLoggerLocationTest.log") +public class AsyncLoggerLocationTest { + + @Test + public void testAsyncLogWritesToLog() throws Exception { + final File file = new File("target", "AsyncLoggerLocationTest.log"); + // System.out.println(f.getAbsolutePath()); + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Async logger msg with location"; + log.info(msg); + CoreLoggerContexts.stopLoggerContext(false, file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + reader.close(); + file.delete(); + assertNotNull(line1, "line1"); + assertTrue(line1.contains(msg), "line1 correct"); + + final String location = "testAsyncLogWritesToLog"; + assertTrue(line1.contains(location), "has location"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerNanoTimeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerNanoTimeTest.java new file mode 100644 index 00000000000..17053665e85 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerNanoTimeTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.time.SystemNanoClock; +import org.apache.logging.log4j.core.time.internal.DummyNanoClock; +import org.apache.logging.log4j.plugins.Named; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@LoggerContextSource("NanoTimeToFileTest.xml") +public class AsyncLoggerNanoTimeTest { + + @Test + public void testAsyncLogUsesNanoTimeClock(final LoggerContext context, @Named final ListAppender list) + throws InterruptedException { + final AsyncLogger log = (AsyncLogger) context.getLogger("com.foo.Bar"); + final long before = System.nanoTime(); + log.info("Use actual System.nanoTime()"); + assertTrue(log.getNanoClock() instanceof SystemNanoClock, "using SystemNanoClock"); + + final long DUMMYNANOTIME = -53; + log.getContext().getConfiguration().setNanoClock(new DummyNanoClock(DUMMYNANOTIME)); + log.updateConfiguration(log.getContext().getConfiguration()); + + // trigger a new nano clock lookup + log.updateConfiguration(log.getContext().getConfiguration()); + + log.info("Use dummy nano clock"); + assertTrue(log.getNanoClock() instanceof DummyNanoClock, "using SystemNanoClock"); + + final List messages = list.getMessages(2, 1, TimeUnit.SECONDS); + assertThat(messages).hasSize(2); + final String line1 = messages.get(0); + final String line2 = messages.get(1); + + assertNotNull(line1, "line1"); + assertNotNull(line2, "line2"); + final String[] line1Parts = line1.split(" AND "); + assertEquals("Use actual System.nanoTime()", line1Parts[2]); + assertEquals(line1Parts[0], line1Parts[1]); + final long loggedNanoTime = Long.parseLong(line1Parts[0]); + assertTrue(loggedNanoTime - before < TimeUnit.SECONDS.toNanos(1), "used system nano time"); + + final String[] line2Parts = line2.split(" AND "); + assertEquals("Use dummy nano clock", line2Parts[2]); + assertEquals(String.valueOf(DUMMYNANOTIME), line2Parts[0]); + assertEquals(String.valueOf(DUMMYNANOTIME), line2Parts[1]); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java new file mode 100644 index 00000000000..e2adb683d85 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.core.time.internal.DummyNanoClock; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerTest.xml") +public class AsyncLoggerTest { + + @Test + public void testAsyncLogWritesToLog() throws Exception { + final File file = new File("target", "AsyncLoggerTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + + final AsyncLogger log = (AsyncLogger) LogManager.getLogger("com.foo.Bar"); + assertTrue(log.getNanoClock() instanceof DummyNanoClock); + + final String msg = "Async logger msg"; + log.info(msg, new InternalError("this is not a real error")); + CoreLoggerContexts.stopLoggerContext(false, file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + reader.close(); + file.delete(); + assertNotNull(line1, "line1"); + assertTrue(line1.contains(msg), "line1 correct"); + + final String location = "testAsyncLogWritesToLog"; + assertFalse(line1.contains(location), "no location"); + + assertTrue(LogManager.getFactory().isClassLoaderDependent()); + } + + // NOTE: only define one @Test method per test class with Async Loggers to prevent spurious failures + // @Test + // public void testNanoClockInitiallyDummy() { + // final AsyncLogger log = (AsyncLogger) LogManager.getLogger("com.foo.Bar"); + // assertTrue(log.getNanoClock() instanceof DummyNanoClock); + // } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestArgumentFreedOnErrorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestArgumentFreedOnErrorTest.java new file mode 100644 index 00000000000..f41dd7df315 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestArgumentFreedOnErrorTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.GarbageCollectionHelper; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.GC_ENABLE_DIRECT_ENCODERS, value = "true") +@SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND, value = "true") +@ContextSelectorType(AsyncLoggerContextSelector.class) +public class AsyncLoggerTestArgumentFreedOnErrorTest { + + // LOG4J2-2725: events are cleared even after failure + @Test + public void testMessageIsGarbageCollected() throws Exception { + final AsyncLogger log = (AsyncLogger) LogManager.getLogger("com.foo.Bar"); + CountDownLatch garbageCollectionLatch = new CountDownLatch(1); + log.fatal(new ThrowingMessage(garbageCollectionLatch)); + try (GarbageCollectionHelper gcHelper = new GarbageCollectionHelper()) { + gcHelper.run(); + assertTrue(garbageCollectionLatch.await(30, TimeUnit.SECONDS), + "Parameter should have been garbage collected"); + } + } + + private static class ThrowingMessage implements Message, StringBuilderFormattable { + + private final CountDownLatch latch; + + ThrowingMessage(CountDownLatch latch) { + this.latch = latch; + } + + @Override + protected void finalize() throws Throwable { + latch.countDown(); + super.finalize(); + } + + @Override + public String getFormattedMessage() { + throw new Error("Expected"); + } + + @Override + public String getFormat() { + return ""; + } + + @Override + public Object[] getParameters() { + return new Object[0]; + } + + @Override + public Throwable getThrowable() { + return null; + } + + @Override + public void formatTo(StringBuilder buffer) { + throw new Error("Expected"); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestCachedThreadName.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestCachedThreadName.java new file mode 100644 index 00000000000..7020f59c6b9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestCachedThreadName.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerTest.xml") +public class AsyncLoggerTestCachedThreadName { + + @Test + public void testAsyncLogUsesCachedThreadName() throws Exception { + final File file = new File("target", "AsyncLoggerTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Async logger msg"; + log.info(msg); + Thread.currentThread().setName("MODIFIED-THREADNAME"); + log.info(msg); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + // System.out.println(line1); + // System.out.println(line2); + reader.close(); + file.delete(); + assertNotNull(line1, "line1"); + assertNotNull(line2, "line2"); + assertTrue(line1.endsWith(" INFO c.f.Bar [main] Async logger msg "), "line1"); + assertTrue(line2.endsWith(" INFO c.f.Bar [main] Async logger msg "), "line2"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestUncachedThreadName.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestUncachedThreadName.java new file mode 100644 index 00000000000..08089ddff2a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestUncachedThreadName.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, value = "UNCACHED") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerTest.xml") +@ContextSelectorType(AsyncLoggerContextSelector.class) +public class AsyncLoggerTestUncachedThreadName { + + @Test + public void testAsyncLogUsesCurrentThreadName() throws Exception { + final File file = new File("target", "AsyncLoggerTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Async logger msg"; + log.info(msg); + Thread.currentThread().setName("MODIFIED-THREADNAME"); + log.info(msg); + CoreLoggerContexts.stopLoggerContext(file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + final String line2 = reader.readLine(); + // System.out.println(line1); + // System.out.println(line2); + reader.close(); + file.delete(); + assertNotNull(line1, "line1"); + assertNotNull(line2, "line2"); + assertTrue(line1.endsWith(" INFO c.f.Bar [main] Async logger msg "), "line1"); + assertTrue(line2.endsWith(" INFO c.f.Bar [MODIFIED-THREADNAME] Async logger msg "), "line2"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java new file mode 100644 index 00000000000..e9a7afee7f3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadContextTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerThreadContextTest.xml") +@ContextSelectorType(AsyncLoggerContextSelector.class) +public class AsyncLoggerThreadContextTest { + + @Test + public void testAsyncLogWritesToLog() throws Exception { + final File file = new File("target", "AsyncLoggerTest.log"); + // System.out.println(f.getAbsolutePath()); + file.delete(); + + ThreadContext.push("stackvalue"); + ThreadContext.put("KEY", "mapvalue"); + + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Async logger msg"; + log.info(msg, new InternalError("this is not a real error")); + CoreLoggerContexts.stopLoggerContext(false, file); // stop async thread + + final BufferedReader reader = new BufferedReader(new FileReader(file)); + final String line1 = reader.readLine(); + reader.close(); + file.delete(); + assertNotNull(line1, "line1"); + assertTrue(line1.contains(msg), "line1 correct"); + + assertTrue(line1.contains("mapvalue"), "ThreadContext.map"); + assertTrue(line1.contains("stackvalue"), "ThreadContext.stack"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java new file mode 100644 index 00000000000..c40862eba61 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +@Tag("async") +public class AsyncLoggerThreadNameStrategyTest { + + @Test + public void testDefaultIfNotConfigured() throws Exception { + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns); + } + + @Test + @SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, value = "\\%%InValid ") + public void testDefaultIfInvalidConfig() throws Exception { + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns); + } + + @Test + @SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, value = "CACHED") + public void testUseCachedThreadNameIfConfigured() throws Exception { + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.CACHED, tns); + } + + @Test + @SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, value = "UNCACHED") + public void testUseUncachedThreadNameIfConfigured() throws Exception { + final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create(); + assertSame(ThreadNameCachingStrategy.UNCACHED, tns); + } + + @Test + public void testUncachedThreadNameStrategyReturnsCurrentThreadName() throws Exception { + final String name1 = "MODIFIED-THREADNAME1"; + Thread.currentThread().setName(name1); + assertEquals(name1, ThreadNameCachingStrategy.UNCACHED.getThreadName()); + + final String name2 = "OTHER-THREADNAME2"; + Thread.currentThread().setName(name2); + assertEquals(name2, ThreadNameCachingStrategy.UNCACHED.getThreadName()); + } + + @Test + public void testCachedThreadNameStrategyReturnsCachedThreadName() throws Exception { + final String original = "Original-ThreadName"; + Thread.currentThread().setName(original); + assertEquals(original, ThreadNameCachingStrategy.CACHED.getThreadName()); + + final String name2 = "OTHER-THREADNAME2"; + Thread.currentThread().setName(name2); + assertEquals(original, ThreadNameCachingStrategy.CACHED.getThreadName()); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTimestampMessageTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTimestampMessageTest.java new file mode 100644 index 00000000000..ffe2c9683c9 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTimestampMessageTest.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.TimestampMessage; +import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.plugins.Named; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Confirms that if you log a {@link TimestampMessage} then there are no unnecessary calls to {@link Clock}. + *

+ * See LOG4J2-744. + *

+ */ +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@LoggerContextSource("AsyncLoggerTimestampMessageTest.xml") +public class AsyncLoggerTimestampMessageTest { + + @Factory + public static Clock getClock() { + return new PoisonClock(); + } + + @Test + public void testAsyncLogWritesToLog(final LoggerContext context, @Named final ListAppender list) + throws InterruptedException { + final Logger log = context.getLogger("com.foo.Bar"); + assertThat(PoisonClock.invoked).hasValue(null); + log.info((Message) new TimeMsg("Async logger msg with embedded timestamp", 123456789000L)); + assertThat(list.getMessages(1, 1, TimeUnit.SECONDS)).containsExactly("123456789000 Async logger msg with embedded timestamp"); + assertThat(PoisonClock.invoked).hasValue(null); + } + + public static class PoisonClock implements Clock { + static final AtomicReference invoked = new AtomicReference<>(); + + @Override + public long currentTimeMillis() { + invoked.set(new AssertionFailedError("PoisonClock should not have been invoked")); + return 987654321L; + } + } + + static class TimeMsg extends SimpleMessage implements TimestampMessage { + private static final long serialVersionUID = 1L; + private final long timestamp; + + public TimeMsg(final String msg, final long timestamp) { + super(msg); + this.timestamp = timestamp; + } + + @Override + public long getTimestamp() { + return timestamp; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerUseAfterShutdownTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerUseAfterShutdownTest.java new file mode 100644 index 00000000000..267f9e88e05 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerUseAfterShutdownTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +/** + * Test for LOG4J2-639 + */ +@Tag("async") +@Tag("functional") +public class AsyncLoggerUseAfterShutdownTest { + @Test + @SetSystemProperty(key = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, value = "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector") + @SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncLoggerTest.xml") + public void testNoErrorIfLogAfterShutdown() throws Exception { + final Logger log = LogManager.getLogger("com.foo.Bar"); + final String msg = "Async logger msg"; + log.info(msg, new InternalError("this is not a real error")); + CoreLoggerContexts.stopLoggerContext(); // stop async thread + + // call the #logMessage() method to bypass the isEnabled check: + // before the LOG4J2-639 fix this would throw a NPE + ((AbstractLogger) log).logMessage("com.foo.Bar", Level.INFO, null, new SimpleMessage("msg"), null); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncAppenderTest.java new file mode 100644 index 00000000000..0a051ffd792 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncAppenderTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.plugins.SingletonFactory; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@Tag("async") +public class AsyncLoggersWithAsyncAppenderTest { + + @SingletonFactory + public ContextSelector contextSelector(final Injector injector) { + return new AsyncLoggerContextSelector(injector); + } + + @Test + @LoggerContextSource("AsyncLoggersWithAsyncAppenderTest.xml") + public void testLoggingWorks(final Logger logger, @Named("List") final ListAppender appender) throws Exception { + logger.error("This {} a test", "is"); + logger.warn("Hello {}!", "world"); + final List list = appender.getMessages(2, 100, TimeUnit.MILLISECONDS); + assertNotNull(list, "No events generated"); + assertEquals(2, list.size(), "Incorrect number of events "); + String msg = list.get(0); + String expected = getClass().getName() + " This {} a test - [is] - This is a test"; + assertEquals(expected, msg); + msg = list.get(1); + expected = getClass().getName() + " Hello {}! - [world] - Hello world!"; + assertEquals(expected, msg); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java new file mode 100644 index 00000000000..3e9388c14dd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggersWithAsyncLoggerConfigTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.plugins.SingletonFactory; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@Tag("async") +public class AsyncLoggersWithAsyncLoggerConfigTest { + + @SingletonFactory + public ContextSelector contextSelector(final Injector injector) { + return new AsyncLoggerContextSelector(injector); + } + + @Test + @LoggerContextSource("AsyncLoggersWithAsyncLoggerConfigTest.xml") + public void testLoggingWorks(final Logger logger, @Named("List") final ListAppender appender) throws Exception { + logger.error("This is a test"); + logger.warn("Hello world!"); + final List list = appender.getMessages(2, 100, TimeUnit.MILLISECONDS); + assertNotNull(list, "No events generated"); + assertEquals(2, list.size(), "Incorrect number of events. Expected 2, got " + list.size()); + String msg = list.get(0); + String expected = getClass().getName() + " This is a test"; + assertEquals(expected, msg, "Expected " + expected + ", Actual " + msg); + msg = list.get(1); + expected = getClass().getName() + " Hello world!"; + assertEquals(expected, msg, "Expected " + expected + ", Actual " + msg); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.java new file mode 100644 index 00000000000..8820918017c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.Locale; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests the AsyncQueueFullPolicyFactory class. + */ +@Tag("async") +public class AsyncQueueFullPolicyFactoryTest { + + @BeforeEach + @AfterEach + public void resetProperties() { + System.clearProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY); + System.clearProperty(Log4jProperties.ASYNC_LOGGER_DISCARD_THRESHOLD); + PropertiesUtil.getProperties().reload(); + } + + @Test + public void testCreateReturnsDefaultRouterByDefault() { + final AsyncQueueFullPolicy router = AsyncQueueFullPolicyFactory.create(); + assertEquals(DefaultAsyncQueueFullPolicy.class, router.getClass()); + } + + @Test + public void testCreateReturnsDiscardingRouterIfSpecified() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER); + assertEquals(DiscardingAsyncQueueFullPolicy.class, AsyncQueueFullPolicyFactory.create().getClass()); + + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + DiscardingAsyncQueueFullPolicy.class.getSimpleName()); + assertEquals(DiscardingAsyncQueueFullPolicy.class, AsyncQueueFullPolicyFactory.create().getClass()); + + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + DiscardingAsyncQueueFullPolicy.class.getName()); + assertEquals(DiscardingAsyncQueueFullPolicy.class, AsyncQueueFullPolicyFactory.create().getClass()); + } + + @Test + public void testCreateDiscardingRouterDefaultThresholdLevelInfo() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER); + assertEquals(Level.INFO, ((DiscardingAsyncQueueFullPolicy) AsyncQueueFullPolicyFactory.create()). + getThresholdLevel()); + } + + @Test + public void testCreateDiscardingRouterCaseInsensitive() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER.toLowerCase(Locale.ENGLISH)); + assertEquals(Level.INFO, ((DiscardingAsyncQueueFullPolicy) AsyncQueueFullPolicyFactory.create()). + getThresholdLevel()); + } + + @Test + public void testCreateDiscardingRouterThresholdLevelCustomizable() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER); + + for (final Level level : Level.values()) { + System.setProperty(Log4jProperties.ASYNC_LOGGER_DISCARD_THRESHOLD, + level.name()); + assertEquals(level, ((DiscardingAsyncQueueFullPolicy) AsyncQueueFullPolicyFactory.create()). + getThresholdLevel()); + } + } + + static class CustomRouterDefaultConstructor implements AsyncQueueFullPolicy { + public CustomRouterDefaultConstructor() { + } + + @Override + public EventRoute getRoute(final long backgroundThreadId, final Level level) { + return null; + } + } + + static class DoesNotImplementInterface { + } + + @Test + public void testCreateReturnsCustomRouterIfSpecified() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + CustomRouterDefaultConstructor.class.getName()); + assertEquals(CustomRouterDefaultConstructor.class, AsyncQueueFullPolicyFactory.create().getClass()); + } + + @Test + public void testCreateReturnsDefaultRouterIfSpecifiedCustomRouterFails() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, + DoesNotImplementInterface.class.getName()); + assertEquals(DefaultAsyncQueueFullPolicy.class, AsyncQueueFullPolicyFactory.create().getClass()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java new file mode 100644 index 00000000000..a5a595727f0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.FileUtils; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * Tests LOG4J2-807. + */ +@Tag("async") +@Tag("sleepy") +public class AsyncRootReloadTest { + + private static final String ISSUE = "LOG4J2-807"; + private static final String ISSUE_CONFIG = ISSUE + ".xml"; + private static final String LOG = "target/" + ISSUE + ".log"; + private static final String RESOURCE = "classpath:" + ISSUE_CONFIG; + + @Test + @CleanUpFiles(LOG) + @LoggerContextSource(RESOURCE) + public void testLog4j2_807(final Logger logger) throws InterruptedException, URISyntaxException { + final URL url = AsyncRootReloadTest.class.getResource("/" + ISSUE_CONFIG); + final File configFile = FileUtils.fileFromUri(url.toURI()); + + logger.info("Log4j configured, will be reconfigured in approx. 5 sec"); + + configFile.setLastModified(System.currentTimeMillis()); + + for (int i = 0; i < 10; i++) { + Thread.sleep(1000); + logger.info("Log4j waiting for reconfiguration"); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java new file mode 100644 index 00000000000..292437161c2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URI; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.ThreadContextTestAccess; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.jmx.RingBufferAdmin; +import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.spi.DefaultThreadContextMap; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.Unbox; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@Tag("sleepy") +@SetSystemProperty(key = Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, value = "128") // minimum ringbuffer size +@SetSystemProperty(key = Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE, value = "128") // minimum ringbuffer size +public class AsyncThreadContextTest { + + private final static int LINE_COUNT = 130; + public static final File[] FILES = { + new File("target", "AsyncLoggerTest.log"), // + new File("target", "SynchronousContextTest.log"), // + new File("target", "AsyncLoggerAndAsyncAppenderTest.log"), // + new File("target", "AsyncAppenderContextTest.log"), // + }; + + @AfterAll + public static void afterClass() { + System.clearProperty(LoggingSystemProperties.THREAD_CONTEXT_GARBAGE_FREE_ENABLED); + System.clearProperty(LoggingSystemProperties.THREAD_CONTEXT_MAP_CLASS); + } + + enum Mode { + ALL_ASYNC(AsyncLoggerContextSelector.class, "AsyncLoggerThreadContextTest.xml"), + MIXED(ClassLoaderContextSelector.class, "AsyncLoggerConfigThreadContextTest.xml"), + BOTH_ALL_ASYNC_AND_MIXED(AsyncLoggerContextSelector.class, "AsyncLoggerConfigThreadContextTest.xml"); + + final Class contextSelectorType; + final URI configUri; + + Mode(final Class contextSelectorType, final String file) { + this.contextSelectorType = contextSelectorType; + configUri = NetUtils.toURI(file); + } + } + + enum ContextImpl { + WEBAPP, GARBAGE_FREE, COPY_ON_WRITE; + + void init() { + System.clearProperty(LoggingSystemProperties.THREAD_CONTEXT_MAP_CLASS); + final String PACKAGE = "org.apache.logging.log4j.spi."; + System.setProperty(LoggingSystemProperties.THREAD_CONTEXT_MAP_CLASS, PACKAGE + implClassSimpleName()); + PropertiesUtil.getProperties().reload(); + ThreadContextTestAccess.init(); + } + + public String implClassSimpleName() { + switch (this) { + case WEBAPP: + return DefaultThreadContextMap.class.getSimpleName(); + case GARBAGE_FREE: + return "GarbageFreeSortedArrayThreadContextMap"; + case COPY_ON_WRITE: + return "CopyOnWriteSortedArrayThreadContextMap"; + } + throw new IllegalStateException("Unknown state " + this); + } + } + + @ParameterizedTest(name = "{0} {1}") + @CsvSource({ + "COPY_ON_WRITE, MIXED", + "WEBAPP, MIXED", + "COPY_ON_WRITE, ALL_ASYNC", + "COPY_ON_WRITE, BOTH_ALL_ASYNC_AND_MIXED", + "WEBAPP, ALL_ASYNC", + "WEBAPP, BOTH_ALL_ASYNC_AND_MIXED", + "GARBAGE_FREE, ALL_ASYNC", + "GARBAGE_FREE, BOTH_ALL_ASYNC_AND_MIXED", + }) + @CleanUpFiles({ + "target/AsyncLoggerTest.log", + "target/SynchronousContextTest.log", + "target/AsyncLoggerAndAsyncAppenderTest.log", + "target/AsyncAppenderContextTest.log", + }) + public void testAsyncLogWritesToLog(final ContextImpl contextImpl, final Mode asyncMode) throws Exception { + doTestAsyncLogWritesToLog(contextImpl, asyncMode, getClass()); + } + + static void doTestAsyncLogWritesToLog(final ContextImpl contextImpl, final Mode asyncMode, final Class testClass) throws Exception { + final Injector injector = DI.createInjector(); + injector.registerBinding(ContextSelector.KEY, injector.getFactory(asyncMode.contextSelectorType)); + injector.init(); + final Log4jContextFactory factory = new Log4jContextFactory(injector); + final String fqcn = testClass.getName(); + final ClassLoader classLoader = testClass.getClassLoader(); + final String name = contextImpl.toString() + ' ' + asyncMode; + contextImpl.init(); + final LoggerContext context = factory.getContext(fqcn, classLoader, null, false, asyncMode.configUri, name); + runTest(context, contextImpl, asyncMode); + } + + private static void runTest(final LoggerContext context, final ContextImpl contextImpl, final Mode asyncMode) throws Exception { + ThreadContext.push("stackvalue"); + ThreadContext.put("KEY", "mapvalue"); + + final Logger log = context.getLogger("com.foo.Bar"); + final String loggerContextName = context.getClass().getSimpleName(); + RingBufferAdmin ring; + if (context instanceof AsyncLoggerContext) { + ring = ((AsyncLoggerContext) context).createRingBufferAdmin(); + } else { + ring = ((AsyncLoggerConfig) log.get()).createRingBufferAdmin(""); + } + + for (int i = 0; i < LINE_COUNT; i++) { + while (i >= 128 && ring.getRemainingCapacity() == 0) { // buffer may be full + Thread.sleep(1); + } + if ((i & 1) == 1) { + ThreadContext.put("count", String.valueOf(i)); + } else { + ThreadContext.remove("count"); + } + log.info("{} {} {} i={}", contextImpl, contextMap(), loggerContextName, Unbox.box(i)); + } + ThreadContext.pop(); + context.stop(); + CoreLoggerContexts.stopLoggerContext(FILES[0]); // stop async thread + + checkResult(FILES[0], loggerContextName, contextImpl); + if (asyncMode == Mode.MIXED || asyncMode == Mode.BOTH_ALL_ASYNC_AND_MIXED) { + for (int i = 1; i < FILES.length; i++) { + checkResult(FILES[i], loggerContextName, contextImpl); + } + } + } + + private static String contextMap() { + final ReadOnlyThreadContextMap impl = ThreadContext.getThreadContextMap(); + return impl == null ? ContextImpl.WEBAPP.implClassSimpleName() : impl.getClass().getSimpleName(); + } + + private static void checkResult(final File file, final String loggerContextName, final ContextImpl contextImpl) throws IOException { + final String contextDesc = contextImpl + " " + contextImpl.implClassSimpleName() + " " + loggerContextName; + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + String expect; + for (int i = 0; i < LINE_COUNT; i++) { + final String line = reader.readLine(); + if ((i & 1) == 1) { + expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, configProp=configValue, configProp2=configValue2, count=" + i + "} " + + contextDesc + " i=" + i; + } else { + expect = "INFO c.f.Bar mapvalue [stackvalue] {KEY=mapvalue, configProp=configValue, configProp2=configValue2} " + contextDesc + " i=" + i; + } + assertEquals(expect, line, file.getName() + ": line " + i); + } + final String noMoreLines = reader.readLine(); + assertNull(noMoreLines, "done"); + } finally { + file.delete(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigGlobalLoggersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigGlobalLoggersTest.java new file mode 100644 index 00000000000..499f395b578 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigGlobalLoggersTest.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.lmax.disruptor.YieldingWaitStrategy; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@LoggerContextSource("AsyncWaitStrategyFactoryConfigGlobalLoggerTest.xml") +public class AsyncWaitStrategyFactoryConfigGlobalLoggersTest { + + @Disabled("This test succeeds when run individually but fails when run by Surefire with all other tests") + @Test + public void testConfigWaitStrategyAndFactory() throws Exception { + final AsyncLogger logger = (AsyncLogger) LogManager.getLogger("com.foo.Bar"); + + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + assertTrue(context instanceof AsyncLoggerContext, "context is AsyncLoggerContext"); + + AsyncWaitStrategyFactory asyncWaitStrategyFactory = context.getConfiguration().getAsyncWaitStrategyFactory(); + assertEquals(AsyncWaitStrategyFactoryConfigTest.YieldingWaitStrategyFactory.class, asyncWaitStrategyFactory.getClass()); + assertTrue(asyncWaitStrategyFactory instanceof AsyncWaitStrategyFactoryConfigTest.YieldingWaitStrategyFactory, "factory is YieldingWaitStrategyFactory"); + + AsyncLoggerDisruptor delegate = logger.getAsyncLoggerDisruptor(); + + assertEquals(YieldingWaitStrategy.class, delegate.getWaitStrategy().getClass()); + assertTrue(delegate.getWaitStrategy() instanceof YieldingWaitStrategy, "waitstrategy is YieldingWaitStrategy"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigTest.java new file mode 100644 index 00000000000..afe08bf49f1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfigTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import com.lmax.disruptor.WaitStrategy; +import com.lmax.disruptor.YieldingWaitStrategy; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@Tag("async") +public class AsyncWaitStrategyFactoryConfigTest { + + @Test + @LoggerContextSource("AsyncWaitStrategyFactoryConfigTest.xml") + public void testConfigWaitStrategyFactory(final LoggerContext context) throws Exception { + AsyncWaitStrategyFactory asyncWaitStrategyFactory = context.getConfiguration().getAsyncWaitStrategyFactory(); + assertThat(asyncWaitStrategyFactory.getClass()).isEqualTo(YieldingWaitStrategyFactory.class); + assertThat(asyncWaitStrategyFactory instanceof YieldingWaitStrategyFactory); //"factory is YieldingWaitStrategyFactory" + } + + @Test + @LoggerContextSource("AsyncWaitStrategyFactoryConfigTest.xml") + public void testWaitStrategy(final LoggerContext context) throws Exception { + + org.apache.logging.log4j.Logger logger = context.getRootLogger(); + + AsyncLoggerConfig loggerConfig = (AsyncLoggerConfig) ((org.apache.logging.log4j.core.Logger) logger).get(); + AsyncLoggerConfigDisruptor delegate = (AsyncLoggerConfigDisruptor) loggerConfig.getAsyncLoggerConfigDelegate(); + assertThat(delegate.getWaitStrategy().getClass()).isEqualTo(YieldingWaitStrategy.class); + assertThat(delegate.getWaitStrategy() instanceof com.lmax.disruptor.YieldingWaitStrategy);// "waitstrategy is YieldingWaitStrategy"); + } + + @Test + @LoggerContextSource("AsyncWaitStrategyIncorrectFactoryConfigTest.xml") + public void testIncorrectConfigWaitStrategyFactory(final LoggerContext context) throws Exception { + AsyncWaitStrategyFactory asyncWaitStrategyFactory = context.getConfiguration().getAsyncWaitStrategyFactory(); + assertThat(asyncWaitStrategyFactory).isNull(); // because invalid configuration + } + + @Test + @LoggerContextSource("AsyncWaitStrategyIncorrectFactoryConfigTest.xml") + public void testIncorrectWaitStrategyFallsBackToDefault( + @Named("WaitStrategyAppenderList") final ListAppender list1, + final LoggerContext context) throws Exception { + org.apache.logging.log4j.Logger logger = context.getRootLogger(); + + AsyncLoggerConfig loggerConfig = (AsyncLoggerConfig) ((org.apache.logging.log4j.core.Logger) logger).get(); + AsyncLoggerConfigDisruptor delegate = (AsyncLoggerConfigDisruptor) loggerConfig.getAsyncLoggerConfigDelegate(); + assertThat(delegate.getWaitStrategy().getClass()).isEqualTo(TimeoutBlockingWaitStrategy.class); + assertThat(delegate.getWaitStrategy() instanceof TimeoutBlockingWaitStrategy); //"waitstrategy is TimeoutBlockingWaitStrategy" + } + + public static class YieldingWaitStrategyFactory implements AsyncWaitStrategyFactory { + @Override + public WaitStrategy createWaitStrategy() { + return new YieldingWaitStrategy(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryIncorrectConfigGlobalLoggersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryIncorrectConfigGlobalLoggersTest.java new file mode 100644 index 00000000000..1106c56330c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryIncorrectConfigGlobalLoggersTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("async") +@ContextSelectorType(AsyncLoggerContextSelector.class) +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "AsyncWaitStrategyIncorrectFactoryConfigGlobalLoggerTest.xml") +public class AsyncWaitStrategyFactoryIncorrectConfigGlobalLoggersTest { + + @Test + public void testIncorrectConfigWaitStrategyFactory() throws Exception { + final LoggerContext context = (LoggerContext) LogManager.getContext(false); + assertTrue(context instanceof AsyncLoggerContext, "context is AsyncLoggerContext"); + + AsyncWaitStrategyFactory asyncWaitStrategyFactory = context.getConfiguration().getAsyncWaitStrategyFactory(); + assertNull(asyncWaitStrategyFactory); + + AsyncLogger logger = (AsyncLogger) context.getRootLogger(); + AsyncLoggerDisruptor delegate = logger.getAsyncLoggerDisruptor(); + assertEquals(TimeoutBlockingWaitStrategy.class, delegate.getWaitStrategy().getClass()); + assertTrue(delegate.getWaitStrategy() instanceof TimeoutBlockingWaitStrategy, + "waitstrategy is TimeoutBlockingWaitStrategy"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelectorTest.java new file mode 100644 index 00000000000..a87cfc169ca --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelectorTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@Tag("async") +@ContextSelectorType(BasicAsyncLoggerContextSelector.class) +public class BasicAsyncLoggerContextSelectorTest { + + private static final String FQCN = BasicAsyncLoggerContextSelectorTest.class.getName(); + + @Test + public void testContextReturnsAsyncLoggerContext() { + final ContextSelector selector = ((Log4jContextFactory) LogManager.getFactory()).getSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false); + + assertTrue(context instanceof AsyncLoggerContext); + } + + @Test + public void testContext2ReturnsAsyncLoggerContext() { + final ContextSelector selector = ((Log4jContextFactory) LogManager.getFactory()).getSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false, null); + + assertTrue(context instanceof AsyncLoggerContext); + } + + @Test + public void testLoggerContextsReturnsAsyncLoggerContext() { + final ContextSelector selector = ((Log4jContextFactory) LogManager.getFactory()).getSelector(); + assertThat(selector.getLoggerContexts()).hasExactlyElementsOfTypes(AsyncLoggerContext.class); + + selector.getContext(FQCN, null, false); + + assertThat(selector.getLoggerContexts()).hasExactlyElementsOfTypes(AsyncLoggerContext.class); + } + + @Test + public void testContextNameIsAsyncDefault() { + final ContextSelector selector = ((Log4jContextFactory) LogManager.getFactory()).getSelector(); + final LoggerContext context = selector.getContext(FQCN, null, false); + assertEquals("AsyncDefault", context.getName()); + } + + @Test + public void testDependentOnClassLoader() { + final ContextSelector selector = ((Log4jContextFactory) LogManager.getFactory()).getSelector(); + assertFalse(selector.isClassLoaderDependent()); + } + + @Test + public void testFactoryIsNotDependentOnClassLoader() { + assertFalse(LogManager.getFactory().isClassLoaderDependent()); + } + + @Test + public void testLogManagerShutdown() { + LoggerContext context = (LoggerContext) LogManager.getContext(); + assertEquals(LifeCycle.State.STARTED, context.getState()); + LogManager.shutdown(); + assertEquals(LifeCycle.State.STOPPED, context.getState()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java new file mode 100644 index 00000000000..e1892b9a2cb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.io.Serializable; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Appender that can be halted and resumed, for testing queue-full scenarios. + */ +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Blocking") +public class BlockingAppender extends AbstractAppender { + private static final long serialVersionUID = 1L; + // logEvents may be nulled to disable event tracking, this is useful in scenarios testing garbage collection. + public List logEvents = new CopyOnWriteArrayList<>(); + public CountDownLatch countDownLatch = null; + + public BlockingAppender(final String name) { + super(name, null, null, true, Property.EMPTY_ARRAY); + } + + @Override + public void append(final LogEvent event) { + + // for scenarios where domain objects log from their toString method in the background thread + event.getMessage().getFormattedMessage(); + + // may be a reusable event, make a copy, don't keep a reference to the original event + List events = logEvents; + if (events != null) { + events.add(Log4jLogEvent.createMemento(event)); + } + + if (countDownLatch == null) { + return; + } + // block until the test class tells us to continue + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @PluginFactory + public static BlockingAppender createAppender( + @PluginAttribute + @Required(message = "No name provided for HangingAppender") + final String name, + @PluginElement final Layout layout, + @PluginElement final Filter filter) { + return new BlockingAppender(name); + } + + @Override + public void start() { + super.start(); + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + super.stop(timeout, timeUnit, false); + setStopped(); + return true; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java similarity index 90% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java index d6d2b568087..cc65c1fef34 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java @@ -17,16 +17,15 @@ package org.apache.logging.log4j.core.async; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests the DefaultAsyncQueueFullPolicy class. */ -@Category(AsyncLoggers.class) +@Tag("async") public class DefaultAsyncQueueFullPolicyTest { private static long currentThreadId() { @@ -50,4 +49,4 @@ public void testGetRouteSynchronousIfQueueFullAndCalledFromSameThread() throws E assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), Level.ALL)); assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), Level.OFF)); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java index 8c5ef5aab23..235cc9e20bb 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java @@ -17,7 +17,7 @@ package org.apache.logging.log4j.core.async; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -117,4 +117,4 @@ public void testGetDiscardCount() throws Exception { assertEquals(EventRoute.DISCARD, router.getRoute(-1L, Level.INFO)); assertEquals("increase", 3, DiscardingAsyncQueueFullPolicy.getDiscardCount(router)); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/GarbageFreeAsyncThreadContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/GarbageFreeAsyncThreadContextTest.java new file mode 100644 index 00000000000..38f9a8f2e0c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/GarbageFreeAsyncThreadContextTest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.async; + +import org.junit.jupiter.api.Test; + +/** + * Provided as a dedicated test as it depends on cached state during Log4j startup. + */ +class GarbageFreeAsyncThreadContextTest { + @Test + void garbageFreeMixed() throws Exception { + AsyncThreadContextTest.doTestAsyncLogWritesToLog(AsyncThreadContextTest.ContextImpl.GARBAGE_FREE, + AsyncThreadContextTest.Mode.MIXED, getClass()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java new file mode 100644 index 00000000000..44edc4a5499 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import static org.junit.Assert.assertArrayEquals; + +/** + * Tests LOG4J2-1688 Multiple loggings of arguments are setting these arguments to null. + */ +@RunWith(BlockJUnit4ClassRunner.class) +@Category(AsyncLoggers.class) +public class Log4j2Jira1688AsyncTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, + AsyncLoggerContextSelector.class.getName()); + } + + @AfterClass + public static void afterClass() { + System.clearProperty(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule("log4j-list.xml"); + private ListAppender listAppender; + + @Before + public void before() throws Exception { + listAppender = context.getListAppender("List"); + } + + private static Object[] createArray(final int size) { + final Object[] args = new Object[size]; + for (int i = 0; i < args.length; i++) { + args[i] = i; + } + return args; + } + + @Test + public void testLog4j2Only() throws InterruptedException { + final ExtendedLogger log4JLogger = context.getLogger(this.getClass()); + final int limit = 11; // more than unrolled varargs + final Object[] args = createArray(limit); + final Object[] originalArgs = Arrays.copyOf(args, args.length); + + listAppender.countDownLatch = new CountDownLatch(1); + log4JLogger.logIfEnabled("test", Level.ERROR, null, "test {}", args); + + listAppender.countDownLatch.await(1, TimeUnit.SECONDS); + assertArrayEquals(Arrays.toString(args), originalArgs, args); + + log4JLogger.logIfEnabled("test", Level.ERROR, null, "test {}", args); + assertArrayEquals(Arrays.toString(args), originalArgs, args); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java new file mode 100644 index 00000000000..8938b20f1c3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * Tests LOG4J2-1688 Multiple loggings of arguments are setting these arguments to null. + */ +@RunWith(BlockJUnit4ClassRunner.class) +@Category(AsyncLoggers.class) +public class Log4j2Jira1688Test { + + @Rule + public LoggerContextRule context = new LoggerContextRule("log4j-list.xml"); + private ListAppender listAppender; + + @Before + public void before() throws Exception { + listAppender = context.getListAppender("List"); + } + + private static Object[] createArray(final int size) { + final Object[] args = new Object[size]; + for (int i = 0; i < args.length; i++) { + args[i] = i; + } + return args; + } + + @Test + public void testLog4j2Only() throws InterruptedException { + final ExtendedLogger log4JLogger = context.getLogger(this.getClass()); + final int limit = 11; // more than unrolled varargs + final Object[] args = createArray(limit); + final Object[] originalArgs = Arrays.copyOf(args, args.length); + + listAppender.countDownLatch = new CountDownLatch(1); + log4JLogger.logIfEnabled("test", Level.ERROR, null, "test {}", args); + + listAppender.countDownLatch.await(1, TimeUnit.SECONDS); + Assert.assertArrayEquals(Arrays.toString(args), originalArgs, args); + + log4JLogger.logIfEnabled("test", Level.ERROR, null, "test {}", args); + Assert.assertArrayEquals(Arrays.toString(args), originalArgs, args); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java index d0542dce4b4..b3d54bfd4e9 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java @@ -36,7 +36,7 @@ * Tests queue full scenarios abstract superclass. */ public abstract class QueueFullAbstractTest { - protected static boolean TRACE = false; + protected static boolean TRACE = Boolean.getBoolean(QueueFullAbstractTest.class.getSimpleName() + ".TRACE"); protected BlockingAppender blockingAppender; protected Unlocker unlocker; @@ -126,4 +126,4 @@ private static Field field(Class c, String name) throws NoSuchFieldException f.setAccessible(true); return f; } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java similarity index 87% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java index dae5095029e..ebaaa831884 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java @@ -21,9 +21,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.AsyncLoggers; import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.message.ParameterizedMessage; import org.junit.Before; import org.junit.BeforeClass; @@ -42,12 +42,6 @@ @Category(AsyncLoggers.class) public class QueueFullAsyncAppenderTest extends QueueFullAbstractTest { - @BeforeClass - public static void beforeClass() { - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, - "log4j2-queueFullAsyncAppender.xml"); - } - @Rule public LoggerContextRule context = new LoggerContextRule( "log4j2-queueFullAsyncAppender.xml"); @@ -59,8 +53,8 @@ public void before() throws Exception { @Test(timeout = 5000) - public void testNormalQueueFullKeepsMessagesInOrder() throws InterruptedException { - final Logger logger = LogManager.getLogger(this.getClass()); + public void testNormalQueueFullKeepsMessagesInOrder() { + final Logger logger = context.getLogger(this.getClass()); blockingAppender.countDownLatch = new CountDownLatch(1); unlocker = new Unlocker(new CountDownLatch(129)); @@ -90,4 +84,4 @@ static void asyncAppenderTest(final Logger logger, } assertTrue(actual.isEmpty()); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java index d8ce85ca2aa..b6769cf3d1b 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java @@ -18,11 +18,11 @@ import java.util.concurrent.CountDownLatch; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; @@ -40,11 +40,12 @@ public class QueueFullAsyncAppenderTest2 extends QueueFullAbstractTest { @BeforeClass public static void beforeClass() { - //FORMAT_MESSAGES_IN_BACKGROUND - System.setProperty("log4j.format.msg.async", "true"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND, "true"); + } - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, - "log4j2-queueFullAsyncAppender.xml"); + @AfterClass + public static void afterClass() throws Exception { + System.clearProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND); } @Rule @@ -58,8 +59,8 @@ public void before() throws Exception { @Test(timeout = 5000) - public void testNormalQueueFullKeepsMessagesInOrder() throws InterruptedException { - final Logger logger = LogManager.getLogger(this.getClass()); + public void testNormalQueueFullKeepsMessagesInOrder() { + final Logger logger = context.getLogger(this.getClass()); blockingAppender.countDownLatch = new CountDownLatch(1); unlocker = new Unlocker(new CountDownLatch(129)); @@ -67,4 +68,4 @@ public void testNormalQueueFullKeepsMessagesInOrder() throws InterruptedExceptio QueueFullAsyncAppenderTest.asyncAppenderTest(logger, unlocker, blockingAppender); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java new file mode 100644 index 00000000000..17b5081b33f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.List; +import java.util.Stack; +import java.util.concurrent.CountDownLatch; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests queue full scenarios with AsyncLoggers in configuration. + */ +@Tag("async") +@DisabledOnOs(value = OS.WINDOWS, disabledReason = "https://issues.apache.org/jira/browse/LOG4J2-3513") +@SetSystemProperty(key = Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE, value = "128") +public class QueueFullAsyncLoggerConfigLoggingFromToStringTest extends QueueFullAbstractTest { + + @Test + @LoggerContextSource(value = "log4j2-queueFullAsyncLoggerConfig.xml", timeout = 5) + public void testLoggingFromToStringCausesOutOfOrderMessages( + final LoggerContext context, @Named("Blocking") final BlockingAppender blockingAppender) { + this.blockingAppender = blockingAppender; + //TRACE = true; + final Logger logger = context.getLogger(this.getClass()); + + blockingAppender.countDownLatch = new CountDownLatch(1); + unlocker = new Unlocker(new CountDownLatch(129)); // count slightly different from "pure" async loggers + unlocker.start(); + + asyncLoggerConfigRecursiveTest(logger, unlocker, blockingAppender, this); + } + + static void asyncLoggerConfigRecursiveTest(final Logger logger, + final Unlocker unlocker, + final BlockingAppender blockingAppender, + final QueueFullAbstractTest factory) { + for (int i = 0; i < 1; i++) { + TRACE("Test logging message " + i + ". Remaining capacity=" + asyncRemainingCapacity(logger)); + TRACE("Test decrementing unlocker countdown latch. Count=" + unlocker.countDownLatch.getCount()); + unlocker.countDownLatch.countDown(); + final DomainObject obj = factory.new DomainObject(129); + logger.info("logging naughty object #{} {}", i, obj); + } + TRACE("Before stop() blockingAppender.logEvents.count=" + blockingAppender.logEvents.size()); + //CoreLoggerContexts.stopLoggerContext(false); // stop async thread + while (blockingAppender.logEvents.size() < 130) { Thread.yield(); } + TRACE("After stop() blockingAppender.logEvents.count=" + blockingAppender.logEvents.size()); + + final Stack actual = transform(blockingAppender.logEvents); + assertEquals("Logging in toString() #0", actual.pop()); + List statusDataList = StatusLogger.getLogger().getStatusData(); + assertEquals("Logging in toString() #128", actual.pop(), "Jumped the queue: queue full"); + StatusData mostRecentStatusData = statusDataList.get(statusDataList.size() - 1); + assertEquals(Level.WARN, mostRecentStatusData.getLevel(), "Expected warn level status message"); + assertThat(mostRecentStatusData.getFormattedStatus(), containsString( + "Log4j2 logged an event out of order to prevent deadlock caused by domain " + + "objects logging from their toString method when the async queue is full")); + + for (int i = 1; i < 128; i++) { + assertEquals("Logging in toString() #" + i, actual.pop(), "First batch"); + } + assertEquals("logging naughty object #0 Who's bad?!", actual.pop()); + assertTrue(actual.isEmpty()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java new file mode 100644 index 00000000000..cb11e263e7f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.concurrent.CountDownLatch; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * Tests queue full scenarios with AsyncLoggers in configuration. + */ +@RunWith(BlockJUnit4ClassRunner.class) +@Category(AsyncLoggers.class) +public class QueueFullAsyncLoggerConfigLoggingFromToStringTest2 extends QueueFullAbstractTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND, "true"); + System.setProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE, "128"); + } + + @AfterClass + public static void afterClass() throws Exception { + System.clearProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND); + System.clearProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule( + "log4j2-queueFullAsyncLoggerConfig.xml"); + + @Before + public void before() throws Exception { + blockingAppender = context.getRequiredAppender("Blocking", BlockingAppender.class); + } + + @Test(timeout = 5000) + public void testLoggingFromToStringCausesOutOfOrderMessages() { + //TRACE = true; + final Logger logger = context.getLogger(this.getClass()); + + blockingAppender.countDownLatch = new CountDownLatch(1); + unlocker = new Unlocker(new CountDownLatch(129)); // count slightly different from "pure" async loggers + unlocker.start(); + + QueueFullAsyncLoggerConfigLoggingFromToStringTest.asyncLoggerConfigRecursiveTest(logger, unlocker, blockingAppender, this); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java index 7d685822a9a..86c40b346dd 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java @@ -19,12 +19,12 @@ import java.util.Stack; import java.util.concurrent.CountDownLatch; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.message.ParameterizedMessage; +import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; @@ -33,7 +33,8 @@ import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Tests queue full scenarios with AsyncLoggers in configuration. @@ -44,9 +45,12 @@ public class QueueFullAsyncLoggerConfigTest extends QueueFullAbstractTest { @BeforeClass public static void beforeClass() { - System.setProperty("AsyncLoggerConfig.RingBufferSize", "128"); // minimum ringbuffer size - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, - "log4j2-queueFullAsyncLoggerConfig.xml"); + System.setProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE, "128"); // minimum ringbuffer size + } + + @AfterClass + public static void afterClass() throws Exception { + System.clearProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE); } @Rule @@ -61,7 +65,7 @@ public void before() throws Exception { @Test(timeout = 5000) public void testNormalQueueFullKeepsMessagesInOrder() throws InterruptedException { - final Logger logger = LogManager.getLogger(this.getClass()); + final Logger logger = context.getLogger(this.getClass()); blockingAppender.countDownLatch = new CountDownLatch(1); unlocker = new Unlocker(new CountDownLatch(129)); @@ -91,4 +95,4 @@ static void asyncLoggerConfigTest(final Logger logger, } assertTrue(actual.isEmpty()); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java new file mode 100644 index 00000000000..38c79a8481e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.concurrent.CountDownLatch; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * Tests queue full scenarios with AsyncLoggers in configuration. + */ +@RunWith(BlockJUnit4ClassRunner.class) +@Category(AsyncLoggers.class) +public class QueueFullAsyncLoggerConfigTest2 extends QueueFullAbstractTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND, "true"); + System.setProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE, "128"); // minimum ringbuffer size + } + + @AfterClass + public static void afterClass() throws Exception { + System.clearProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND); + System.clearProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule( + "log4j2-queueFullAsyncLoggerConfig.xml"); + + @Before + public void before() throws Exception { + blockingAppender = context.getRequiredAppender("Blocking", BlockingAppender.class); + } + + + @Test(timeout = 5000) + public void testNormalQueueFullKeepsMessagesInOrder() { + final Logger logger = context.getLogger(this.getClass()); + + blockingAppender.countDownLatch = new CountDownLatch(1); + unlocker = new Unlocker(new CountDownLatch(129)); + unlocker.start(); + + QueueFullAsyncLoggerConfigTest.asyncLoggerConfigTest(logger, unlocker, blockingAppender); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java new file mode 100644 index 00000000000..f803f6fe9bc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.Stack; +import java.util.concurrent.CountDownLatch; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests queue full scenarios with pure AsyncLoggers (all loggers async). + */ +@RunWith(BlockJUnit4ClassRunner.class) +@Category(AsyncLoggers.class) +public class QueueFullAsyncLoggerLoggingFromToStringTest extends QueueFullAbstractTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, "128"); // minimum ringbuffer size + } + + @AfterClass + public static void afterClass() { + System.clearProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule( + "log4j2-queueFull.xml", AsyncLoggerContextSelector.class); + + @Before + public void before() throws Exception { + blockingAppender = context.getRequiredAppender("Blocking", BlockingAppender.class); + } + + @Test(timeout = 50000) + public void testLoggingFromToStringCausesOutOfOrderMessages() { + final Logger logger = context.getLogger(this.getClass()); + + blockingAppender.countDownLatch = new CountDownLatch(1); + unlocker = new Unlocker(new CountDownLatch(129)); + unlocker.start(); + + asyncLoggerRecursiveTest(logger, unlocker, blockingAppender, this); + } + + static void asyncLoggerRecursiveTest(final Logger logger, + final Unlocker unlocker, + final BlockingAppender blockingAppender, + final QueueFullAbstractTest factory) { + for (int i = 0; i < 1; i++) { + TRACE("Test logging message " + i + ". Remaining capacity=" + asyncRemainingCapacity(logger)); + TRACE("Test decrementing unlocker countdown latch. Count=" + unlocker.countDownLatch.getCount()); + unlocker.countDownLatch.countDown(); + final DomainObject obj = factory.new DomainObject(129); + logger.info(new ParameterizedMessage("logging naughty object #{} {}", i, obj)); + } + TRACE("Before stop() blockingAppender.logEvents.count=" + blockingAppender.logEvents.size()); + CoreLoggerContexts.stopLoggerContext(false); // stop async thread + while (blockingAppender.logEvents.size() < 129) { Thread.yield(); } + TRACE("After stop() blockingAppender.logEvents.count=" + blockingAppender.logEvents.size()); + + final Stack actual = transform(blockingAppender.logEvents); + assertEquals("Jumped the queue: test(2)+domain1(65)+domain2(61)=128: queue full", + "Logging in toString() #127", actual.pop()); + assertEquals("Logging in toString() #128", actual.pop()); + assertEquals("logging naughty object #0 Who's bad?!", actual.pop()); + + for (int i = 0; i < 127; i++) { + assertEquals("First batch", "Logging in toString() #" + i, actual.pop()); + } + assertTrue(actual.isEmpty()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java index 16918382aed..2302f0c995f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java @@ -19,15 +19,12 @@ import java.util.Stack; import java.util.concurrent.CountDownLatch; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.apache.logging.log4j.core.CoreLoggerContexts; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.util.Strings; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -38,7 +35,8 @@ import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Tests queue full scenarios with pure AsyncLoggers (all loggers async). @@ -50,17 +48,14 @@ public class QueueFullAsyncLoggerLoggingFromToStringTest2 extends QueueFullAbstr @BeforeClass public static void beforeClass() { - //FORMAT_MESSAGES_IN_BACKGROUND - System.setProperty("log4j.format.msg.async", "true"); - - System.setProperty("AsyncLogger.RingBufferSize", "128"); // minimum ringbuffer size - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, - "log4j2-queueFull.xml"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND, "true"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, "128"); // minimum ringbuffer size } @AfterClass public static void afterClass() { - System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + System.clearProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND); + System.clearProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE); } @Rule @@ -75,8 +70,8 @@ public void before() throws Exception { @Ignore @Test(timeout = 5000) - public void testLoggingFromToStringCausesOutOfOrderMessages() throws InterruptedException { - final Logger logger = LogManager.getLogger(this.getClass()); + public void testLoggingFromToStringCausesOutOfOrderMessages() { + final Logger logger = context.getLogger(this.getClass()); blockingAppender.countDownLatch = new CountDownLatch(1); unlocker = new Unlocker(new CountDownLatch(129)); @@ -105,4 +100,4 @@ public void testLoggingFromToStringCausesOutOfOrderMessages() throws Interrupted } assertTrue(actual.isEmpty()); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java index e5ba78df37e..d09f72f3f33 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java @@ -19,14 +19,11 @@ import java.util.Stack; import java.util.concurrent.CountDownLatch; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.categories.AsyncLoggers; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.util.Strings; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -36,7 +33,8 @@ import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Tests queue full scenarios with pure AsyncLoggers (all loggers async). @@ -47,14 +45,12 @@ public class QueueFullAsyncLoggerTest extends QueueFullAbstractTest { @BeforeClass public static void beforeClass() { - System.setProperty("AsyncLogger.RingBufferSize", "128"); // minimum ringbuffer size - System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, - "log4j2-queueFull.xml"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, "128"); // minimum ringbuffer size } @AfterClass public static void afterClass() { - System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY); + System.clearProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE); } @Rule @@ -68,8 +64,8 @@ public void before() throws Exception { @Test(timeout = 5000) - public void testNormalQueueFullKeepsMessagesInOrder() throws InterruptedException { - final Logger logger = LogManager.getLogger(this.getClass()); + public void testNormalQueueFullKeepsMessagesInOrder() { + final Logger logger = context.getLogger(this.getClass()); blockingAppender.countDownLatch = new CountDownLatch(1); unlocker = new Unlocker(new CountDownLatch(129)); @@ -99,4 +95,4 @@ static void asyncLoggerTest(final Logger logger, } assertTrue(actual.isEmpty()); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java new file mode 100644 index 00000000000..86ab23965a7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.concurrent.CountDownLatch; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * Tests queue full scenarios with pure AsyncLoggers (all loggers async). + */ +@RunWith(BlockJUnit4ClassRunner.class) +@Category(AsyncLoggers.class) +public class QueueFullAsyncLoggerTest2 extends QueueFullAbstractTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND, "true"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, "128"); // minimum ringbuffer size + } + + @AfterClass + public static void afterClass() { + System.clearProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND); + System.clearProperty(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule( + "log4j2-queueFull.xml", AsyncLoggerContextSelector.class); + + @Before + public void before() throws Exception { + blockingAppender = context.getRequiredAppender("Blocking", BlockingAppender.class); + } + + + @Test(timeout = 5000) + public void testNormalQueueFullKeepsMessagesInOrder() { + final Logger logger = context.getLogger(this.getClass()); + + blockingAppender.countDownLatch = new CountDownLatch(1); + unlocker = new Unlocker(new CountDownLatch(129)); + unlocker.start(); + + QueueFullAsyncLoggerTest.asyncLoggerTest(logger, unlocker, blockingAppender); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java new file mode 100644 index 00000000000..baf7212a7ac --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.GarbageCollectionHelper; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.categories.AsyncLoggers; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.apache.logging.log4j.message.Message; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import static org.junit.Assert.assertTrue; + +/** + * Tests queue full scenarios with pure AsyncLoggers (all loggers async). + */ +@RunWith(BlockJUnit4ClassRunner.class) +@Category(AsyncLoggers.class) +public class QueueFullAsyncLoggerTest3 extends QueueFullAbstractTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND, "true"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, "discard"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, "128"); // minimum ringbuffer size + } + + @AfterClass + public static void afterClass() { + System.clearProperty(Log4jProperties.ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND); + System.clearProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY); + System.clearProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule( + "log4j2-queueFull.xml", AsyncLoggerContextSelector.class); + + @Before + public void before() throws Exception { + blockingAppender = context.getRequiredAppender("Blocking", BlockingAppender.class); + } + + + @Test(timeout = 15000) + public void discardedMessagesShouldBeGarbageCollected() throws InterruptedException { + final Logger logger = context.getLogger(this.getClass()); + + blockingAppender.logEvents = null; + blockingAppender.countDownLatch = new CountDownLatch(1); + int count = 200; + CountDownLatch garbageCollectionLatch = new CountDownLatch(count); + for (int i = 0; i < count; i++) { + logger.info(new CountdownOnGarbageCollectMessage(garbageCollectionLatch)); + } + blockingAppender.countDownLatch.countDown(); + + try (GarbageCollectionHelper gcHelper = new GarbageCollectionHelper()) { + gcHelper.run(); + assertTrue("Parameter should have been garbage collected", garbageCollectionLatch.await(30, TimeUnit.SECONDS)); + } + } + + private static final class CountdownOnGarbageCollectMessage implements Message { + + private final CountDownLatch latch; + + CountdownOnGarbageCollectMessage(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public String getFormattedMessage() { + return "formatted"; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public Object[] getParameters() { + return new Object[0]; + } + + @Override + public Throwable getThrowable() { + return null; + } + + @Override + protected void finalize() throws Throwable { + latch.countDown(); + super.finalize(); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java new file mode 100644 index 00000000000..2971d1ebb43 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java @@ -0,0 +1,283 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext.ContextStack; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.core.time.internal.DummyNanoClock; +import org.apache.logging.log4j.core.time.internal.FixedPreciseClock; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ReusableMessageFactory; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.MutableThreadContextStack; +import org.apache.logging.log4j.util.FilteredObjectInputStream; +import org.apache.logging.log4j.util.StringMap; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the RingBufferLogEvent class. + */ +@Tag("async") +public class RingBufferLogEventTest { + + @Test + public void testToImmutable() { + final LogEvent logEvent = new RingBufferLogEvent(); + assertNotSame(logEvent, logEvent.toImmutable()); + } + + /** + * @see LOG4J2-2816 + */ + @Test + public void testIsPopulated() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + + assertFalse(evt.isPopulated()); + + final String loggerName = null; + final Marker marker = null; + final String fqcn = null; + final Level level = null; + final Message data = null; + final Throwable t = null; + final ContextStack contextStack = null; + final String threadName = null; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, data, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, new FixedPreciseClock(), new DummyNanoClock(1)); + + assertTrue(evt.isPopulated()); + + evt.clear(); + + assertFalse(evt.isPopulated()); + } + + @Test + public void testGetLevelReturnsOffIfNullLevelSet() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + final String loggerName = null; + final Marker marker = null; + final String fqcn = null; + final Level level = null; + final Message data = null; + final Throwable t = null; + final ContextStack contextStack = null; + final String threadName = null; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, data, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, new FixedPreciseClock(), new DummyNanoClock(1)); + assertEquals(Level.OFF, evt.getLevel()); + } + + @Test + public void testGetMessageReturnsNonNullMessage() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + final String loggerName = null; + final Marker marker = null; + final String fqcn = null; + final Level level = null; + final Message data = null; + final Throwable t = null; + final ContextStack contextStack = null; + final String threadName = null; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, data, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, new FixedPreciseClock(), new DummyNanoClock(1)); + assertNotNull(evt.getMessage()); + } + + @Test + public void testGetMillisReturnsConstructorMillisForNormalMessage() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + final String loggerName = null; + final Marker marker = null; + final String fqcn = null; + final Level level = null; + final Message data = null; + final Throwable t = null; + final ContextStack contextStack = null; + final String threadName = null; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, data, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, new FixedPreciseClock(123, 456), new DummyNanoClock(1)); + assertEquals(123, evt.getTimeMillis()); + assertEquals(456, evt.getInstant().getNanoOfMillisecond()); + } + + @SuppressWarnings("BanSerializableRead") + @Test + public void testSerializationDeserialization() throws IOException, ClassNotFoundException { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + final String loggerName = "logger.name"; + final Marker marker = null; + final String fqcn = "f.q.c.n"; + final Level level = Level.TRACE; + final Message data = new SimpleMessage("message"); + final Throwable t = new InternalError("not a real error"); + final ContextStack contextStack = null; + final String threadName = "main"; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, data, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, + new FixedPreciseClock(12345, 678), new DummyNanoClock(1)); + ((StringMap) evt.getContextData()).putValue("key", "value"); + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(evt); + + final ObjectInputStream in = new FilteredObjectInputStream(new ByteArrayInputStream(baos.toByteArray())); + final RingBufferLogEvent other = (RingBufferLogEvent) in.readObject(); + assertEquals(loggerName, other.getLoggerName()); + assertEquals(marker, other.getMarker()); + assertEquals(fqcn, other.getLoggerFqcn()); + assertEquals(level, other.getLevel()); + assertEquals(data, other.getMessage()); + assertNull(other.getThrown(), "null after serialization"); + assertEquals(new ThrowableProxy(t), other.getThrownProxy()); + assertEquals(evt.getContextData(), other.getContextData()); + assertEquals(contextStack, other.getContextStack()); + assertEquals(threadName, other.getThreadName()); + assertEquals(location, other.getSource()); + assertEquals(12345, other.getTimeMillis()); + assertEquals(678, other.getInstant().getNanoOfMillisecond()); + } + + @SuppressWarnings("deprecation") + @Test + public void testCreateMementoReturnsCopy() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + final String loggerName = "logger.name"; + final Marker marker = MarkerManager.getMarker("marked man"); + final String fqcn = "f.q.c.n"; + final Level level = Level.TRACE; + final Message data = new SimpleMessage("message"); + final Throwable t = new InternalError("not a real error"); + final ContextStack contextStack = new MutableThreadContextStack(Arrays.asList("a", "b")); + final String threadName = "main"; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, data, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, new FixedPreciseClock(12345, 678), new DummyNanoClock(1)); + ((StringMap) evt.getContextData()).putValue("key", "value"); + + final LogEvent actual = evt.createMemento(); + assertEquals(evt.getLoggerName(), actual.getLoggerName()); + assertEquals(evt.getMarker(), actual.getMarker()); + assertEquals(evt.getLoggerFqcn(), actual.getLoggerFqcn()); + assertEquals(evt.getLevel(), actual.getLevel()); + assertEquals(evt.getMessage(), actual.getMessage()); + assertEquals(evt.getThrown(), actual.getThrown()); + assertEquals(evt.getContextData(), actual.getContextData()); + assertEquals(evt.getContextStack(), actual.getContextStack()); + assertEquals(evt.getThreadName(), actual.getThreadName()); + assertEquals(evt.getTimeMillis(), actual.getTimeMillis()); + assertEquals(evt.getInstant().getNanoOfMillisecond(), actual.getInstant().getNanoOfMillisecond()); + assertEquals(evt.getSource(), actual.getSource()); + assertEquals(evt.getThrownProxy(), actual.getThrownProxy()); + } + + @Test + public void testCreateMementoRetainsParametersAndFormat() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + // Initialize the event with parameters + evt.swapParameters(new Object[10]); + final String loggerName = "logger.name"; + final Marker marker = MarkerManager.getMarker("marked man"); + final String fqcn = "f.q.c.n"; + final Level level = Level.TRACE; + final ReusableMessageFactory factory = new ReusableMessageFactory(); + final Message message = factory.newMessage("Hello {}!", "World"); + try { + final Throwable t = new InternalError("not a real error"); + final ContextStack contextStack = new MutableThreadContextStack(Arrays.asList("a", "b")); + final String threadName = "main"; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, message, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, new FixedPreciseClock(12345, 678), new DummyNanoClock(1)); + ((StringMap) evt.getContextData()).putValue("key", "value"); + + final Message actual = evt.createMemento().getMessage(); + assertEquals("Hello {}!", actual.getFormat()); + assertArrayEquals(new String[]{"World"}, actual.getParameters()); + assertEquals("Hello World!", actual.getFormattedMessage()); + } finally { + ReusableMessageFactory.release(message); + } + } + + @Test + public void testMementoReuse() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + // Initialize the event with parameters + evt.swapParameters(new Object[10]); + final String loggerName = "logger.name"; + final Marker marker = MarkerManager.getMarker("marked man"); + final String fqcn = "f.q.c.n"; + final Level level = Level.TRACE; + final ReusableMessageFactory factory = new ReusableMessageFactory(); + final Message message = factory.newMessage("Hello {}!", "World"); + try { + final Throwable t = new InternalError("not a real error"); + final ContextStack contextStack = new MutableThreadContextStack(Arrays.asList("a", "b")); + final String threadName = "main"; + final StackTraceElement location = null; + evt.setValues(null, loggerName, marker, fqcn, level, message, t, (StringMap) evt.getContextData(), + contextStack, -1, threadName, -1, location, new FixedPreciseClock(12345, 678), new DummyNanoClock(1)); + ((StringMap) evt.getContextData()).putValue("key", "value"); + + final Message memento1 = evt.memento(); + final Message memento2 = evt.memento(); + assertThat(memento1, sameInstance(memento2)); + } finally { + ReusableMessageFactory.release(message); + } + } + + @Test + public void testMessageTextNeverThrowsNpe() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + try { + evt.getFormattedMessage(); + } catch (final NullPointerException e) { + fail("the messageText field was not set"); + } + } + + @Test + public void testForEachParameterNothingSet() { + final RingBufferLogEvent evt = new RingBufferLogEvent(); + evt.forEachParameter((parameter, parameterIndex, state) -> fail("Should not have been called"), null); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java index 82d606597f4..7ba94128598 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/AbstractRunQueue.java @@ -21,8 +21,6 @@ import org.apache.logging.log4j.core.async.perftest.ResponseTimeTest.PrintingAsyncQueueFullPolicy; -import com.lmax.disruptor.collections.Histogram; - public abstract class AbstractRunQueue implements IPerfTestRunner { abstract BlockingQueue createQueue(int capacity); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/Histogram.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/Histogram.java new file mode 100644 index 00000000000..bf0823bbf22 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/Histogram.java @@ -0,0 +1,390 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.core.async.perftest; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Arrays; + +/** + *

Histogram for tracking the frequency of observations of values below interval upper bounds.

+ * + *

This class is useful for recording timings across a large number of observations + * when high performance is required.

+ * + *

The interval bounds are used to define the ranges of the histogram buckets. If provided bounds + * are [10,20,30,40,50] then there will be five buckets, accessible by index 0-4. Any value + * 0-10 will fall into the first interval bar, values 11-20 will fall into the + * second bar, and so on.

+ */ +@Deprecated +public final class Histogram +{ + // tracks the upper intervals of each of the buckets/bars + private final long[] upperBounds; + // tracks the count of the corresponding bucket + private final long[] counts; + // minimum value so far observed + private long minValue = Long.MAX_VALUE; + // maximum value so far observed + private long maxValue = 0L; + + /** + * Create a new Histogram with a provided list of interval bounds. + * + * @param upperBounds of the intervals. Bounds must be provided in order least to greatest, and + * lowest bound must be greater than or equal to 1. + * @throws IllegalArgumentException if any of the upper bounds are less than or equal to zero + * @throws IllegalArgumentException if the bounds are not in order, least to greatest + */ + public Histogram(final long[] upperBounds) + { + validateBounds(upperBounds); + + this.upperBounds = Arrays.copyOf(upperBounds, upperBounds.length); + this.counts = new long[upperBounds.length]; + } + + /** + * Validates the input bounds; used by constructor only. + */ + private void validateBounds(final long[] upperBounds) + { + long lastBound = -1L; + if (upperBounds.length <= 0) + { + throw new IllegalArgumentException("Must provide at least one interval"); + } + for (final long bound : upperBounds) + { + if (bound <= 0L) + { + throw new IllegalArgumentException("Bounds must be positive values"); + } + + if (bound <= lastBound) + { + throw new IllegalArgumentException("bound " + bound + " is not greater than " + lastBound); + } + + lastBound = bound; + } + } + + /** + * Size of the list of interval bars (ie: count of interval bars). + * + * @return size of the interval bar list. + */ + public int getSize() + { + return upperBounds.length; + } + + /** + * Get the upper bound of an interval for an index. + * + * @param index of the upper bound. + * @return the interval upper bound for the index. + */ + public long getUpperBoundAt(final int index) + { + return upperBounds[index]; + } + + /** + * Get the count of observations at a given index. + * + * @param index of the observations counter. + * @return the count of observations at a given index. + */ + public long getCountAt(final int index) + { + return counts[index]; + } + + /** + * Add an observation to the histogram and increment the counter for the interval it matches. + * + * @param value for the observation to be added. + * @return return true if in the range of intervals and successfully added observation; otherwise false. + */ + public boolean addObservation(final long value) + { + int low = 0; + int high = upperBounds.length - 1; + + // do a classic binary search to find the high value + while (low < high) + { + int mid = low + ((high - low) >> 1); + if (upperBounds[mid] < value) + { + low = mid + 1; + } + else + { + high = mid; + } + } + + // if the binary search found an eligible bucket, increment + if (value <= upperBounds[high]) + { + counts[high]++; + trackRange(value); + + return true; + } + + // otherwise value was not found + return false; + } + + /** + * Track minimum and maximum observations + */ + private void trackRange(final long value) + { + if (value < minValue) + { + minValue = value; + } + + if (value > maxValue) + { + maxValue = value; + } + } + + /** + *

Add observations from another Histogram into this one.

+ * + *

Histograms must have the same intervals.

+ * + * @param histogram from which to add the observation counts. + * @throws IllegalArgumentException if interval count or values do not match exactly + */ + public void addObservations(final Histogram histogram) + { + // validate the intervals + if (upperBounds.length != histogram.upperBounds.length) + { + throw new IllegalArgumentException("Histograms must have matching intervals"); + } + + for (int i = 0, size = upperBounds.length; i < size; i++) + { + if (upperBounds[i] != histogram.upperBounds[i]) + { + throw new IllegalArgumentException("Histograms must have matching intervals"); + } + } + + // increment all of the internal counts + for (int i = 0, size = counts.length; i < size; i++) + { + counts[i] += histogram.counts[i]; + } + + // refresh the minimum and maximum observation ranges + trackRange(histogram.minValue); + trackRange(histogram.maxValue); + } + + /** + * Clear the list of interval counters + */ + public void clear() + { + maxValue = 0L; + minValue = Long.MAX_VALUE; + + for (int i = 0, size = counts.length; i < size; i++) + { + counts[i] = 0L; + } + } + + /** + * Count total number of recorded observations. + * + * @return the total number of recorded observations. + */ + public long getCount() + { + long count = 0L; + + for (int i = 0, size = counts.length; i < size; i++) + { + count += counts[i]; + } + + return count; + } + + /** + * Get the minimum observed value. + * + * @return the minimum value observed. + */ + public long getMin() + { + return minValue; + } + + /** + * Get the maximum observed value. + * + * @return the maximum of the observed values; + */ + public long getMax() + { + return maxValue; + } + + /** + *

Calculate the mean of all recorded observations.

+ * + *

The mean is calculated by summing the mid points of each interval multiplied by the count + * for that interval, then dividing by the total count of observations. The max and min are + * considered for adjusting the top and bottom bin when calculating the mid point, this + * minimises skew if the observed values are very far away from the possible histogram values.

+ * + * @return the mean of all recorded observations. + */ + public BigDecimal getMean() + { + // early exit to avoid divide by zero later + if (0L == getCount()) + { + return BigDecimal.ZERO; + } + + // precalculate the initial lower bound; needed in the loop + long lowerBound = counts[0] > 0L ? minValue : 0L; + // use BigDecimal to avoid precision errors + BigDecimal total = BigDecimal.ZERO; + + // midpoint is calculated as the average between the lower and upper bound + // (after taking into account the min & max values seen) + // then, simply multiply midpoint by the count of values at the interval (intervalTotal) + // and add to running total (total) + for (int i = 0, size = upperBounds.length; i < size; i++) + { + if (0L != counts[i]) + { + long upperBound = Math.min(upperBounds[i], maxValue); + long midPoint = lowerBound + ((upperBound - lowerBound) / 2L); + + BigDecimal intervalTotal = new BigDecimal(midPoint).multiply(new BigDecimal(counts[i])); + total = total.add(intervalTotal); + } + + // and recalculate the lower bound for the next time around the loop + lowerBound = Math.max(upperBounds[i] + 1L, minValue); + } + + return total.divide(new BigDecimal(getCount()), 2, RoundingMode.HALF_UP); + } + + /** + * Calculate the upper bound within which 99% of observations fall. + * + * @return the upper bound for 99% of observations. + */ + public long getTwoNinesUpperBound() + { + return getUpperBoundForFactor(0.99d); + } + + /** + * Calculate the upper bound within which 99.99% of observations fall. + * + * @return the upper bound for 99.99% of observations. + */ + public long getFourNinesUpperBound() + { + return getUpperBoundForFactor(0.9999d); + } + + /** + *

Get the interval upper bound for a given factor of the observation population.

+ * + *

Note this does not get the actual percentile measurement, it only gets the bucket

+ * + * @param factor representing the size of the population. + * @return the interval upper bound. + * @throws IllegalArgumentException if factor < 0.0 or factor > 1.0 + */ + public long getUpperBoundForFactor(final double factor) + { + if (0.0d >= factor || factor >= 1.0d) + { + throw new IllegalArgumentException("factor must be >= 0.0 and <= 1.0"); + } + + final long totalCount = getCount(); + final long tailTotal = totalCount - Math.round(totalCount * factor); + long tailCount = 0L; + + // reverse search the intervals ('tailCount' from end) + for (int i = counts.length - 1; i >= 0; i--) + { + if (0L != counts[i]) + { + tailCount += counts[i]; + if (tailCount >= tailTotal) + { + return upperBounds[i]; + } + } + } + + return 0L; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Histogram{"); + + sb.append("min=").append(getMin()).append(", "); + sb.append("max=").append(getMax()).append(", "); + sb.append("mean=").append(getMean()).append(", "); + sb.append("99%=").append(getTwoNinesUpperBound()).append(", "); + sb.append("99.99%=").append(getFourNinesUpperBound()).append(", "); + + sb.append('['); + for (int i = 0, size = counts.length; i < size; i++) + { + sb.append(upperBounds[i]).append('=').append(counts[i]).append(", "); + } + + if (counts.length > 0) + { + sb.setLength(sb.length() - 2); + } + sb.append(']'); + + sb.append('}'); + + return sb.toString(); + } +} + diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java index 62bec14a2fd..bdcfcde083c 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/IPerfTestRunner.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core.async.perftest; -import com.lmax.disruptor.collections.Histogram; - public interface IPerfTestRunner { String LINE100 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-=^~|\\@`[]{};:+*,.<>/?_123456"; String THROUGHPUT_MSG = LINE100 + LINE100 + LINE100 + LINE100 diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java index 65b97df77d6..f73e3313b8e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/IdleStrategy.java @@ -21,7 +21,7 @@ * *

Note regarding potential for TTSP(Time To Safe Point) issues

* - * If the caller spins in a 'counted' loop, and the implementation does not include a a safepoint poll this may cause a TTSP + * If the caller spins in a 'counted' loop, and the implementation does not include a safepoint poll this may cause a TTSP * (Time To SafePoint) problem. If this is the case for your application you can solve it by preventing the idle method from * being inlined by using a Hotspot compiler command as a JVM argument e.g: * -XX:CompileCommand=dontinline,org.apache.logging.log4j.core.async.perftest.NoOpIdleStrategy::idle diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java index 001bc642f81..77f3fc193d0 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java @@ -19,8 +19,6 @@ import java.io.File; import java.util.concurrent.TimeUnit; -import com.lmax.disruptor.collections.Histogram; - public class MultiThreadPerfTest extends PerfTest { public static void main(final String[] args) throws Exception { @@ -109,4 +107,4 @@ public void run() { PerfTest.reportResult(resultFile, name, histogram); } } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/NoOpIdleStrategy.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java similarity index 99% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java index 9b34955cfd5..4161a0fc217 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTest.java @@ -24,8 +24,6 @@ import org.apache.logging.log4j.core.util.Loader; -import com.lmax.disruptor.collections.Histogram; - /** * Single-threaded performance test. Usually invoked from PerfTestDriver as part of a series of tests. *

diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java index 2be64999111..396dc1df8cc 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestDriver.java @@ -29,6 +29,7 @@ import java.util.List; import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; +import org.apache.logging.log4j.core.impl.Log4jProperties; /** * Runs a sequence of performance tests. @@ -36,7 +37,7 @@ public class PerfTestDriver { private static final String DEFAULT_WAIT_STRATEGY = "Block"; - static enum WaitStrategy { + enum WaitStrategy { Sleep, Yield, Block; public static WaitStrategy get() { @@ -86,17 +87,17 @@ List processArguments(final String java) { // args.add("-XX:+PrintGCApplicationConcurrentTime"); // args.add("-XX:+PrintSafepointStatistics"); - args.add("-Dlog4j.configuration=" + log4jConfig); // log4j 1.2 - args.add("-Dlog4j.configurationFile=" + log4jConfig); // log4j 2 + args.add("-D" + Log4jProperties.CONFIG_V1_FILE_NAME + '=' + log4jConfig); // 1.2 + args.add("-D" + Log4jProperties.CONFIG_LOCATION + '=' + log4jConfig); // 2.x args.add("-Dlogback.configurationFile=" + log4jConfig);// logback final int ringBufferSize = getUserSpecifiedRingBufferSize(); if (ringBufferSize >= 128) { - args.add("-DAsyncLoggerConfig.RingBufferSize=" + ringBufferSize); - args.add("-DAsyncLogger.RingBufferSize=" + ringBufferSize); + args.add("-D" + Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE + '=' + ringBufferSize); + args.add("-D" + Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE + '=' + ringBufferSize); } - args.add("-DAsyncLoggerConfig.WaitStrategy=" + wait); - args.add("-DAsyncLogger.WaitStrategy=" + wait); + args.add("-D" + Log4jProperties.ASYNC_CONFIG_WAIT_STRATEGY + '=' + wait); + args.add("-D" + Log4jProperties.ASYNC_LOGGER_WAIT_STRATEGY + '=' + wait); if (systemProperties != null) { Collections.addAll(args, systemProperties); } @@ -192,24 +193,24 @@ public String toString() { } } - static enum Runner { + enum Runner { Log4j12(RunLog4j1.class), // Log4j2(RunLog4j2.class), // Logback(RunLogback.class); private final Class implementationClass; - private Runner(final Class cls) { + Runner(final Class cls) { this.implementationClass = cls; } } public static void main(final String[] args) throws Exception { final long start = System.nanoTime(); - + final List tests = selectTests(); runPerfTests(args, tests); - + System.out.printf("Done. Total duration: %.1f minutes%n", (System.nanoTime() - start) / (60.0 * 1000.0 * 1000.0 * 1000.0)); @@ -218,13 +219,13 @@ public static void main(final String[] args) throws Exception { private static List selectTests() throws IOException { final List tests = new ArrayList<>(); - + // final String CACHEDCLOCK = "-Dlog4j.Clock=CachedClock"; - final String SYSCLOCK = "-Dlog4j.Clock=SystemClock"; - final String ALL_ASYNC = "-DLog4jContextSelector=" + AsyncLoggerContextSelector.class.getName(); + final String SYSCLOCK = asArgument(Log4jProperties.CONFIG_CLOCK, "SystemClock"); + final String ALL_ASYNC = asArgument(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, AsyncLoggerContextSelector.class.getName()); - final String THREADNAME = "-DAsyncLogger.ThreadNameStrategy=" // - + System.getProperty("AsyncLogger.ThreadNameStrategy", "CACHED"); + final String THREADNAME = asArgument(Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, + System.getProperty(Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, "CACHED")); // includeLocation=false add(tests, 1, "perf3PlainNoLoc.xml", Runner.Log4j2, "Loggers all async", ALL_ASYNC, SYSCLOCK, THREADNAME); @@ -363,4 +364,8 @@ public void run() { t.start(); return t; } + + private static String asArgument(final String key, final String value) { + return String.format("-D%s=%s", key, value); + } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java index 43069c68978..71021581a3a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/PerfTestResultFormatter.java @@ -151,7 +151,7 @@ private void process(final String line) throws ParseException { private Comparator sort() { return new Comparator() { - List expected = Arrays.asList("1 thread", "2 threads", + final List expected = Arrays.asList("1 thread", "2 threads", "4 threads", "8 threads", "16 threads", "32 threads", "64 threads"); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java index 93f0f2e4002..efeaa4917ee 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java @@ -29,9 +29,12 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.async.DefaultAsyncQueueFullPolicy; import org.apache.logging.log4j.core.async.EventRoute; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.util.Loader; +import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; + /** * Latency test showing both service time and response time. *

Service time = time to perform the desired operation, response time = service time + queueing time.

@@ -105,12 +108,12 @@ public static void main(final String[] args) throws Exception { final String loggerLib = args.length > 2 ? args[2] : "Log4j2"; // print to console if ringbuffer is full - System.setProperty("log4j2.AsyncQueueFullPolicy", PrintingAsyncQueueFullPolicy.class.getName()); - System.setProperty("AsyncLogger.RingBufferSize", String.valueOf(256 * 1024)); - //System.setProperty("Log4jContextSelector", AsyncLoggerContextSelector.class.getName()); - //System.setProperty("log4j.configurationFile", "perf3PlainNoLoc.xml"); - if (System.getProperty("AsyncLogger.WaitStrategy") == null) { - System.setProperty("AsyncLogger.WaitStrategy", "Yield"); + System.setProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY, PrintingAsyncQueueFullPolicy.class.getName()); + System.setProperty(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE, String.valueOf(256 * 1024)); + //System.setProperty(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, AsyncLoggerContextSelector.class.getName()); + //System.setProperty(Log4jProperties.CONFIG_LOCATION, "perf3PlainNoLoc.xml"); + if (System.getProperty(Log4jProperties.ASYNC_LOGGER_WAIT_STRATEGY) == null) { + System.setProperty(Log4jProperties.ASYNC_LOGGER_WAIT_STRATEGY, "Yield"); } //for (Object key : System.getProperties().keySet()) { // System.out.println(key + "=" + System.getProperty((String) key)); @@ -140,7 +143,7 @@ public static void main(final String[] args) throws Exception { runLatencyTest(logger, WARMUP_DURATION_MILLIS, WARMUP_COUNT, loadMessagesPerSec, idleStrategy, warmupServiceTmHistograms, warmupResponseTmHistograms, threadCount); System.out.println("-----------------Warmup done. load=" + loadMessagesPerSec); - if (!Constants.ENABLE_DIRECT_ENCODERS || !Constants.ENABLE_THREADLOCALS) { + if (!Constants.ENABLE_DIRECT_ENCODERS || !isThreadLocalsEnabled()) { //System.gc(); //Thread.sleep(5000); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunConversant.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunJCTools.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java index 6db549e6ecc..aca0444fd1e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j1.java @@ -19,8 +19,6 @@ import org.apache.log4j.LogManager; import org.apache.log4j.Logger; -import com.lmax.disruptor.collections.Histogram; - public class RunLog4j1 implements IPerfTestRunner { final Logger LOGGER = LogManager.getLogger(getClass()); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java index df2a15d5d41..a7b457c8545 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLog4j2.java @@ -18,8 +18,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.CoreLoggerContexts; -import com.lmax.disruptor.collections.Histogram; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; public class RunLog4j2 implements IPerfTestRunner { final Logger LOGGER = LogManager.getLogger(getClass()); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java index aaf22c6239c..b812d7433ae 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/RunLogback.java @@ -21,8 +21,6 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.core.spi.LifeCycle; -import com.lmax.disruptor.collections.Histogram; - public class RunLogback implements IPerfTestRunner { final Logger LOGGER = (Logger) LoggerFactory.getLogger(getClass()); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/YieldIdleStrategy.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/YieldIdleStrategy.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/YieldIdleStrategy.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/perftest/YieldIdleStrategy.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AdvertiserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AdvertiserTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/AdvertiserTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AdvertiserTest.java index ad2ad7c5741..61f7892911a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AdvertiserTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AdvertiserTest.java @@ -16,30 +16,28 @@ */ package org.apache.logging.log4j.core.config; -import java.io.File; -import java.util.Map; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.xml.XmlConfiguration; import org.apache.logging.log4j.status.StatusLogger; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * - */ +@Tag("functional") +@StatusLoggerLevel("OFF") public class AdvertiserTest { private static final String CONFIG = "log4j-advertiser.xml"; - private static final String STATUS_LOG = "target/status.log"; - @BeforeClass + @BeforeAll public static void setupClass() { - final File file = new File(STATUS_LOG); - file.delete(); System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, CONFIG); final LoggerContext ctx = LoggerContext.getContext(); final Configuration config = ctx.getConfiguration(); @@ -53,14 +51,12 @@ public static void setupClass() { } } - @AfterClass + @AfterAll public static void cleanupClass() { System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); final LoggerContext ctx = LoggerContext.getContext(); ctx.reconfigure(); StatusLogger.getLogger().reset(); - final File file = new File(STATUS_LOG); - file.delete(); } private void verifyExpectedEntriesAdvertised(final Map> entries) { @@ -85,10 +81,10 @@ private void verifyExpectedEntriesAdvertised(final Map> entries = InMemoryAdvertiser.getAdvertisedEntries(); - assertTrue("Entries found: " + entries, entries.isEmpty()); + assertTrue(entries.isEmpty(), "Entries found: " + entries); //reconfigure for subsequent testing ctx.start(); @@ -118,7 +114,7 @@ public void testAdvertisementsAddedOnReconfigAfterStop() { ctx.stop(); final Map> entries = InMemoryAdvertiser.getAdvertisedEntries(); - assertTrue("Entries found: " + entries, entries.isEmpty()); + assertTrue(entries.isEmpty(), "Entries found: " + entries); ctx.start(); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java index 58ac9ea4e7d..e75dd24ee46 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java @@ -18,13 +18,13 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.test.appender.FailOnceAppender; -import org.junit.Test; +import org.apache.logging.log4j.core.test.appender.FailOnceAppender; +import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the AppenderControlArraySet class.. @@ -34,11 +34,11 @@ public class AppenderControlArraySetTest { @Test public void testInitiallyEmpty() throws Exception { assertTrue(new AppenderControlArraySet().isEmpty()); - assertTrue(new AppenderControlArraySet().get().length == 0); + assertEquals(0, new AppenderControlArraySet().get().length); } private AppenderControl createControl(final String name) { - final Appender appender = FailOnceAppender.createAppender(name); + final Appender appender = FailOnceAppender.createAppender(name, null); return new AppenderControl(appender, Level.INFO, null); } @@ -174,7 +174,7 @@ public void testClearReturnsAllItems() throws Exception { public void testIsEmptyMeansZeroLengthArray() throws Exception { final AppenderControlArraySet set = new AppenderControlArraySet(); assertTrue(set.isEmpty()); - assertTrue(set.get().length == 0); + assertEquals(0, set.get().length); } @Test @@ -188,4 +188,4 @@ public void testGetReturnsAddedItems() throws Exception { assertEquals(3, set.get().length); assertArrayEquals(controls, set.get()); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationMissingTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationMissingTest.java new file mode 100644 index 00000000000..6522c79cf38 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationMissingTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SetSystemProperty(key = Log4jProperties.CONFIG_LOCATION, value = "classpath:log4j-comp-logger-root.xml,log4j-does-not-exist.json") +public class CompositeConfigurationMissingTest { + + @Test + public void testMissingConfig() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + + final AbstractConfiguration config = (AbstractConfiguration) ctx.getConfiguration(); + assertNotNull(config, "No configuration returned"); + //Test for Root log level override + assertEquals(Level.ERROR, config.getRootLogger().getLevel(), "Expected Root logger log level to be ERROR"); + + //Test for no cat2 level override + final LoggerConfig cat2 = config.getLogger("cat2"); + assertEquals(Level.DEBUG, cat2.getLevel(), "Expected cat2 log level to be INFO"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java index cb7b4977ab9..6d1639af1e7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java @@ -27,7 +27,7 @@ import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.filter.RegexFilter; import org.apache.logging.log4j.core.util.Throwables; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Assert; import org.junit.Test; import org.junit.runner.Description; @@ -115,6 +115,9 @@ public void evaluate() throws Throwable { appendersMap.size()); assertTrue(appendersMap.get("File") instanceof FileAppender); assertTrue(appendersMap.get("STDOUT") instanceof ConsoleAppender); + + assertEquals("Expected COMPOSITE_SOURCE for composite configuration but got " + config.getConfigurationSource(), + config.getConfigurationSource(), ConfigurationSource.COMPOSITE_SOURCE); } }; runTest(lcr, test); @@ -157,13 +160,32 @@ public void evaluate() throws Throwable { //Regression //Check level on cat3 (not present in root config) assertEquals("Expected cat3 log level to be ERROR", Level.ERROR, config.getLogger("cat3").getLevel()); - //Check level on cat1 (not present in overriden config) + //Check level on cat1 (not present in overridden config) assertEquals("Expected cat1 log level to be DEBUG", Level.DEBUG, config.getLogger("cat1").getLevel()); } }; runTest(lcr, test); } + @Test + public void testMissingConfig() { + final LoggerContextRule lcr = new LoggerContextRule("classpath:log4j-comp-logger-root.xml,log4j-does-not-exist.json"); + final Statement test = new Statement() { + @Override + public void evaluate() throws Throwable { + final AbstractConfiguration config = (AbstractConfiguration) lcr.getConfiguration(); + assertNotNull("No configuration returned", config); + //Test for Root log level override + assertEquals("Expected Root logger log level to be ERROR", Level.ERROR, config.getRootLogger().getLevel()); + + //Test for no cat2 level override + final LoggerConfig cat2 = config.getLogger("cat2"); + assertEquals("Expected cat2 log level to be INFO", Level.DEBUG, cat2.getLevel()); + } + }; + runTest(lcr, test); + } + @Test public void testAppenderRefFilterMerge() { final LoggerContextRule lcr = new LoggerContextRule( diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationFactoryTest.java new file mode 100644 index 00000000000..6db5c0f14e7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationFactoryTest.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.filter.ThreadContextMapFilter; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.apache.logging.log4j.util.Unbox.box; +import static org.junit.jupiter.api.Assertions.*; + +@CleanUpFiles({ + "target/test-xml.log", + "target/test-xinclude.log", + "target/test-json.log", + "target/test-yaml.log", + "target/test-properties.log" +}) +class ConfigurationFactoryTest { + + static final String LOGGER_NAME = "org.apache.logging.log4j.test1.Test"; + static final String FILE_LOGGER_NAME = "org.apache.logging.log4j.test2.Test"; + static final String APPENDER_NAME = "STDOUT"; + + /** + * Runs various configuration checks on a configured LoggerContext that should match the equivalent configuration in + * {@code log4j-test1.xml}. + */ + static void checkConfiguration(final LoggerContext context) { + final Configuration configuration = context.getConfiguration(); + final Map appenders = configuration.getAppenders(); + // these used to be separate tests + assertAll(() -> assertNotNull(appenders), + () -> assertEquals(3, appenders.size()), + () -> assertNotNull(configuration.getLoggerContext()), + () -> assertEquals(configuration.getRootLogger(), configuration.getLoggerConfig(Strings.EMPTY)), + () -> assertThrows(NullPointerException.class, () -> configuration.getLoggerConfig(null))); + + final Logger logger = context.getLogger(LOGGER_NAME); + assertEquals(Level.DEBUG, logger.getLevel()); + + assertEquals(1, logger.filterCount()); + final Iterator filterIterator = logger.getFilters(); + assertTrue(filterIterator.hasNext()); + assertTrue(filterIterator.next() instanceof ThreadContextMapFilter); + + final Appender appender = appenders.get(APPENDER_NAME); + assertTrue(appender instanceof ConsoleAppender); + assertEquals(APPENDER_NAME, appender.getName()); + } + + static void checkFileLogger(final LoggerContext context, final Path logFile) throws IOException { + final long currentThreadId = Thread.currentThread().getId(); + final Logger logger = context.getLogger(FILE_LOGGER_NAME); + logger.debug("Greetings from ConfigurationFactoryTest in thread#{}", box(currentThreadId)); + final List lines = Files.readAllLines(logFile); + assertEquals(1, lines.size()); + assertTrue(lines.get(0).endsWith(Long.toString(currentThreadId))); + } + + @Test + @LoggerContextSource("log4j-test1.xml") + void xml(final LoggerContext context) throws IOException { + checkConfiguration(context); + final Path logFile = Paths.get("target", "test-xml.log"); + checkFileLogger(context, logFile); + } + + @Test + @LoggerContextSource("log4j-xinclude.xml") + void xinclude(final LoggerContext context) throws IOException { + checkConfiguration(context); + final Path logFile = Paths.get("target", "test-xinclude.log"); + checkFileLogger(context, logFile); + } + + @Test + @Tag("json") + @LoggerContextSource("log4j-test1.json") + void json(final LoggerContext context) throws IOException { + checkConfiguration(context); + final Path logFile = Paths.get("target", "test-json.log"); + checkFileLogger(context, logFile); + } + + @Test + @Tag("yaml") + @LoggerContextSource("log4j-test1.yaml") + void yaml(final LoggerContext context) throws IOException { + checkConfiguration(context); + final Path logFile = Paths.get("target", "test-yaml.log"); + checkFileLogger(context, logFile); + } + + @Test + @LoggerContextSource("log4j-test1.properties") + void properties(final LoggerContext context) throws IOException { + checkConfiguration(context); + final Path logFile = Paths.get("target", "test-properties.log"); + checkFileLogger(context, logFile); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java new file mode 100644 index 00000000000..9182e0189e7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.logging.log4j.core.net.UrlConnectionFactory; +import org.junit.jupiter.api.Test; + +import com.sun.management.UnixOperatingSystemMXBean; + +public class ConfigurationSourceTest { + + @Test + public void testJira_LOG4J2_2770_byteArray() throws Exception { + ConfigurationSource configurationSource = new ConfigurationSource(new ByteArrayInputStream(new byte[] { 'a', 'b' })); + assertNotNull(configurationSource.resetInputStream()); + } + + /** + * Checks if the usage of 'jar:' URLs does not increase the file descriptor + * count and the jar file can be deleted. + * + * @throws Exception + */ + @Test + public void testNoJarFileLeak() throws Exception { + final Path original = Paths.get("target", "test-classes", "jarfile.jar"); + final Path copy = Paths.get("target", "test-classes", "jarfile-copy.jar"); + Files.copy(original, copy); + final URL jarUrl = new URL("jar:" + copy.toUri().toURL() + "!/config/console.xml"); + final long expected = getOpenFileDescriptorCount(); + UrlConnectionFactory.createConnection(jarUrl).getInputStream().close(); + // This can only fail on UNIX + assertEquals(expected, getOpenFileDescriptorCount()); + // This can only fail on Windows + try { + Files.delete(copy); + } catch (IOException e) { + fail(e); + } + } + + private long getOpenFileDescriptorCount() { + final OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); + if (os instanceof UnixOperatingSystemMXBean) { + return ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount(); + } + return 0L; + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorSetLevelTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorSetLevelTest.java new file mode 100644 index 00000000000..987fb8b744e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorSetLevelTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@Tag("functional") +@LoggerContextSource("log4j-set-level.xml") +public class ConfiguratorSetLevelTest { + + private final ListAppender app1; + private final LoggerContext loggerContext; + private org.apache.logging.log4j.Logger logger1; + + public ConfiguratorSetLevelTest(final LoggerContext context, @Named("LIST1") final ListAppender first) { + this.loggerContext = context; + logger1 = context.getLogger("org.apache.logging"); + app1 = first.clear(); + } + + @Test + public void testSetLevel() { + Logger logger = loggerContext.getLogger(ConfiguratorSetLevelTest.class); + Configurator.setLevel(logger, Level.DEBUG); + LoggerConfig loggerConfig = ((AbstractConfiguration)loggerContext.getConfiguration()) + .getLogger(ConfiguratorSetLevelTest.class.getName()); + assertNotNull(loggerConfig); + assertEquals(Level.DEBUG, loggerConfig.getLevel()); + assertEquals(0, loggerConfig.getAppenderRefs().size()); + logger.trace("Test trace message"); + logger.debug("Test debug message"); + assertEquals(1, app1.getEvents().size()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorTest.java new file mode 100644 index 00000000000..17dae4e4e60 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import java.io.File; +import java.net.URI; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +@Tag("functional") +public class ConfiguratorTest { + + @Test + public void testInitializeFromAbsoluteFilePath() { + final String path = new File("src/test/resources/log4j-list.xml").getAbsolutePath(); + testInitializeFromFilePath(path); + } + + @Test + public void testInitializeFromRelativeFilePath() { + final String path = new File("src/test/resources/log4j-list.xml").toString(); + testInitializeFromFilePath(path); + } + + @Test + public void testReconfigure() { + final String path = new File("src/test/resources/log4j-list.xml").getAbsolutePath(); + try (final LoggerContext loggerContext = Configurator.initialize(getClass().getName(), null, path)) { + assertNotNull(loggerContext.getConfiguration().getAppender("List")); + URI uri = loggerContext.getConfigLocation(); + assertNotNull(uri, "No configuration location returned"); + Configurator.reconfigure(); + assertEquals(uri, loggerContext.getConfigLocation(), "Unexpected configuration location returned"); + } + } + + @Test + public void testReconfigureFromPath() { + final String path = new File("src/test/resources/log4j-list.xml").getAbsolutePath(); + try (final LoggerContext loggerContext = Configurator.initialize(getClass().getName(), null, path)) { + assertNotNull(loggerContext.getConfiguration().getAppender("List")); + URI uri = loggerContext.getConfigLocation(); + assertNotNull(uri, "No configuration location returned"); + final URI location = new File("src/test/resources/log4j2-config.xml").toURI(); + Configurator.reconfigure(location); + assertEquals(location, loggerContext.getConfigLocation(), "Unexpected configuration location returned"); + } + } + + private void testInitializeFromFilePath(final String path) { + try (final LoggerContext loggerContext = Configurator.initialize(getClass().getName(), null, path)) { + assertNotNull(loggerContext.getConfiguration().getAppender("List")); + } + } + + /** + * LOG4J2-3631: Configurator uses getName() instead of getCanonicalName(). + */ + @Test + public void testSetLevelUsesCanonicalName() { + final String path = new File("src/test/resources/log4j-list.xml").getAbsolutePath(); + try (final LoggerContext loggerContext = Configurator.initialize(getClass().getName(), null, path)) { + Configurator.setLevel(Internal.class, Level.DEBUG); + final Configuration config = loggerContext.getConfiguration(); + assertNotNull(config); + final String canonicalName = Internal.class.getCanonicalName(); + assertThat(config.getLoggerConfig(canonicalName)).extracting(LoggerConfig::getName, LoggerConfig::getExplicitLevel) + .containsExactly(canonicalName, Level.DEBUG); + } + } + + private static class Internal { + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java new file mode 100644 index 00000000000..af36b0d36b7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import java.io.IOException; +import java.io.Serializable; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.config.xml.XmlConfiguration; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.status.StatusConsoleListener; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@CleanUpFiles("target/test.log") +@SetSystemProperty(key = "log4j.level", value = "info") +@SetSystemProperty(key = "log.level", value = "info") +public class CustomConfigurationTest { + + public static final Path LOG_FILE = Paths.get("target", "test.log"); + + @Test + @LoggerContextSource("log4j-props.xml") + public void testConfig(final LoggerContext ctx) throws IOException { + // don't bother using "error" since that's the default; try another level + final Configuration config = ctx.getConfiguration(); + assertThat(config, instanceOf(XmlConfiguration.class)); + for (final StatusListener listener : StatusLogger.getLogger().getListeners()) { + if (listener instanceof StatusConsoleListener) { + assertSame(listener.getStatusLevel(), Level.INFO); + break; + } + } + final Layout layout = PatternLayout.newBuilder() + .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .setConfiguration(config) + .build(); + // @formatter:off + final FileAppender appender = FileAppender.newBuilder() + .setFileName(LOG_FILE.toString()) + .setAppend(false) + .setName("File") + .setIgnoreExceptions(false) + .setBufferSize(4000) + .setBufferedIo(false) + .setLayout(layout) + .build(); + // @formatter:on + appender.start(); + config.addAppender(appender); + final AppenderRef ref = AppenderRef.createAppenderRef("File", null, null); + final AppenderRef[] refs = new AppenderRef[] {ref}; + + final LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.INFO, "org.apache.logging.log4j", + "true", refs, null, config, null ); + loggerConfig.addAppender(appender, null, null); + config.addLogger("org.apache.logging.log4j", loggerConfig); + ctx.updateLoggers(); + final Logger logger = ctx.getLogger(CustomConfigurationTest.class); + logger.info("This is a test"); + assertTrue(Files.exists(LOG_FILE)); + assertThat(Files.size(LOG_FILE), greaterThan(0L)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/FileOutputTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/FileOutputTest.java new file mode 100644 index 00000000000..4818c4f2831 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/FileOutputTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + + +import org.apache.logging.log4j.test.junit.CleanUpFiles; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.condition.OS.WINDOWS; + +import org.junit.jupiter.api.condition.DisabledOnOs; + +@DisabledOnOs(WINDOWS) // FIXME: Fix status logger to close files so this will pass on windows. +@CleanUpFiles({"target/status.log", "target/test.log"}) +public class FileOutputTest { + + @Test + @LoggerContextSource("classpath:log4j-filetest.xml") + public void testConfig() throws IOException { + final Path logFile = Paths.get("target", "status.log"); + assertTrue(Files.exists(logFile), "Status output file does not exist"); + assertTrue(Files.size(logFile) > 0, "File is empty"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.java new file mode 100644 index 00000000000..3b2b29db07a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.core.net.Advertiser; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; + +import java.util.HashMap; +import java.util.Map; + +@Configurable(elementType = "advertiser") +@Plugin("memory") +public class InMemoryAdvertiser implements Advertiser { + private static final Map> PROPERTIES = new HashMap<>(); + + public static Map> getAdvertisedEntries() + { + return Map.copyOf(PROPERTIES); + } + + @Override + public Object advertise(final Map newEntry) { + final Object object = new Object(); + PROPERTIES.put(object, new HashMap<>(newEntry)); + return object; + } + + @Override + public void unadvertise(final Object advertisedObject) { + PROPERTIES.remove(advertisedObject); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/JiraLog4j2_2134Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/JiraLog4j2_2134Test.java new file mode 100644 index 00000000000..d677b63903a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/JiraLog4j2_2134Test.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Tag; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +@Tag("yaml") +@LoggerContextSource("log4j2-2134.yml") +public class JiraLog4j2_2134Test { + + @Test + public void testRefresh() { + Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + PatternLayout layout = PatternLayout.newBuilder() + // @formatter:off + .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .setConfiguration(config) + .build(); + // @formatter:on + Appender appender = FileAppender.newBuilder().setFileName("target/test.log").setLayout(layout) + .setConfiguration(config).setBufferSize(4000).setName("File").build(); + // appender.start(); + config.addAppender(appender); + AppenderRef ref = AppenderRef.createAppenderRef("File", null, null); + AppenderRef[] refs = new AppenderRef[] { ref }; + LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.INFO, "testlog4j2refresh", "true", refs, + null, config, null); + loggerConfig.addAppender(appender, null, null); + config.addLogger("testlog4j2refresh", loggerConfig); + ctx.stop(); + ctx.start(config); + + assertDoesNotThrow(() -> log.error("Info message")); + } + + @Test + public void testRefreshMinimalCodeStart() { + Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + ctx.start(config); + + assertDoesNotThrow(() -> log.error("Info message")); + } + + @Test + public void testRefreshMinimalCodeStopStart() { + Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + ctx.stop(); + ctx.start(); + + assertDoesNotThrow(() -> log.error("Info message")); + } + + @Test + public void testRefreshMinimalCodeStopStartConfig() { + Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + ctx.stop(); + ctx.start(config); + + assertDoesNotThrow(() -> log.error("Info message")); + } + + @SuppressWarnings("deprecation") + @Test + public void testRefreshDeprecatedApis() { + Logger log = LogManager.getLogger(this.getClass()); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + PatternLayout layout = PatternLayout.newBuilder() + .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN) + .setPatternSelector(null) + .setConfiguration(config) + .setRegexReplacement(null) + .setCharset(null) + .setAlwaysWriteExceptions(false) + .setNoConsoleNoAnsi(false) + .setHeader(null) + .setFooter(null) + .build(); + // @formatter:off + Appender appender = FileAppender.newBuilder() + .setFileName("target/test.log") + .setAppend(false) + .setLocking(false) + .setName("File") + .setImmediateFlush(true) + .setIgnoreExceptions(false) + .setBufferedIo(false) + .setBufferSize(4000) + .setLayout(layout) + .setAdvertise(false) + .setConfiguration(config) + .build(); + // @formatter:on + appender.start(); + config.addAppender(appender); + AppenderRef ref = AppenderRef.createAppenderRef("File", null, null); + AppenderRef[] refs = new AppenderRef[] { ref }; + LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.INFO, "testlog4j2refresh", "true", refs, + null, config, null); + loggerConfig.addAppender(appender, null, null); + config.addLogger("testlog4j2refresh", loggerConfig); + ctx.stop(); + ctx.start(config); + + assertDoesNotThrow(() -> log.error("Info message")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java new file mode 100644 index 00000000000..db7285f562a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java @@ -0,0 +1,110 @@ +package org.apache.logging.log4j.core.config;/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.impl.Log4jLogEvent.Builder; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for LoggerConfig. + */ +public class LoggerConfigTest { + + private static LoggerConfig createForProperties(final Property[] properties) { + return LoggerConfig.createLogger(true, Level.INFO, "name", "false", new AppenderRef[0], properties, + new NullConfiguration(), null); + } + + @SuppressWarnings({"deprecation"}) + @Test + public void testPropertiesWithoutSubstitution() { + assertNull(createForProperties(null).getPropertyList(), "null propertiesList"); + + final Property[] all = new Property[] { + Property.createProperty("key1", "value1"), + Property.createProperty("key2", "value2"), + }; + final LoggerConfig loggerConfig = createForProperties(all); + final List list = loggerConfig.getPropertyList(); + assertEquals(new HashSet<>(list), + new HashSet<>(loggerConfig.getPropertyList()), "map and list contents equal"); + + final Object[] actualList = new Object[1]; + loggerConfig.setLogEventFactory((loggerName, marker, fqcn, level, data, properties, t) -> { + actualList[0] = properties; + return new Builder().setTimeMillis(System.currentTimeMillis()).build(); + }); + loggerConfig.log("name", "fqcn", null, Level.INFO, new SimpleMessage("msg"), null); + assertSame(list, actualList[0], "propertiesList passed in as is if no substitutions required"); + } + + @Test + public void testPropertiesWithSubstitution() { + final Property[] all = new Property[] { + Property.createProperty("key1", "value1-${sys:user.name}"), + Property.createProperty("key2", "value2-${sys:user.name}"), + }; + final LoggerConfig loggerConfig = createForProperties(all); + final List list = loggerConfig.getPropertyList(); + assertEquals(new HashSet<>(list), + new HashSet<>(loggerConfig.getPropertyList()), "map and list contents equal"); + + final Object[] actualListHolder = new Object[1]; + loggerConfig.setLogEventFactory((loggerName, marker, fqcn, level, data, properties, t) -> { + actualListHolder[0] = properties; + return new Builder().setTimeMillis(System.currentTimeMillis()).build(); + }); + loggerConfig.log("name", "fqcn", null, Level.INFO, new SimpleMessage("msg"), null); + assertNotSame(list, actualListHolder[0], "propertiesList with substitutions"); + + @SuppressWarnings("unchecked") + final List actualList = (List) actualListHolder[0]; + + for (int i = 0; i < list.size(); i++) { + assertEquals(list.get(i).getName(), actualList.get(i).getName(), "name[" + i + "]"); + final String value = list.get(i).getValue().replace("${sys:user.name}", System.getProperty("user.name")); + assertEquals(value, actualList.get(i).getValue(), "value[" + i + "]"); + } + } + + @Test + public void testLevel() { + Configuration configuration = new DefaultConfiguration(); + LoggerConfig config1 = LoggerConfig.newBuilder() + .withLoggerName("org.apache.logging.log4j.test") + .withLevel(Level.ERROR) + .withAdditivity(false) + .withConfig(configuration) + .build(); + LoggerConfig config2 = LoggerConfig.newBuilder() + .withLoggerName("org.apache.logging.log4j") + .withAdditivity(false) + .withConfig(configuration) + .build(); + config1.setParent(config2); + assertEquals(config1.getLevel(), Level.ERROR, "Unexpected Level"); + assertEquals(config1.getExplicitLevel(), Level.ERROR, "Unexpected explicit level"); + assertEquals(config2.getLevel(), Level.ERROR, "Unexpected Level"); + assertNull(config2.getExplicitLevel(),"Unexpected explicit level"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java new file mode 100644 index 00000000000..51082104d57 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests LoggersPlugin. + */ +@StatusLoggerLevel("ERROR") +@LoggerContextSource("multipleRootLoggersTest.xml") +public class LoggersPluginTest { + + @Test + public void testEmptyAttribute() throws Exception { + final Logger logger = LogManager.getLogger(); + logger.info("Test"); + final StatusData data = StatusLogger.getLogger().getStatusData().get(0); + //System.out.println(data.getFormattedStatus()); + + assertThat(data.getLevel()).isEqualTo(Level.ERROR); + assertThat(data.getMessage().getFormattedMessage()).startsWith("Could not configure plugin element Loggers"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/MissingRootLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MissingRootLoggerTest.java similarity index 78% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/MissingRootLoggerTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MissingRootLoggerTest.java index ba711ddb654..c4e69a291f4 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/MissingRootLoggerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MissingRootLoggerTest.java @@ -22,43 +22,37 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; -import static org.apache.logging.log4j.hamcrest.MapMatchers.hasSize; +import static org.apache.logging.log4j.core.test.hamcrest.MapMatchers.hasSize; +import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -@RunWith(JUnit4.class) +@LoggerContextSource("missingRootLogger.xml") public class MissingRootLoggerTest { - @Rule - public LoggerContextRule context = new LoggerContextRule("missingRootLogger.xml"); - @Test - public void testMissingRootLogger() throws Exception { - final LoggerContext ctx = context.getLoggerContext(); + public void testMissingRootLogger(final LoggerContext ctx) throws Exception { final Logger logger = ctx.getLogger("sample.Logger1"); - assertTrue("Logger should have the INFO level enabled", logger.isInfoEnabled()); - assertFalse("Logger should have the DEBUG level disabled", logger.isDebugEnabled()); + assertTrue(logger.isInfoEnabled(), "Logger should have the INFO level enabled"); + assertFalse(logger.isDebugEnabled(), "Logger should have the DEBUG level disabled"); final Configuration config = ctx.getConfiguration(); - assertNotNull("Config not null", config); + assertNotNull(config, "Config not null"); // final String MISSINGROOT = "MissingRootTest"; // assertTrue("Incorrect Configuration. Expected " + MISSINGROOT + " but found " + config.getName(), // MISSINGROOT.equals(config.getName())); final Map map = config.getAppenders(); - assertNotNull("Appenders not null", map); + assertNotNull(map, "Appenders not null"); assertThat("There should only be two appenders", map, hasSize(2)); assertThat(map, hasKey("List")); assertThat(map, hasKey("DefaultConsole-2")); final Map loggerMap = config.getLoggers(); - assertNotNull("loggerMap not null", loggerMap); + assertNotNull(loggerMap, "loggerMap not null"); assertThat("There should only be one configured logger", loggerMap, hasSize(1)); // only the sample logger, no root logger in loggerMap! assertThat("contains key=sample", loggerMap, hasKey("sample")); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MockReliabilityStrategy.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MockReliabilityStrategy.java new file mode 100644 index 00000000000..5e5e87cbdc8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MockReliabilityStrategy.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.Supplier; +import org.opentest4j.MultipleFailuresError; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * Mock object for validating the behavior of a configuration interacting with ReliabilityStrategy. + */ +public class MockReliabilityStrategy implements ReliabilityStrategy { + + private final LoggerConfig config; + private final List errors = Collections.synchronizedList(new ArrayList<>()); + + public MockReliabilityStrategy(final LoggerConfig config) { + this.config = config; + } + + @Override + public void log( + final Supplier reconfigured, final String loggerName, final String fqcn, final Marker marker, + final Level level, final Message data, final Throwable t) { + config.log(loggerName, fqcn, marker, level, data, t); + } + + @Override + public void log( + final Supplier reconfigured, final String loggerName, final String fqcn, + final StackTraceElement location, final Marker marker, final Level level, final Message data, final Throwable t) { + config.log(loggerName, fqcn, location, marker, level, data, t); + } + + @Override + public void log(final Supplier reconfigured, final LogEvent event) { + config.log(event); + } + + @Override + public LoggerConfig getActiveLoggerConfig(final Supplier next) { + return config; + } + + @Override + public void afterLogEvent() { + // no-op + } + + @Override + public void beforeStopAppenders() { + checkState(LifeCycle.State.STOPPED, config); + for (final Appender appender : config.getAppenders().values()) { + checkState(LifeCycle.State.STARTED, appender); + } + } + + @Override + public void beforeStopConfiguration(final Configuration configuration) { + checkState(LifeCycle.State.STOPPING, configuration); + checkState(LifeCycle.State.STARTED, config); + } + + void rethrowAssertionErrors() { + synchronized (errors) { + if (!errors.isEmpty()) { + throw new MultipleFailuresError(null, errors); + } + } + } + + private void checkState(final LifeCycle.State expected, final LifeCycle object) { + try { + assertSame(expected, object.getState(), () -> "Expected state " + expected + " for LifeCycle object " + object); + } catch (final AssertionError e) { + errors.add(e); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MultipleTriggeringPolicyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MultipleTriggeringPolicyTest.java new file mode 100644 index 00000000000..89de53a8cd6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/MultipleTriggeringPolicyTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Tests related to LOG4J2-1100. + */ +class MultipleTriggeringPolicyTest { + @Test + @LoggerContextSource("LOG4J2-1100/log4j2.xml") + void xml(final Configuration configuration) { + assertBothTriggeringPoliciesConfigured(configuration); + } + + @Test + @Tag("json") + @LoggerContextSource("LOG4J2-1100/log4j2.json") + void json(final Configuration configuration) { + assertBothTriggeringPoliciesConfigured(configuration); + } + + @Test + @Tag("yaml") + @LoggerContextSource("LOG4J2-1100/log4j2-good.yaml") + void yaml(final Configuration configuration) { + assertBothTriggeringPoliciesConfigured(configuration); + } + + @Test + @Tag("yaml") + @Disabled("LOG4J2-1100 demonstration") + @LoggerContextSource("LOG4J2-1100/log4j2-good.yaml") + void unsupportedYamlSyntax(final Configuration configuration) { + assertBothTriggeringPoliciesConfigured(configuration); + } + + void assertBothTriggeringPoliciesConfigured(final Configuration configuration) { + final RollingFileAppender appender = configuration.getAppender("File"); + assertNotNull(appender); + final CompositeTriggeringPolicy compositeTriggeringPolicy = appender.getTriggeringPolicy(); + assertNotNull(compositeTriggeringPolicy); + final TriggeringPolicy[] triggeringPolicies = compositeTriggeringPolicy.getTriggeringPolicies(); + assertEquals(2, triggeringPolicies.length); + final SizeBasedTriggeringPolicy sizeBasedTriggeringPolicy; + final TimeBasedTriggeringPolicy timeBasedTriggeringPolicy; + if (triggeringPolicies[0] instanceof SizeBasedTriggeringPolicy) { + sizeBasedTriggeringPolicy = (SizeBasedTriggeringPolicy) triggeringPolicies[0]; + timeBasedTriggeringPolicy = (TimeBasedTriggeringPolicy) triggeringPolicies[1]; + } else { + sizeBasedTriggeringPolicy = (SizeBasedTriggeringPolicy) triggeringPolicies[1]; + timeBasedTriggeringPolicy = (TimeBasedTriggeringPolicy) triggeringPolicies[0]; + } + assertEquals(7, timeBasedTriggeringPolicy.getInterval()); + assertEquals(100 * 1024 * 1024, sizeBasedTriggeringPolicy.getMaxFileSize()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/NestedLoggerConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/NestedLoggerConfigTest.java new file mode 100644 index 00000000000..edec79cb35e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/NestedLoggerConfigTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.xml.XmlConfiguration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for LoggerConfig hierarchies. + */ +@RunWith(Parameterized.class) +public class NestedLoggerConfigTest { + + @Parameterized.Parameters(name = "{0}") + public static List data() throws IOException { + return ImmutableList.of("logger-config/LoggerConfig/", "logger-config/AsyncLoggerConfig/"); + } + + private final String prefix; + + public NestedLoggerConfigTest(String prefix) { + this.prefix = prefix; + } + + @Test + public void testInheritParentDefaultLevel() throws IOException { + Configuration configuration = loadConfiguration(prefix + "default-level.xml"); + try { + assertEquals(Level.ERROR, configuration.getLoggerConfig("com.foo").getLevel()); + } finally { + configuration.stop(); + } + } + + @Test + public void testInheritParentLevel() throws IOException { + Configuration configuration = loadConfiguration(prefix + "inherit-level.xml"); + try { + assertEquals(Level.TRACE, configuration.getLoggerConfig("com.foo").getLevel()); + } finally { + configuration.stop(); + } + } + + private Configuration loadConfiguration(String resourcePath) throws IOException { + try (InputStream in = getClass().getClassLoader().getResourceAsStream(resourcePath)) { + Configuration configuration = new XmlConfiguration(new LoggerContext("test"), new ConfigurationSource(in)); + configuration.initialize(); + configuration.start(); + return configuration; + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java new file mode 100644 index 00000000000..ff08c3b3939 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/PropertyTest.java @@ -0,0 +1,76 @@ +package org.apache.logging.log4j.core.config;/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test for LOG4J2-1313 + * not working + */ +@LoggerContextSource("configPropertyTest.xml") +public class PropertyTest { + + @Test + public void testEmptyAttribute(@Named("List") final ListAppender app) throws Exception { + final org.apache.logging.log4j.Logger logger = LogManager.getLogger(); + logger.info("msg"); + + final List messages = app.getMessages(); + assertNotNull(messages, "No Messages"); + assertEquals(1, messages.size(), "message count" + messages); + + // + // + // + //elementValue + // + // + //elementValue3 + final String expect = "1=elementValue" + // ${sys:elementKey} + ",2=" + // ${sys:emptyElementKey} + ",a=" + // ${sys:emptyAttributeKey} + ",b=" + // ${sys:emptyAttributeKey2} + ",3=attributeValue" + // ${sys:attributeKey} + ",4=attributeValue2" + // ${sys:attributeWithEmptyElementKey} + ",5=elementValue3,m=msg"; // ${sys:bothElementAndAttributeKey} + assertEquals(expect, messages.get(0)); + app.clear(); + } + + @Test + public void testNullValueIsConvertedToEmptyString() { // LOG4J2-1313 support + assertEquals("", Property.createProperty("name", null).getValue()); + } + + @Test + public void testIsValueNeedsLookup() { + assertTrue(Property.createProperty("", "${").isValueNeedsLookup(), "with ${ as value"); + assertTrue(Property.createProperty("", "blah${blah").isValueNeedsLookup(), "with ${ in value"); + assertFalse(Property.createProperty("", "").isValueNeedsLookup(), "empty value"); + assertFalse(Property.createProperty("", "blahblah").isValueNeedsLookup(), "without ${ in value"); + assertFalse(Property.createProperty("", "blahb{sys:lah").isValueNeedsLookup(), "without $ in value"); + assertFalse(Property.createProperty("", "blahb$sys:lah").isValueNeedsLookup(), "without { in value"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java new file mode 100644 index 00000000000..72c7e73d9ee --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReconfigurationDeadlockTest.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.validation.constraints.Required; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Tag; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * Performs reconfiguration whilst logging. + * + * @see LOG4J2-620 + * @see TestAppender + */ +@LoggerContextSource("reconfiguration-deadlock.xml") +@Tag("sleepy") +public class ReconfigurationDeadlockTest { + + private static final int WORKER_COUNT = 100; + + private ExecutorService executor; + + @BeforeEach + public void startExecutor() { + executor = Executors.newFixedThreadPool(WORKER_COUNT); + } + + @AfterEach + public void stopExecutor() throws InterruptedException { + executor.shutdownNow(); + final boolean terminated = executor.awaitTermination(30, TimeUnit.SECONDS); + Assertions.assertTrue(terminated, "couldn't terminate the executor"); + } + + @RepeatedTest(100) + public void reconfiguration_should_not_cause_deadlock_for_ongoing_logging() throws Exception { + + // Try to update the config file to ensure that we can indeed update it. + updateConfigFileModTime(); + + // Start the workers. + final CountDownLatch workerStartLatch = new CountDownLatch(WORKER_COUNT); + List> workerFutures = initiateWorkers(workerStartLatch, executor); + + // Await workers to start and update the config file. + workerStartLatch.await(10, TimeUnit.SECONDS); + updateConfigFileModTime(); + + // Verify that all workers have finished okay. + for (int workerIndex = 0; workerIndex < WORKER_COUNT; workerIndex++) { + final Future workerFuture = workerFutures.get(workerIndex); + try { + Object workerResult = workerFuture.get(30, TimeUnit.SECONDS); + Assertions.assertNull(workerResult); + } catch (final Throwable failure) { + final String message = String.format( + "check for worker %02d/%02d has failed", + (workerIndex + 1), WORKER_COUNT); + throw new AssertionError(message, failure); + } + } + + } + + private static void updateConfigFileModTime() { + final File file = new File("target/test-classes/reconfiguration-deadlock.xml"); + final boolean fileModified = file.setLastModified(System.currentTimeMillis()); + Assertions.assertTrue(fileModified, "couldn't update file modification time"); + } + + @SuppressWarnings("SameParameterValue") + private static List> initiateWorkers( + final CountDownLatch workerStartLatch, + final ExecutorService executor) { + final Logger logger = LogManager.getRootLogger(); + return IntStream + .range(0, WORKER_COUNT) + .mapToObj((final int workerIndex) -> executor.submit(() -> { + int i = 0; + for (; i < 1_000; i++) { + logger.error("worker={}, iteration={}", workerIndex, i); + } + workerStartLatch.countDown(); + for (; i < 5_000; i++) { + logger.error("worker={}, iteration={}", workerIndex, i); + } + })) + .collect(Collectors.toList()); + } + + /** + * A dummy appender doing nothing but burning CPU cycles whilst randomly accessing the logger. + */ + @Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) + @Plugin("ReconfigurationDeadlockTestAppender") + public static final class TestAppender extends AbstractAppender { + + private final Logger logger; + + private TestAppender( + final String name, + final Filter filter, + final Layout layout, + final boolean ignoreExceptions) { + super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); + this.logger = LogManager.getRootLogger(); + } + + @PluginFactory + public static TestAppender createAppender( + @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name, + @PluginAttribute("ignoreExceptions") final boolean ignore, + @PluginElement("Layout") final Layout layout, + @PluginElement("Filter") final Filter filter) { + return new TestAppender(name, filter, layout, ignore); + } + + /** + * Does nothing but burning CPU cycles and accessing to the logger. + */ + @Override + public void append(final LogEvent event) { + boolean endOfBatch; + final int eventHashCode = event.hashCode(); + switch (Math.abs(eventHashCode % 4)) { + case 0: endOfBatch = logger.isTraceEnabled(); break; + case 1: endOfBatch = logger.isDebugEnabled(); break; + case 2: endOfBatch = logger.isInfoEnabled(); break; + case 3: endOfBatch = logger.isWarnEnabled(); break; + default: throw new IllegalStateException(); + } + event.setEndOfBatch(endOfBatch); + } + + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReliabilityStrategyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReliabilityStrategyTest.java new file mode 100644 index 00000000000..e86e66306c5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ReliabilityStrategyTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SetSystemProperty(key = Log4jProperties.CONFIG_RELIABILITY_STRATEGY, value = "org.apache.logging.log4j.core.config.MockReliabilityStrategy") +class ReliabilityStrategyTest { + + @Test + @LoggerContextSource("ReliabilityStrategyTest.xml") + void beforeStopAppendersCalledBeforeAsyncAppendersStopped(@Named final AsyncAppender async, final Configuration config) { + assertTrue(async.isStarted()); + final MockReliabilityStrategy reliabilityStrategy = + (MockReliabilityStrategy) config.getRootLogger().getReliabilityStrategy(); + config.stop(); + assertTrue(async.isStopped()); + reliabilityStrategy.rethrowAssertionErrors(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/TestConfigurator.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/TestConfigurator.java new file mode 100644 index 00000000000..59cf193cfc7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/TestConfigurator.java @@ -0,0 +1,467 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.filter.CompositeFilter; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.Serializable; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.apache.logging.log4j.core.test.hamcrest.MapMatchers.hasSize; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +@Tag("functional") +@StatusLoggerLevel("OFF") +public class TestConfigurator { + + private static final String CONFIG_NAME = "ConfigTest"; + + private static final String FILESEP = System.getProperty("file.separator"); + + + private LoggerContext ctx = null; + + private static final String[] CHARS = new String[] { + "aaaaaaaaaa", + "bbbbbbbbbb", + "cccccccccc", + "dddddddddd", + "eeeeeeeeee", + "ffffffffff", + "gggggggggg", + "hhhhhhhhhh", + "iiiiiiiiii", + "jjjjjjjjjj", + "kkkkkkkkkk", + "llllllllll", + "mmmmmmmmmm", + }; + + @AfterEach + public void cleanup() { + System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); + if (ctx != null) { + Configurator.shutdown(ctx); + ctx = null; + } + } + + @Test + public void testInitialize_Name_PathName() throws Exception { + ctx = Configurator.initialize("Test1", "target/test-classes/log4j2-config.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testInitialize_Name_ClassLoader_URI() throws Exception { + ctx = Configurator.initialize("Test1", null, new File("target/test-classes/log4j2-config.xml").toURI()); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testInitialize_InputStream_File() throws Exception { + final File file = new File("target/test-classes/log4j2-config.xml"); + final InputStream is = new FileInputStream(file); + final ConfigurationSource source = new ConfigurationSource(is, file); + ctx = Configurator.initialize(null, source); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testInitialize_InputStream_Path() throws Exception { + final Path path = Paths.get("target/test-classes/log4j2-config.xml"); + final InputStream is = Files.newInputStream(path); + final ConfigurationSource source = new ConfigurationSource(is, path); + ctx = Configurator.initialize(null, source); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testInitialize_NullClassLoader_ConfigurationSourceWithInputStream_NoId() throws Exception { + final InputStream is = new FileInputStream("target/test-classes/log4j2-config.xml"); + final ConfigurationSource source = + new ConfigurationSource(is); + ctx = Configurator.initialize(null, source); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testInitialize_Name_LocationName() throws Exception { + ctx = Configurator.initialize("Test1", "log4j2-config.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testFromClassPathProperty() throws Exception { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "classpath:log4j2-config.xml"); + ctx = Configurator.initialize("Test1", null); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testFromClassPathWithClassPathPrefix() throws Exception { + ctx = Configurator.initialize("Test1", "classpath:log4j2-config.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Incorrect Configuration."); + } + + @Test + public void testFromClassPathWithClassLoaderPrefix() throws Exception { + ctx = Configurator.initialize("Test1", "classloader:log4j2-config.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Incorrect Configuration."); + } + + @Test + public void testByName() throws Exception { + ctx = Configurator.initialize("-config", null); + LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + @Test + @Tag("sleepy") + public void testReconfiguration() throws Exception { + final File file = new File("target/test-classes/log4j2-config.xml"); + assertTrue(file.setLastModified(System.currentTimeMillis() - 120000), "setLastModified should have succeeded."); + ctx = Configurator.initialize("Test1", "target/test-classes/log4j2-config.xml"); + final Logger logger = LogManager.getLogger("org.apache.test.TestConfigurator"); + Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("Wrong configuration", map, hasKey("List")); + + // Sleep and check + Thread.sleep(50); + if (!file.setLastModified(System.currentTimeMillis())) { + Thread.sleep(500); + } + assertTrue(file.setLastModified(System.currentTimeMillis()), "setLastModified should have succeeded."); + TimeUnit.SECONDS.sleep(config.getWatchManager().getIntervalSeconds()+1); + for (int i = 0; i < 17; ++i) { + logger.debug("Test message " + i); + } + + // Sleep and check + Thread.sleep(50); + if (is(theInstance(config)).matches(ctx.getConfiguration())) { + Thread.sleep(500); + } + final Configuration newConfig = ctx.getConfiguration(); + assertThat("Configuration not reset", newConfig, is(not(theInstance(config)))); + Configurator.shutdown(ctx); + config = ctx.getConfiguration(); + assertEquals(NullConfiguration.NULL_NAME, config.getName(), "Unexpected Configuration."); + } + + + @Test + public void testEnvironment() throws Exception { + ctx = Configurator.initialize("-config", null); + LogManager.getLogger("org.apache.test.TestConfigurator"); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals(CONFIG_NAME, config.getName(), "Incorrect Configuration."); + final Map map = config.getAppenders(); + assertNotNull(map, "Appenders map should not be null."); + assertThat(map, hasSize(greaterThan(0))); + assertThat("No ListAppender named List2", map, hasKey("List2")); + final Appender app = map.get("List2"); + final Layout layout = app.getLayout(); + assertNotNull(layout, "Appender List2 does not have a Layout"); + assertThat("Appender List2 is not configured with a PatternLayout", layout, instanceOf(PatternLayout.class)); + final String pattern = ((PatternLayout) layout).getConversionPattern(); + assertNotNull(pattern, "No conversion pattern for List2 PatternLayout"); + assertFalse(pattern.startsWith("${env:PATH}"), "Environment variable was not substituted"); + } + + @Test + public void testNoLoggers() throws Exception { + ctx = Configurator.initialize("Test1", "bad/log4j-loggers.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + final String name = DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(config.hashCode()); + assertEquals(name, config.getName(), "Unexpected Configuration."); + } + + @Test + public void testBadStatus() throws Exception { + ctx = Configurator.initialize("Test1", "bad/log4j-status.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals("XMLConfigTest", config.getName(), "Unexpected Configuration"); + final LoggerConfig root = config.getLoggerConfig(""); + assertNotNull(root, "No Root Logger"); + assertSame(Level.ERROR, root.getLevel(), "Expected error level, was " + root.getLevel()); + } + + @Test + public void testBadFilterParam() throws Exception { + ctx = Configurator.initialize("Test1", "bad/log4j-badfilterparam.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals("XMLConfigTest", config.getName(), "Unexpected Configuration"); + final LoggerConfig lcfg = config.getLoggerConfig("org.apache.logging.log4j.test1"); + assertNotNull(lcfg, "No Logger"); + final Filter filter = lcfg.getFilter(); + assertNull(filter, "Unexpected Filter"); + } + + @Test + public void testNoFilters() throws Exception { + ctx = Configurator.initialize("Test1", "bad/log4j-nofilter.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals("XMLConfigTest", config.getName(), "Unexpected Configuration"); + final LoggerConfig lcfg = config.getLoggerConfig("org.apache.logging.log4j.test1"); + assertNotNull(lcfg, "No Logger"); + final Filter filter = lcfg.getFilter(); + assertNotNull(filter, "No Filter"); + assertThat(filter, instanceOf(CompositeFilter.class)); + assertTrue(((CompositeFilter) filter).isEmpty(), "Unexpected filters"); + } + + @Test + public void testBadLayout() throws Exception { + ctx = Configurator.initialize("Test1", "bad/log4j-badlayout.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals("XMLConfigTest", config.getName(), "Unexpected Configuration"); + } + + @Test + public void testBadFileName() throws Exception { + final StringBuilder dir = new StringBuilder("/VeryLongDirectoryName"); + + for (final String element : CHARS) { + dir.append(element); + dir.append(element.toUpperCase()); + } + final String value = FILESEP.equals("/") ? dir.toString() + "/test.log" : "1:/target/bad:file.log"; + System.setProperty("testfile", value); + ctx = Configurator.initialize("Test1", "bad/log4j-badfilename.xml"); + LogManager.getLogger("org.apache.test.TestConfigurator"); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals("XMLConfigTest", config.getName(), "Unexpected Configuration"); + assertThat(config.getAppenders(), hasSize(equalTo(2))); + } + + @Test + public void testBuilder() throws Exception { + final ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + builder.setStatusLevel(Level.ERROR); + builder.setConfigurationName("BuilderTest"); + builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL) + .addAttribute("level", Level.DEBUG)); + final AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").addAttribute("target", + ConsoleAppender.Target.SYSTEM_OUT); + appenderBuilder.add(builder.newLayout("PatternLayout"). + addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); + appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, + Filter.Result.NEUTRAL).addAttribute("marker", "FLOW")); + builder.add(appenderBuilder); + builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG). + add(builder.newAppenderRef("Stdout")). + addAttribute("additivity", false)); + builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout"))); + ctx = Configurator.initialize(builder.build()); + final Configuration config = ctx.getConfiguration(); + assertNotNull(config, "No configuration"); + assertEquals("BuilderTest", config.getName(), "Unexpected Configuration"); + assertThat(config.getAppenders(), hasSize(equalTo(1))); + } + + @Test + public void testRolling() throws Exception { + final ConfigurationBuilder< BuiltConfiguration > builder = + ConfigurationBuilderFactory.newConfigurationBuilder(); + + builder.setStatusLevel( Level.ERROR); + builder.setConfigurationName("RollingBuilder"); + // create the console appender + AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").addAttribute("target", + ConsoleAppender.Target.SYSTEM_OUT); + appenderBuilder.add(builder.newLayout("PatternLayout"). + addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); + builder.add( appenderBuilder ); + + final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout") + .addAttribute("pattern", "%d [%t] %-5level: %msg%n"); + final ComponentBuilder triggeringPolicy = builder.newComponent("Policies") + .addComponent(builder.newComponent("CronTriggeringPolicy").addAttribute("schedule", "0 0 0 * * ?")) + .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "100M")); + appenderBuilder = builder.newAppender("rolling", "RollingFile") + .addAttribute("fileName", "target/rolling.log") + .addAttribute("filePattern", "target/archive/rolling-%d{MM-dd-yy}.log.gz") + .add(layoutBuilder) + .addComponent(triggeringPolicy); + builder.add(appenderBuilder); + + // create the new logger + builder.add( builder.newLogger( "TestLogger", Level.DEBUG ) + .add( builder.newAppenderRef( "rolling" ) ) + .addAttribute( "additivity", false ) ); + + builder.add( builder.newRootLogger( Level.DEBUG ) + .add( builder.newAppenderRef( "rolling" ) ) ); + final Configuration config = builder.build(); + config.initialize(); + assertNotNull(config.getAppender("rolling"), "No rolling file appender"); + assertEquals("RollingBuilder", config.getName(), "Unexpected Configuration"); + // Initialize the new configuration + final LoggerContext ctx = Configurator.initialize( config ); + Configurator.shutdown(ctx); + + } + + + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/TestConfiguratorError.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/TestConfiguratorError.java new file mode 100644 index 00000000000..5b2046faaee --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/TestConfiguratorError.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.test.junit.LoggerContextFactoryExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; + +import static org.junit.jupiter.api.Assertions.assertNull; + +@ResourceLock(LoggingSystemProperties.LOGGER_CONTEXT_FACTORY_CLASS) +public class TestConfiguratorError { + + @RegisterExtension + static final LoggerContextFactoryExtension EXTENSION = new LoggerContextFactoryExtension(SimpleLoggerContextFactory.INSTANCE); + + @Test + public void testErrorNoClassLoader() throws Exception { + try (final LoggerContext ctx = Configurator.initialize("Test1", "target/test-classes/log4j2-config.xml")) { + assertNull(ctx, "No LoggerContext should have been returned"); + } + } + + @Test + public void testErrorNullClassLoader() throws Exception { + try (final LoggerContext ctx = Configurator.initialize("Test1", null, + "target/test-classes/log4j2-config.xml")) { + assertNull(ctx, "No LoggerContext should have been returned"); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/BasicArbiterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/BasicArbiterTest.java new file mode 100644 index 00000000000..2e808696e38 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/BasicArbiterTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.arbiters; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests basic condition processing. + */ +public class BasicArbiterTest { + + static final String CONFIG = "log4j2-arbiters.json"; + static LoggerContext loggerContext = null; + + @AfterEach + public void after() { + loggerContext.stop(); + loggerContext = null; + } + + @Test + public void prodTest() { + System.setProperty("env", "prod"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertTrue(app instanceof ListAppender); + } + + @Test + public void devTest() { + System.setProperty("env", "dev"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertTrue(app instanceof ConsoleAppender); + } + + @Test void classArbiterTest() { + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("ShouldExist"); + assertNotNull(app); + assertTrue(app instanceof ListAppender); + app = loggerContext.getConfiguration().getAppender("ShouldNotExist"); + assertNull(app); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/SelectArbiterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/SelectArbiterTest.java new file mode 100644 index 00000000000..443a386ac21 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/arbiters/SelectArbiterTest.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.arbiters; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests basic condition processing. + */ +public class SelectArbiterTest { + + static final String CONFIG = "log4j2-selectArbiters.xml"; + static LoggerContext loggerContext = null; + + @AfterEach + public void after() { + loggerContext.stop(); + loggerContext = null; + } + + @Test + public void prodTest() { + System.setProperty("env", "prod"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertTrue(app instanceof ListAppender); + } + + @Test + public void devTest() { + System.setProperty("env", "dev"); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("Out"); + assertNotNull(app); + assertTrue(app instanceof ConsoleAppender); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java new file mode 100644 index 00000000000..03103312557 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.xml.XmlConfiguration; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("legacy-plugins.xml") +public class LegacyPluginTest { + + @Test + public void testLegacy(final Configuration configuration) throws Exception { + assertThat(configuration, instanceOf(XmlConfiguration.class)); + for (Map.Entry entry : configuration.getAppenders().entrySet()) { + if (entry.getKey().equalsIgnoreCase("console")) { + Layout layout = entry.getValue().getLayout(); + assertNotNull("No layout for Console Appender"); + String name = layout.getClass().getSimpleName(); + assertEquals("LogstashLayout", name, "Incorrect Layout class. Expected LogstashLayout, Actual " + name); + } else if (entry.getKey().equalsIgnoreCase("customConsole")) { + Layout layout = entry.getValue().getLayout(); + assertNotNull("No layout for CustomConsole Appender"); + String name = layout.getClass().getSimpleName(); + assertEquals("CustomConsoleLayout", + name, "Incorrect Layout class. Expected CustomConsoleLayout, Actual " + name); + } + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java index 35a3855b955..0416d32d46d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java @@ -17,6 +17,17 @@ package org.apache.logging.log4j.core.config.plugins.convert; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.appender.rolling.action.Duration; +import org.apache.logging.log4j.core.layout.GelfLayout; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + import java.io.File; import java.math.BigDecimal; import java.math.BigInteger; @@ -33,19 +44,11 @@ import java.util.Collection; import java.util.UUID; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.appender.rolling.action.Duration; -import org.apache.logging.log4j.core.layout.GelfLayout; -import org.apache.logging.log4j.core.net.Facility; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; /** - * Tests {@link TypeConverters}. + * Tests {@link CoreTypeConverters}. */ @RunWith(Parameterized.class) public class TypeConvertersTest { @@ -151,11 +154,11 @@ public static Collection data() throws Exception { { "123", "123".getBytes(Charset.defaultCharset()), null, byte[].class }, { "0xC773218C7EC8EE99", byteArray, null, byte[].class }, { "0xc773218c7ec8ee99", byteArray, null, byte[].class }, - { "Base64:cGxlYXN1cmUu", "pleasure.".getBytes("US-ASCII"), null, byte[].class }, + { "Base64:cGxlYXN1cmUu", "pleasure.".getBytes(StandardCharsets.US_ASCII), null, byte[].class }, // JRE // JRE Charset { "UTF-8", StandardCharsets.UTF_8, null, Charset.class }, - { "ASCII", Charset.forName("ASCII"), "UTF-8", Charset.class }, + { "ASCII", StandardCharsets.US_ASCII, "UTF-8", Charset.class }, { "Not a real charset", StandardCharsets.UTF_8, "UTF-8", Charset.class }, { null, StandardCharsets.UTF_8, "UTF-8", Charset.class }, { null, null, null, Charset.class }, @@ -215,7 +218,9 @@ public TypeConvertersTest(final String value, final Object expected, final Strin @Test public void testConvert() throws Exception { - final Object actual = TypeConverters.convert(value, clazz, defaultValue); + final Injector injector = DI.createInjector(); + injector.init(); + final Object actual = injector.getTypeConverter(clazz).convert(value, defaultValue); final String assertionMessage = "\nGiven: " + value + "\nDefault: " + defaultValue; if (expected != null && expected instanceof char[]) { assertArrayEquals(assertionMessage, (char[]) expected, (char[]) actual); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java new file mode 100644 index 00000000000..d8a5a3c95f1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.validation.validators; + +import java.util.function.Function; + +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.plugins.test.validation.ValidatingPlugin; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class RequiredValidatorTest { + + private final Injector injector = DI.createInjector().registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity); + private Node node; + + @BeforeEach + public void setUp() throws Exception { + final PluginNamespace category = injector.getInstance(new @Namespace("Test") Key<>() {}); + final PluginType pluginType = category.get("Validator"); + assertNotNull(pluginType, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", pluginType); + } + + @Test + public void testNullDefaultValue() throws Exception { + final ValidatingPlugin validatingPlugin = injector.configure(node); + assertNull(validatingPlugin); + } + + @Test + public void testNonNullValue() throws Exception { + node.getAttributes().put("name", "foo"); + final ValidatingPlugin validatingPlugin = injector.configure(node); + assertEquals("foo", validatingPlugin.getName()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java new file mode 100644 index 00000000000..853ea433383 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.validation.validators; + +import java.util.function.Function; + +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.plugins.test.validation.HostAndPort; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class ValidHostValidatorTest { + + private final Injector injector = DI.createInjector().registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity); + private Node node; + + @BeforeEach + public void setUp() throws Exception { + final PluginNamespace category = injector.getInstance(new @Namespace("Test") Key<>() {}); + PluginType plugin = category.get("HostAndPort"); + assertNotNull(plugin, "Rebuild this module to ensure annotation processing has been done."); + node = new Node(null, "HostAndPort", plugin); + } + + @Test + public void testNullHost() throws Exception { + assertNull(injector.configure(node)); + } + + @Test + public void testInvalidIpAddress() throws Exception { + node.getAttributes().put("host", "256.256.256.256"); + node.getAttributes().put("port", "1"); + final HostAndPort plugin = injector.configure(node); + assertNull(plugin, "Expected null, but got: " + plugin); + } + + @Test + public void testLocalhost() throws Exception { + node.getAttributes().put("host", "localhost"); + node.getAttributes().put("port", "1"); + final HostAndPort hostAndPort = injector.configure(node); + assertNotNull(hostAndPort); + assertTrue(hostAndPort.isValid()); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java new file mode 100644 index 00000000000..38059da8910 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.validation.validators; + +import java.util.function.Function; + +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +@StatusLoggerLevel("OFF") +public class ValidPortValidatorTest { + private final Injector injector = DI.createInjector().registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity); + private Node node; + + @BeforeEach + public void setUp() throws Exception { + final PluginNamespace category = injector.getInstance(new @Namespace("Test") Key<>() {}); + PluginType plugin = category.get("HostAndPort"); + assertNotNull(plugin, "Rebuild this module to ensure annotation processing has been done."); + node = new Node(null, "HostAndPort", plugin); + node.getAttributes().put("host", "localhost"); + } + + @Test + public void testNegativePort() throws Exception { + node.getAttributes().put("port", "-1"); + assertNull(injector.configure(node)); + } + + @Test + public void testValidPort() throws Exception { + node.getAttributes().put("port", "10"); + assertNotNull(injector.configure(node)); + } + + @Test + public void testInvalidPort() throws Exception { + node.getAttributes().put("port", "1234567890"); + assertNull(injector.configure(node)); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java new file mode 100644 index 00000000000..788c685585b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.validation.validators; + +import java.util.function.Function; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.appender.FailoverAppender; +import org.apache.logging.log4j.core.appender.FailoversPlugin; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.status.StatusData; +import org.apache.logging.log4j.status.StatusListener; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.*; + +@StatusLoggerLevel("OFF") +public class ValidatingPluginWithFailoverTest { + + private final Injector injector = DI.createInjector().registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity); + private Node node; + + @BeforeEach + public void setUp() throws Exception { + final PluginNamespace category = injector.getInstance(Core.PLUGIN_NAMESPACE_KEY); + PluginType plugin = category.get("Failover"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + + AppenderRef appenderRef = AppenderRef.createAppenderRef("List", Level.ALL, null); + node = new Node(null, "failover", plugin); + Node failoversNode = new Node(node, "Failovers", category.get("Failovers")); + Node appenderRefNode = new Node(failoversNode, "appenderRef", category.get("appenderRef")); + appenderRefNode.getAttributes().put("ref", "file"); + appenderRefNode.setObject(appenderRef); + failoversNode.getChildren().add(appenderRefNode); + failoversNode.setObject(FailoversPlugin.createFailovers(appenderRef)); + node.getAttributes().put("primary", "CONSOLE"); + node.getAttributes().put("name", "Failover"); + node.getChildren().add(failoversNode); + } + + @Test + public void testDoesNotLog_NoParameterThatMatchesElement_message() { + final StatusListener listener = mock(StatusListener.class); + when(listener.getStatusLevel()).thenReturn(Level.WARN); + injector.registerBinding(Key.forClass(Configuration.class), NullConfiguration::new); + final StatusLogger logger = StatusLogger.getLogger(); + logger.trace("Initializing"); + logger.registerListener(listener); + final FailoverAppender failoverAppender = injector.configure(node); + + verify(listener, times(1)).getStatusLevel(); + verify(listener, never()).log(any(StatusData.class)); + verifyNoMoreInteractions(listener); + assertNotNull(failoverAppender); + assertEquals("Failover", failoverAppender.getName()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java new file mode 100644 index 00000000000..a5c39d0e5df --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.validation.validators; + +import java.util.function.Function; + +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.test.validation.ValidatingPluginWithGenericBuilder; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class ValidatingPluginWithGenericBuilderTest { + + private final Injector injector = DI.createInjector().registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity); + private Node node; + + @BeforeEach + public void setUp() throws Exception { + final PluginNamespace category = injector.getInstance(new @Namespace("Test") Key<>() {}); + final var plugin = category.get("ValidatingPluginWithGenericBuilder"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", plugin); + } + + @Test + public void testNullDefaultValue() throws Exception { + final ValidatingPluginWithGenericBuilder validatingPlugin = injector.configure(node); + assertNull(validatingPlugin); + } + + @Test + public void testNonNullValue() throws Exception { + node.getAttributes().put("name", "foo"); + final ValidatingPluginWithGenericBuilder validatingPlugin = injector.configure(node); + assertNotNull(validatingPlugin); + assertEquals("foo", validatingPlugin.getName()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java new file mode 100644 index 00000000000..918ff808f34 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.validation.validators; + +import java.util.function.Function; + +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.test.validation.PluginWithGenericSubclassFoo1Builder; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class ValidatingPluginWithGenericSubclassFoo1BuilderTest { + + private final Injector injector = DI.createInjector().registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity); + private Node node; + + @BeforeEach + public void setUp() throws Exception { + final PluginNamespace category = injector.getInstance(new @Namespace("Test") Key<>() {}); + final var plugin = category.get("PluginWithGenericSubclassFoo1Builder"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", plugin); + } + + @Test + public void testNullDefaultValue() throws Exception { + final PluginWithGenericSubclassFoo1Builder validatingPlugin = injector.configure(node); + assertNull(validatingPlugin); + } + + @Test + public void testNonNullValue() throws Exception { + node.getAttributes().put("thing", "thing1"); + node.getAttributes().put("foo1", "foo1"); + final PluginWithGenericSubclassFoo1Builder validatingPlugin = injector.configure(node); + assertNotNull(validatingPlugin); + assertEquals("thing1", validatingPlugin.getThing()); + assertEquals("foo1", validatingPlugin.getFoo1()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java new file mode 100644 index 00000000000..566130b8cf2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.validation.validators; + +import java.util.function.Function; + +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.test.validation.ValidatingPluginWithTypedBuilder; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class ValidatingPluginWithTypedBuilderTest { + + private final Injector injector = DI.createInjector().registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity); + private Node node; + + @BeforeEach + public void setUp() throws Exception { + final PluginNamespace category = injector.getInstance(new @Namespace("Test") Key<>() {}); + final var plugin = category.get("ValidatingPluginWithTypedBuilder"); + assertNotNull(plugin, "Rebuild this module to make sure annotation processing kicks in."); + node = new Node(null, "Validator", plugin); + } + + @Test + public void testNullDefaultValue() throws Exception { + final ValidatingPluginWithTypedBuilder validatingPlugin = injector.configure(node); + assertNull(validatingPlugin); + } + + @Test + public void testNonNullValue() throws Exception { + node.getAttributes().put("name", "foo"); + final ValidatingPluginWithTypedBuilder validatingPlugin = injector.configure(node); + assertNotNull(validatingPlugin); + assertEquals("foo", validatingPlugin.getName()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java new file mode 100644 index 00000000000..4b384782de8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.properties; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.*; + +class PropertiesConfigurationTest { + + @Test + @LoggerContextSource("log4j2-properties.properties") + void testPropertiesConfiguration(final Configuration config) { + assertEquals(config.getState(), LifeCycle.State.STARTED, "Incorrect State: " + config.getState()); + final Map appenders = config.getAppenders(); + assertNotNull(appenders); + assertEquals(1, appenders.size(), "Incorrect number of Appenders: " + appenders.size()); + final Map loggers = config.getLoggers(); + assertNotNull(loggers); + assertEquals(2, loggers.size(), "Incorrect number of LoggerConfigs: " + loggers.size()); + final Filter filter = config.getFilter(); + assertNotNull(filter, "No Filter"); + assertTrue(filter instanceof ThresholdFilter, "Not a Threshold Filter"); + final Logger logger = LogManager.getLogger(getClass()); + logger.info("Welcome to Log4j!"); + } + + @Test + @LoggerContextSource("log4j2-properties-root-only.properties") + void testRootLoggerOnly(final Configuration config) { + assertEquals(config.getState(), LifeCycle.State.STARTED, "Incorrect State: " + config.getState()); + final Map appenders = config.getAppenders(); + assertNotNull(appenders); + assertEquals(appenders.size(), 1, "Incorrect number of Appenders: " + appenders.size()); + final Map loggers = config.getLoggers(); + assertNotNull(loggers); + assertEquals(loggers.size(), 1, "Incorrect number of LoggerConfigs: " + loggers.size()); + final Filter filter = config.getFilter(); + assertNotNull(filter, "No Filter"); + assertThat(filter, instanceOf(ThresholdFilter.class)); + final Logger logger = LogManager.getLogger(getClass()); + logger.info("Welcome to Log4j!"); + } + + @Test + @LoggerContextSource("log4j-rolling.properties") + void testRollingFile(final Configuration config) { + assertEquals(config.getState(), LifeCycle.State.STARTED, "Incorrect State: " + config.getState()); + final Map appenders = config.getAppenders(); + assertNotNull(appenders); + assertEquals(appenders.size(), 3, "Incorrect number of Appenders: " + appenders.size()); + final Map loggers = config.getLoggers(); + assertNotNull(loggers); + assertEquals(loggers.size(), 2, "Incorrect number of LoggerConfigs: " + loggers.size()); + final Filter filter = config.getFilter(); + assertNotNull(filter, "No Filter"); + assertThat(filter, instanceOf(ThresholdFilter.class)); + final Logger logger = LogManager.getLogger(getClass()); + logger.info("Welcome to Log4j!"); + } + + @Test + @LoggerContextSource("log4j2-properties-trailing-space-on-level.properties") + void testTrailingSpaceOnLevel(final Configuration config) { + assertEquals(config.getState(), LifeCycle.State.STARTED, "Incorrect State: " + config.getState()); + final Map appenders = config.getAppenders(); + assertNotNull(appenders); + assertEquals(appenders.size(), 1, "Incorrect number of Appenders: " + appenders.size()); + final Map loggers = config.getLoggers(); + assertNotNull(loggers); + assertEquals(loggers.size(), 2, "Incorrect number of LoggerConfigs: " + loggers.size()); + final Filter filter = config.getFilter(); + assertNotNull(filter, "No Filter"); + assertThat(filter, instanceOf(ThresholdFilter.class)); + final Logger logger = LogManager.getLogger(getClass()); + + assertEquals(Level.DEBUG, logger.getLevel(), "Incorrect level " + logger.getLevel()); + + logger.debug("Welcome to Log4j!"); + } + + @Test + @LoggerContextSource("RootLoggerLevelAppenderTest.properties") + void testRootLoggerLevelAppender(final LoggerContext context, @Named final ListAppender app) { + context.getRootLogger().info("Hello world!"); + final List events = app.getEvents(); + assertEquals(1, events.size()); + assertEquals("Hello world!", events.get(0).getMessage().getFormattedMessage()); + } + + @Test + @LoggerContextSource("LoggerLevelAppenderTest.properties") + void testLoggerLevelAppender(final LoggerContext context, @Named final ListAppender first, @Named final ListAppender second) { + context.getLogger(getClass()).atInfo().log("message"); + final List firstEvents = first.getEvents(); + final List secondEvents = second.getEvents(); + assertEquals(firstEvents, secondEvents); + assertEquals(1, firstEvents.size()); + } + + @SetSystemProperty(key = "coreProps", value = "DEBUG, first, second") + @Test + @LoggerContextSource("LoggerLevelSysPropsAppenderTest.properties") + void testLoggerLevelSysPropsAppender(final LoggerContext context, @Named final ListAppender first, + @Named final ListAppender second, @Named final ListAppender third) { + context.getLogger(getClass()).atInfo().log("message"); + context.getLogger(getClass()).atDebug().log("debug message"); + context.getRootLogger().atInfo().log("test message"); + final List firstEvents = first.getEvents(); + final List secondEvents = second.getEvents(); + assertEquals(firstEvents, secondEvents); + assertEquals(2, firstEvents.size()); + final List thirdEvents = third.getEvents(); + assertEquals(1, thirdEvents.size()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java index 37781a90556..94fa1528f13 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationPropsTest.java @@ -21,20 +21,18 @@ import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.status.StatusLogger; -import org.junit.AfterClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; -/** - * - */ public class XmlConfigurationPropsTest { private static final String CONFIG = "log4j-props.xml"; private static final String CONFIG1 = "log4j-props1.xml"; - @AfterClass + @AfterAll public static void cleanupClass() { System.clearProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY); final LoggerContext ctx = LoggerContext.getContext(); @@ -48,10 +46,9 @@ public void testNoProps() { final LoggerContext ctx = LoggerContext.getContext(); ctx.reconfigure(); final Configuration config = ctx.getConfiguration(); - assertTrue("Configuration is not an XmlConfiguration", config instanceof XmlConfiguration); + assertThat(config, instanceOf(XmlConfiguration.class)); } - @Test public void testDefaultStatus() { System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, CONFIG1); @@ -60,7 +57,7 @@ public void testDefaultStatus() { final LoggerContext ctx = LoggerContext.getContext(); ctx.reconfigure(); final Configuration config = ctx.getConfiguration(); - assertTrue("Configuration is not an XmlConfiguration", config instanceof XmlConfiguration); + assertThat(config, instanceOf(XmlConfiguration.class)); } finally { System.clearProperty(Constants.LOG4J_DEFAULT_STATUS_LEVEL); } @@ -74,10 +71,9 @@ public void testWithConfigProp() { final LoggerContext ctx = LoggerContext.getContext(); ctx.reconfigure(); final Configuration config = ctx.getConfiguration(); - assertTrue("Configuration is not an XmlConfiguration", config instanceof XmlConfiguration); + assertThat(config, instanceOf(XmlConfiguration.class)); } finally { System.clearProperty("log4j.level"); - } } @@ -90,7 +86,7 @@ public void testWithProps() { final LoggerContext ctx = LoggerContext.getContext(); ctx.reconfigure(); final Configuration config = ctx.getConfiguration(); - assertTrue("Configuration is not an XmlConfiguration", config instanceof XmlConfiguration); + assertThat(config, instanceOf(XmlConfiguration.class)); } finally { System.clearProperty("log4j.level"); System.clearProperty("log.level"); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationSecurity.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationSecurity.java new file mode 100644 index 00000000000..4cd4c740626 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationSecurity.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.xml; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@Tag("functional") +@Tag("security") +public class XmlConfigurationSecurity { + + @Test + @Timeout(5) + public void xmlSecurity() throws IOException { + final LoggerContext context = Configurator.initialize("XmlConfigurationSecurity", "XmlConfigurationSecurity.xml"); + assertNotNull(context.getConfiguration().getAppender("list")); + } + +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java new file mode 100644 index 00000000000..3293fab936b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlLoggerPropsTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.xml; + +import java.util.List; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SetSystemProperty(key = "test", value = "test") +public class XmlLoggerPropsTest { + + @Test + @LoggerContextSource("log4j-loggerprops.xml") + public void testWithProps(final LoggerContext context, @Named("List") final ListAppender listAppender) { + assertThat(context.getConfiguration(), is(instanceOf(XmlConfiguration.class))); + context.getLogger(getClass()).debug("Test with props"); + context.getLogger("tiny.bubbles").debug("Test on root"); + final List events = listAppender.getMessages(); + listAppender.clear(); + assertThat(events, hasSize(2)); + assertThat(events.get(0), allOf( + containsString("user="), + containsString("phrasex=****"), + containsString("test=test"), + containsString("test2=test2default"), + containsString("test3=Unknown"), + containsString("test4=test"), + containsString("test5=test"), + containsString("attribKey=attribValue"), + containsString("duplicateKey=nodeValue") + )); + assertThat(events.get(1), allOf( + containsString("user="), + containsString("phrasex=****"), + containsString("test=test"), + containsString("test2=test2default"), + containsString("test3=Unknown"), + containsString("test4=test"), + containsString("test5=test"), + containsString("attribKey=attribValue"), + containsString("duplicateKey=nodeValue") + )); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlSchemaTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlSchemaTest.java new file mode 100644 index 00000000000..6d150e0023f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/xml/XmlSchemaTest.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.xml; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.mutable.MutableInt; +import org.junit.jupiter.api.Test; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.XMLFilterImpl; +import org.xml.sax.helpers.XMLReaderFactory; + +import javax.xml.XMLConstants; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class XmlSchemaTest { + + private static final String TARGET_NAMESPACE = "http://logging.apache.org/log4j/2.0/config"; + + private static final List IGNORE_CONFIGS = Arrays.asList( // + "log4j2-arbiters.xml", // Arbiters violate XML schema as they can appear anywhere + "log4j2-scriptArbiters.xml", + "log4j2-selectArbiters.xml", + "log4j-core-gctests/src/test/resources/gcFreeLogging.xml", // has 2 tags defined + "legacy-plugins.xml", // + "logback", // logback configs + "log4j-xinclude", // + "log4j12", // log4j 1.x configs + "perf-CountingNoOpAppender.xml", // uses test-appender CountingNoOp + "reconfiguration-deadlock.xml", // uses test-appender ReconfigurationDeadlockTestAppender + "AsyncWaitStrategy", // uses AsyncWaitStrategyFactory (LOG4J2-3472) + "XmlConfigurationSecurity.xml" // used for testing XML parser; shouldn't be parseable in secure settings + ); + + private static String capitalizeTags(final String xml) { + final StringBuffer sb = new StringBuffer(); + final Matcher m = Pattern.compile("([<][/]?[a-z])").matcher(xml); + while (m.find()) { + m.appendReplacement(sb, m.group(1).toUpperCase()); + } + return m.appendTail(sb).toString(); + } + + private static String fixXml(String xml) { + xml = StringUtils.replace(xml, "JSONLayout", "JsonLayout"); + xml = StringUtils.replace(xml, "HTMLLayout", "HtmlLayout"); + xml = StringUtils.replace(xml, "XMLLayout", "XmlLayout"); + xml = StringUtils.replace(xml, "appender-ref", "AppenderRef"); + xml = StringUtils.replace(xml, " onmatch=", " onMatch="); + xml = StringUtils.replace(xml, " onMisMatch=", " onMismatch="); + xml = StringUtils.replace(xml, " onmismatch=", " onMismatch="); + xml = StringUtils.replace(xml, " exceptions = new ArrayList<>(); + + try (final Stream testResources = Files.list(Paths.get("src", "test", "resources"))) { + testResources + .filter(filePath -> { + final String fileName = filePath.getFileName().toString(); + if (!fileName.endsWith(".xml")) + return false; + for (final String ignore : IGNORE_CONFIGS) { + if (fileName.contains(ignore)) + return false; + } + return true; + }) // + .forEach(filePath -> { + System.out.println("Validating " + configs.incrementAndGet() + ". [" + filePath + "]..."); + System.out.flush(); + + try { + final String xml = fixXml(Files.readString(filePath)); + validator.validate(new SAXSource(namespaceAdder, + new InputSource(new ByteArrayInputStream(xml.getBytes()))), null); + } catch (final Exception ex) { + exceptions.add(ex); + } + }); + } + + assertThat(exceptions).isEmpty(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java index 077e2b1961c..34075cd41f0 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java @@ -22,9 +22,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.message.Message; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the AbstractFilter test. @@ -85,4 +85,4 @@ public Result filter(final Logger logger, final Level level, final Marker marker return testResult; } } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java index 4e0830d140f..55cd1b2dd1c 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java @@ -17,18 +17,18 @@ */ package org.apache.logging.log4j.core.filter; -import static org.junit.Assert.*; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; public class AbstractFilterableTest { MockedAbstractFilterable filterable; - @Before + @BeforeEach public void setup() { filterable = new MockedAbstractFilterable(); } @@ -51,7 +51,7 @@ public void testAddMultipleSimpleFilters() throws Exception { // into a CompositeFilter.class filterable.addFilter(filter); assertTrue(filterable.getFilter() instanceof CompositeFilter); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -64,7 +64,7 @@ public void testAddMultipleEqualSimpleFilter() throws Exception { // into a CompositeFilter.class filterable.addFilter(filter); assertTrue(filterable.getFilter() instanceof CompositeFilter); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -90,7 +90,7 @@ public void testAddMultipleCompositeFilters() throws Exception { // into a CompositeFilter.class filterable.addFilter(compositeFilter); assertTrue(filterable.getFilter() instanceof CompositeFilter); - assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -106,7 +106,7 @@ public void testAddSimpleFilterAndCompositeFilter() throws Exception { // into a CompositeFilter.class filterable.addFilter(compositeFilter); assertTrue(filterable.getFilter() instanceof CompositeFilter); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -122,7 +122,7 @@ public void testAddCompositeFilterAndSimpleFilter() throws Exception { // into a CompositeFilter.class filterable.addFilter(notInCompositeFilterFilter); assertTrue(filterable.getFilter() instanceof CompositeFilter); - assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -167,7 +167,7 @@ public void testRemoveSimpleEqualFilterFromMultipleSimpleFilters() throws Except filterable.addFilter(filterCopy); filterable.removeFilter(filterCopy); assertTrue(filterable.getFilter() instanceof CompositeFilter); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); filterable.removeFilter(filterCopy); assertEquals(filterOriginal, filterable.getFilter()); filterable.removeFilter(filterOriginal); @@ -223,7 +223,7 @@ public void testRemoveSimpleFilterFromCompositeAndSimpleFilter() { // should not remove internal filter of compositeFilter filterable.removeFilter(anotherFilter); assertTrue(filterable.getFilter() instanceof CompositeFilter); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); } @Test @@ -246,16 +246,16 @@ public void testRemoveFiltersFromComposite() { filterable.addFilter(compositeFilter); filterable.addFilter(anotherFilter); - assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); filterable.removeFilter(filter1); - assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size()); + assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length); filterable.removeFilter(filter2); assertSame(anotherFilter, filterable.getFilter()); } - private class MockedAbstractFilterable extends AbstractFilterable {} + private static class MockedAbstractFilterable extends AbstractFilterable {} - private class EqualFilter extends AbstractFilter { + private static class EqualFilter extends AbstractFilter { private final String key; public EqualFilter(final String key) { this.key = key; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterLogDelayTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterLogDelayTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterLogDelayTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterLogDelayTest.java index 3d8af9d370a..9913381f884 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterLogDelayTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterLogDelayTest.java @@ -17,14 +17,14 @@ */ package org.apache.logging.log4j.core.filter; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import org.junit.jupiter.api.Test; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; -import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; /** * Unit test for BurstFilter. diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java new file mode 100644 index 00000000000..a11534302bd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java @@ -0,0 +1,139 @@ +/* Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit test for BurstFilter. + */ +@LoggerContextSource("log4j-burst.xml") +@Tag("sleepy") +public class BurstFilterTest { + + private final ListAppender app; + private final BurstFilter filter; + private final Logger logger; + + public BurstFilterTest(final LoggerContext context, @Named("ListAppender") final ListAppender app) { + this.app = app; + this.filter = (BurstFilter) app.getFilter(); + assertNotNull(filter); + this.logger = context.getLogger(getClass()); + } + + /** + * Test BurstFilter by surpassing maximum number of log messages allowed by filter and + * making sure only the maximum number are indeed logged, then wait for while and make + * sure the filter allows the appropriate number of messages to be logged. + */ + @Test + public void test() throws Exception { + System.nanoTime(); + for (int i = 0; i < 110; i++) { + if (i % 10 == 0) { + Thread.sleep(200); + } + logger.info("Logging 110 messages, should only see 100 logs # " + (i + 1)); + assertTrue(filter.getAvailable() < 100, "Incorrect number of available slots"); + } + List msgs = app.getMessages(); + assertEquals(100, msgs.size(), "Incorrect message count. Should be 100, actual " + msgs.size()); + app.clear(); + + assertTrue(filter.getAvailable() < 100, "Incorrect number of available slots"); + // Allow some of the events to clear + Thread.sleep(1500); + + for (int i = 0; i < 110; i++) { + logger.info("Waited 1.5 seconds and trying to log again, should see more than 0 and less than 100" + (i + 1)); + } + + msgs = app.getMessages(); + assertFalse(msgs.isEmpty(), "No messages were counted."); + assertTrue(msgs.size() < 100, "Incorrect message count. Should be > 0 and < 100, actual " + msgs.size()); + app.clear(); + + filter.clear(); + + for (int i = 0; i < 110; i++) { + logger.info("Waited 1.5 seconds and trying to log again, should see more than 0 and less than 100" + (i + 1)); + } + assertEquals(0, filter.getAvailable(), ""); + app.clear(); + + + // now log 100 debugs, they shouldn't get through because there are no available slots. + for (int i = 0; i < 110; i++) { + logger.debug( + "TEST FAILED! Logging 110 debug messages, shouldn't see any of them because they are debugs #" + (i + 1)); + } + + msgs = app.getMessages(); + assertTrue(msgs.isEmpty(), "Incorrect message count. Should be 0, actual " + msgs.size()); + app.clear(); + + // now log 100 warns, they should all get through because the filter's level is set at info + for (int i = 0; i < 110; i++) { + logger.warn("Logging 110 warn messages, should see all of them because they are warns #" + (i + 1)); + } + + msgs = app.getMessages(); + assertEquals(110, msgs.size(), "Incorrect message count. Should be 110, actual " + msgs.size()); + app.clear(); + + // now log 100 errors, they should all get through because the filter level is set at info + for (int i = 0; i < 110; i++) { + logger.error("Logging 110 error messages, should see all of them because they are errors #" + (i + 1)); + } + + msgs = app.getMessages(); + assertEquals(110, msgs.size(), "Incorrect message count. Should be 110, actual " + msgs.size()); + app.clear(); + + // now log 100 fatals, they should all get through because the filter level is set at info + for (int i = 0; i < 110; i++) { + logger.fatal("Logging 110 fatal messages, should see all of them because they are fatals #" + (i + 1)); + } + + msgs = app.getMessages(); + assertEquals(110, msgs.size(), "Incorrect message count. Should be 110, actual " + msgs.size()); + app.clear(); + + // wait and make sure we can log messages again despite the fact we just logged a bunch of warns, errors, fatals + Thread.sleep(3100); + + for (int i = 0; i < 110; i++) { + logger.debug("Waited 3+ seconds, should see 100 logs #" + (i + 1)); + } + msgs = app.getMessages(); + assertEquals(100, msgs.size(), "Incorrect message count. Should be 100, actual " + msgs.size()); + app.clear(); + + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java new file mode 100644 index 00000000000..0a5698b6078 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/CompositeFilterTest.java @@ -0,0 +1,49 @@ +/* Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.logging.log4j.core.filter; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Filter.Result; +import org.junit.jupiter.api.Test; + +public class CompositeFilterTest { + + @Test + public void testConcatenation() { + final Filter a = DenyAllFilter.newBuilder().setOnMatch(Result.ACCEPT).build(); + final Filter b = DenyAllFilter.newBuilder().setOnMatch(Result.NEUTRAL).build(); + final Filter c = DenyAllFilter.newBuilder().setOnMatch(Result.DENY).build(); + // The three values need to be distinguishable + assertNotEquals(a, b); + assertNotEquals(a, c); + assertNotEquals(b, c); + final Filter[] expected = new Filter[] {a, b, c}; + final CompositeFilter singleA = CompositeFilter.createFilters(new Filter[] {a}); + final CompositeFilter singleB = CompositeFilter.createFilters(new Filter[] {b}); + final CompositeFilter singleC = CompositeFilter.createFilters(new Filter[] {c}); + // Concatenating one at a time + final CompositeFilter concat1 = singleA.addFilter(b).addFilter(c); + assertArrayEquals(expected, concat1.getFiltersArray()); + // In reverse order + final CompositeFilter concat2 = singleA.addFilter(singleB.addFilter(singleC)); + assertArrayEquals(expected, concat2.getFiltersArray()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilterTest.java new file mode 100644 index 00000000000..ecf9364bd2c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilterTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@UsingThreadContextMap +public class DynamicThresholdFilterTest { + + @Test + public void testFilter() { + ThreadContext.put("userid", "testuser"); + ThreadContext.put("organization", "apache"); + final KeyValuePair[] pairs = new KeyValuePair[] { + new KeyValuePair("testuser", "DEBUG"), + new KeyValuePair("JohnDoe", "warn") }; + final DynamicThresholdFilter filter = DynamicThresholdFilter.newBuilder() + .setKey("userid") + .setPairs(pairs) + .setDefaultThreshold(Level.ERROR) + .get(); + filter.start(); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, (Object) null, (Throwable) null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null)); + ThreadContext.clearMap(); + ThreadContext.put("userid", "JohnDoe"); + ThreadContext.put("organization", "apache"); + LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.DEBUG).setMessage(new SimpleMessage("Test")).build(); + assertSame(Filter.Result.DENY, filter.filter(event)); + event = Log4jLogEvent.newBuilder().setLevel(Level.ERROR).setMessage(new SimpleMessage("Test")).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event)); + ThreadContext.clearMap(); + } + + @Test + public void testFilterWorksWhenParamsArePassedAsArguments() { + ThreadContext.put("userid", "testuser"); + ThreadContext.put("organization", "apache"); + final KeyValuePair[] pairs = new KeyValuePair[] { + new KeyValuePair("testuser", "DEBUG"), + new KeyValuePair("JohnDoe", "warn") }; + final DynamicThresholdFilter filter = DynamicThresholdFilter.newBuilder() + .setKey("userid") + .setPairs(pairs) + .setDefaultThreshold(Level.ERROR) + .setOnMatch(Filter.Result.ACCEPT) + .setOnMismatch(Filter.Result.NEUTRAL) + .get(); + filter.start(); + assertTrue(filter.isStarted()); + final Object [] replacements = {"one", "two", "three"}; + assertSame(Filter.Result.ACCEPT, filter.filter(null, Level.DEBUG, null, "some test message", replacements)); + assertSame(Filter.Result.ACCEPT, filter.filter(null, Level.DEBUG, null, "some test message", "one", "two", "three")); + ThreadContext.clearMap(); + } + + @Test + @LoggerContextSource("log4j2-dynamicfilter.xml") + public void testConfig(final Configuration config) { + final Filter filter = config.getFilter(); + assertNotNull(filter, "No DynamicThresholdFilter"); + assertTrue(filter instanceof DynamicThresholdFilter, "Not a DynamicThresholdFilter"); + final DynamicThresholdFilter dynamic = (DynamicThresholdFilter) filter; + final String key = dynamic.getKey(); + assertNotNull(key, "Key is null"); + assertEquals("loginId", key, "Incorrect key value"); + final Map map = dynamic.getLevelMap(); + assertNotNull(map, "Map is null"); + assertEquals(1, map.size(), "Incorrect number of map elements"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java new file mode 100644 index 00000000000..712fe0a4eaf --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Base64; +import java.util.Enumeration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.Assert; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Unit test for simple App. + */ +public class HttpThreadContextMapFilterTest implements MutableThreadContextMapFilter.FilterConfigUpdateListener { + + private static final String BASIC = "Basic "; + private static final String expectedCreds = "log4j:log4j"; + private static Server server; + private static final Base64.Decoder decoder = Base64.getDecoder(); + private static int port; + static final String CONFIG = "log4j2-mutableFilter.xml"; + static LoggerContext loggerContext = null; + static final File targetFile = new File("target/test-classes/testConfig.json"); + static final Path target = targetFile.toPath(); + CountDownLatch updated = new CountDownLatch(1); + + + @BeforeAll + public static void startServer() throws Exception { + try { + server = new Server(0); + ServletContextHandler context = new ServletContextHandler(); + ServletHolder defaultServ = new ServletHolder("default", TestServlet.class); + defaultServ.setInitParameter("resourceBase", System.getProperty("user.dir")); + defaultServ.setInitParameter("dirAllowed", "true"); + context.addServlet(defaultServ, "/"); + server.setHandler(context); + + // Start Server + server.start(); + port = ((ServerConnector) server.getConnectors()[0]).getLocalPort(); + try { + Files.deleteIfExists(target); + } catch (IOException ioe) { + // Ignore this. + } + } catch (Throwable ex) { + ex.printStackTrace(); + throw ex; + } + } + + @AfterAll + public static void stopServer() throws Exception { + server.stop(); + } + @AfterEach + public void after() { + try { + Files.deleteIfExists(target); + } catch (IOException ioe) { + // Ignore this. + } + loggerContext.stop(); + loggerContext = null; + } + + @Test + public void filterTest() throws Exception { + System.setProperty("log4j2.Configuration.allowedProtocols", "http"); + System.setProperty("logging.auth.username", "log4j"); + System.setProperty("logging.auth.password", "log4j"); + System.setProperty("configLocation", "http://localhost:" + port + "/testConfig.json"); + ThreadContext.put("loginId", "rgoers"); + Path source = new File("target/test-classes/emptyConfig.json").toPath(); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + long fileTime = targetFile.lastModified() - 2000; + assertTrue(targetFile.setLastModified(fileTime)); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("List"); + assertNotNull(app); + assertTrue(app instanceof ListAppender); + MutableThreadContextMapFilter filter = (MutableThreadContextMapFilter) loggerContext.getConfiguration().getFilter(); + assertNotNull(filter); + filter.registerListener(this); + Logger logger = loggerContext.getLogger("Test"); + logger.debug("This is a test"); + Assertions.assertEquals(0, ((ListAppender) app).getEvents().size()); + source = new File("target/test-classes/filterConfig.json").toPath(); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + assertNotEquals(fileTime, targetFile.lastModified()); + if (!updated.await(5, TimeUnit.SECONDS)) { + fail("File update was not detected"); + } + updated = new CountDownLatch(1); + logger.debug("This is a test"); + Assertions.assertEquals(1, ((ListAppender) app).getEvents().size()); + Assertions.assertTrue(Files.deleteIfExists(target)); + if (!updated.await(5, TimeUnit.SECONDS)) { + fail("File update for delete was not detected"); + } + } + + public static class TestServlet extends DefaultServlet { + + private static final long serialVersionUID = -2885158530511450659L; + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + Enumeration headers = request.getHeaders(HttpHeader.AUTHORIZATION.toString()); + if (headers == null) { + response.sendError(401, "No Auth header"); + return; + } + while (headers.hasMoreElements()) { + String authData = headers.nextElement(); + Assert.assertTrue("Not a Basic auth header", authData.startsWith(BASIC)); + String credentials = new String(decoder.decode(authData.substring(BASIC.length()))); + if (!expectedCreds.equals(credentials)) { + response.sendError(401, "Invalid credentials"); + return; + } + } + if (request.getServletPath().equals("/testConfig.json")) { + File file = new File("target/test-classes/testConfig.json"); + if (!file.exists()) { + response.sendError(404, "File not found"); + return; + } + long modifiedSince = request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString()); + long lastModified = (file.lastModified() / 1000) * 1000; + if (modifiedSince > 0 && lastModified <= modifiedSince) { + response.setStatus(304); + return; + } + response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), lastModified); + response.setContentLengthLong(file.length()); + Files.copy(file.toPath(), response.getOutputStream()); + response.getOutputStream().flush(); + response.setStatus(200); + } else { + response.sendError(400, "Unsupported request"); + } + } + } + + @Override + public void onEvent() { + updated.countDown(); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.java index b99c6a9f3d9..97d8140737d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/LevelRangeFilterTest.java @@ -21,13 +21,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class LevelRangeFilterTest { @Test @@ -56,5 +53,5 @@ public void testMinimumOnlyLevel() { assertTrue(filter.isStarted()); assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null)); } - + } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.java new file mode 100644 index 00000000000..041816fac30 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.message.StringMapMessage; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MapFilterTest { + + @Test + public void testFilter() { + final KeyValuePair[] pairs = new KeyValuePair[] { new KeyValuePair("FromAccount", "211000"), + new KeyValuePair("ToAccount", "123456")}; + MapFilter filter = MapFilter.createFilter(pairs, "and", null, null); + assertNotNull(filter); + filter.start(); + StringMapMessage msg = new StringMapMessage(); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "211000"); + msg.put("Amount", "1000.00"); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, msg, null)); + msg.put("ToAccount", "111111"); + assertSame(Filter.Result.DENY, filter.filter(null, Level.ERROR, null, msg, null)); + filter = MapFilter.createFilter(pairs, "or", null, null); + assertNotNull(filter); + filter.start(); + msg = new StringMapMessage(); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "211000"); + msg.put("Amount", "1000.00"); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, msg, null)); + msg.put("ToAccount", "111111"); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, msg, null)); + } + + @Test + @LoggerContextSource("log4j2-mapfilter.xml") + public void testConfig(final Configuration config, @Named("LIST") final ListAppender app) { + final Filter filter = config.getFilter(); + assertNotNull(filter, "No MapFilter"); + assertTrue(filter instanceof MapFilter, "Not a MapFilter"); + final MapFilter mapFilter = (MapFilter) filter; + assertFalse(mapFilter.isAnd(), "Should not be And filter"); + final IndexedReadOnlyStringMap map = mapFilter.getStringMap(); + assertNotNull(map, "No Map"); + assertFalse(map.isEmpty(), "No elements in Map"); + assertEquals(1, map.size(), "Incorrect number of elements in Map"); + assertTrue(map.containsKey("eventId"), "Map does not contain key eventId"); + assertEquals(2, map.>getValue("eventId").size(), "List does not contain 2 elements"); + final Logger logger = LogManager.getLogger(MapFilterTest.class); + final Map eventMap = new HashMap<>(); + eventMap.put("eventId", "Login"); + logger.debug(new StringMapMessage(eventMap)); + final List msgs = app.getMessages(); + assertNotNull(msgs, "No messages"); + assertFalse(msgs.isEmpty(), "No messages"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/MarkerFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MarkerFilterTest.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/MarkerFilterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MarkerFilterTest.java index 1850e771b50..8ef4a460d85 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/MarkerFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MarkerFilterTest.java @@ -23,13 +23,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class MarkerFilterTest { @Test diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java new file mode 100644 index 00000000000..b21a865120f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit test for simple App. + */ +public class MutableThreadContextMapFilterTest implements MutableThreadContextMapFilter.FilterConfigUpdateListener { + + static final String CONFIG = "log4j2-mutableFilter.xml"; + static LoggerContext loggerContext = null; + static File targetFile = new File("target/test-classes/testConfig.json"); + static Path target = targetFile.toPath(); + CountDownLatch updated = new CountDownLatch(1); + + @AfterEach + public void after() { + try { + Files.deleteIfExists(target); + } catch (IOException ioe) { + // Ignore this. + } + ThreadContext.clearMap(); + loggerContext.stop(); + loggerContext = null; + } + + @Test + public void filterTest() throws Exception { + System.setProperty("configLocation", "target/test-classes/testConfig.json"); + ThreadContext.put("loginId", "rgoers"); + Path source = new File("target/test-classes/emptyConfig.json").toPath(); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + long fileTime = targetFile.lastModified() - 1000; + assertTrue(targetFile.setLastModified(fileTime)); + loggerContext = Configurator.initialize(null, CONFIG); + assertNotNull(loggerContext); + Appender app = loggerContext.getConfiguration().getAppender("List"); + assertNotNull(app); + assertTrue(app instanceof ListAppender); + MutableThreadContextMapFilter filter = (MutableThreadContextMapFilter) loggerContext.getConfiguration().getFilter(); + assertNotNull(filter); + filter.registerListener(this); + Logger logger = loggerContext.getLogger("Test"); + logger.debug("This is a test"); + Assertions.assertEquals(0, ((ListAppender) app).getEvents().size()); + source = new File("target/test-classes/filterConfig.json").toPath(); + String msg = null; + boolean copied = false; + for (int i = 0; i < 5 && !copied; ++i) { + Thread.sleep(100 + (100 * i)); + try { + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + copied = true; + } catch (Exception ex) { + msg = ex.getMessage(); + } + } + assertTrue(copied, "File not copied: " + msg); + assertNotEquals(fileTime, targetFile.lastModified()); + if (!updated.await(5, TimeUnit.SECONDS)) { + fail("File update was not detected"); + } + logger.debug("This is a test"); + Assertions.assertEquals(1, ((ListAppender) app).getEvents().size()); + } + + @Override + public void onEvent() { + updated.countDown(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/NoMarkerFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/NoMarkerFilterTest.java new file mode 100644 index 00000000000..d98a5d4faa0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/NoMarkerFilterTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class NoMarkerFilterTest { + + @Test + public void testMarkers() { + final Marker sampleMarker = MarkerManager.getMarker("SampleMarker"); + NoMarkerFilter filter = NoMarkerFilter.newBuilder().build(); + filter.start(); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.DENY, filter.filter(null, null, sampleMarker, (Object) null, (Throwable) null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, null, null, (Object) null, (Throwable) null)); + filter.stop(); + LogEvent event = Log4jLogEvent.newBuilder() // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Hello, world!")).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event)); + + filter.start(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event)); + event = Log4jLogEvent.newBuilder() // + .setMarker(sampleMarker) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Hello, world!")).build(); + assertSame(Filter.Result.DENY, filter.filter(event)); + filter.stop(); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java index c48edbe42ba..9b682632370 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java @@ -16,12 +16,6 @@ */ package org.apache.logging.log4j.core.filter; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Filter.Result; @@ -30,14 +24,15 @@ import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.status.StatusLogger; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class RegexFilterTest { - @BeforeClass + @BeforeAll public static void before() { StatusLogger.getLogger().setLevel(Level.OFF); } @@ -86,4 +81,24 @@ public void testNoMsg() throws Exception { assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Message) null, (Throwable) null)); assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, null, (Object[]) null)); } + + @Test + public void testParameterizedMsg() throws Exception { + final String msg = "params {} {}"; + final Object[] params = { "foo", "bar" }; + + // match against raw message + final RegexFilter rawFilter = RegexFilter.createFilter("params \\{\\} \\{\\}", null, + true, // useRawMsg + Result.ACCEPT, Result.DENY); + final Result rawResult = rawFilter.filter(null, null, null, msg, params); + assertThat(rawResult, equalTo(Result.ACCEPT)); + + // match against formatted message + final RegexFilter fmtFilter = RegexFilter.createFilter("params foo bar", null, + false, // useRawMsg + Result.ACCEPT, Result.DENY); + final Result fmtResult = fmtFilter.filter(null, null, null, msg, params); + assertThat(fmtResult, equalTo(Result.ACCEPT)); + } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.java new file mode 100644 index 00000000000..db306f3b687 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import java.util.Collection; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class StructuredDataFilterTest { + + @Test + public void testFilter() { + final KeyValuePair[] pairs = new KeyValuePair[] { new KeyValuePair("id.name", "AccountTransfer"), + new KeyValuePair("ToAccount", "123456")}; + StructuredDataFilter filter = StructuredDataFilter.createFilter(pairs, "and", null, null); + assertNotNull(filter); + filter.start(); + StructuredDataMessage msg = new StructuredDataMessage("AccountTransfer@18060", "Transfer Successful", "Audit"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "211000"); + msg.put("Amount", "1000.00"); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, msg, null)); + msg.put("ToAccount", "111111"); + assertSame(Filter.Result.DENY, filter.filter(null, Level.ERROR, null, msg, null)); + filter = StructuredDataFilter.createFilter(pairs, "or", null, null); + assertNotNull(filter); + filter.start(); + msg = new StructuredDataMessage("AccountTransfer@18060", "Transfer Successful", "Audit"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "211000"); + msg.put("Amount", "1000.00"); + assertTrue(filter.isStarted()); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, msg, null)); + msg.put("ToAccount", "111111"); + assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, msg, null)); + } + + @Test + @LoggerContextSource("log4j2-sdfilter.xml") + public void testConfig(final Configuration config) { + final Filter filter = config.getFilter(); + assertNotNull(filter, "No StructuredDataFilter"); + assertTrue(filter instanceof StructuredDataFilter, "Not a StructuredDataFilter"); + final StructuredDataFilter sdFilter = (StructuredDataFilter) filter; + assertFalse(sdFilter.isAnd(), "Should not be And filter"); + final IndexedReadOnlyStringMap map = sdFilter.getStringMap(); + assertNotNull(map, "No Map"); + assertFalse(map.isEmpty(), "No elements in Map"); + assertEquals(1, map.size(), "Incorrect number of elements in Map"); + assertTrue(map.containsKey("eventId"), "Map does not contain key eventId"); + assertEquals(2, map.>getValue("eventId").size(), "List does not contain 2 elements"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilterTest.java index 546ef306c8d..2ef851cb31f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilterTest.java @@ -20,14 +20,10 @@ import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.util.KeyValuePair; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; - -/** - * - */ public class ThreadContextMapFilterTest { @Test @@ -37,6 +33,7 @@ public void testFilter() { final KeyValuePair[] pairs = new KeyValuePair[] { new KeyValuePair("userid", "JohnDoe"), new KeyValuePair("organization", "Apache")}; ThreadContextMapFilter filter = ThreadContextMapFilter.createFilter(pairs, "and", null, null); + assertNotNull(filter); filter.start(); assertTrue(filter.isStarted()); assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, (Throwable) null)); @@ -48,6 +45,7 @@ public void testFilter() { assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, (Throwable) null)); ThreadContext.clearMap(); filter = ThreadContextMapFilter.createFilter(pairs, "or", null, null); + assertNotNull(filter); filter.start(); assertTrue(filter.isStarted()); ThreadContext.put("userid", "testuser"); @@ -59,6 +57,7 @@ public void testFilter() { assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, (Throwable) null)); final KeyValuePair[] single = new KeyValuePair[] {new KeyValuePair("userid", "testuser")}; filter = ThreadContextMapFilter.createFilter(single, null, null, null); + assertNotNull(filter); filter.start(); assertTrue(filter.isStarted()); assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.DEBUG, null, (Object) null, (Throwable) null)); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ThresholdFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThresholdFilterTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ThresholdFilterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThresholdFilterTest.java index 5ae7d52d73c..4fed5118ac1 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/ThresholdFilterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/ThresholdFilterTest.java @@ -21,13 +21,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class ThresholdFilterTest { @Test diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.java new file mode 100644 index 00000000000..4c586557314 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.java @@ -0,0 +1,218 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.time.Clock; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.TimeZone; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TimeFilterTest { + private static long CLOCKTIME = System.currentTimeMillis(); + + /** Helper class */ + public static class FixedTimeClock implements Clock { + @Override + public long currentTimeMillis() { + return CLOCKTIME; + } + } + + @Test + public void springForward() { + final TimeFilter filter = new TimeFilter(LocalTime.of(2,0), LocalTime.of(3,0), + ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 3, 8), new FixedTimeClock()); + filter.start(); + assertTrue(filter.isStarted()); + ZonedDateTime date = ZonedDateTime.of(2020, 3, 8, 2, 6, 30, 0, ZoneId.of("America/Los_Angeles")); + CLOCKTIME = date.toInstant().toEpochMilli(); + LogEvent event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.plusDays(1).withHour(2); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.withHour(4); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.DENY, filter.filter(event), + "Time " + CLOCKTIME + " is within range: " + filter.toString()); + } + + + @Test + public void fallBack() { + final TimeFilter filter = new TimeFilter(LocalTime.of(1,0), LocalTime.of(2,0), + ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 11, 1), new FixedTimeClock()); + filter.start(); + assertTrue(filter.isStarted()); + ZonedDateTime date = ZonedDateTime.of(2020, 11, 1, 1, 6, 30, 0, ZoneId.of("America/Los_Angeles")).withEarlierOffsetAtOverlap(); + CLOCKTIME = date.toInstant().toEpochMilli(); + LogEvent event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = ZonedDateTime.of(2020, 11, 1, 1, 6, 30, 0, ZoneId.of("America/Los_Angeles")).withLaterOffsetAtOverlap(); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.DENY, filter.filter(event), + "Time " + CLOCKTIME + " is within range: " + filter.toString()); + date = date.plusDays(1).withHour(1).withMinute(30); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.withHour(4); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.DENY, filter.filter(event), + "Time " + CLOCKTIME + " is within range: " + filter.toString()); + } + + + @Test + public void overnight() { + final TimeFilter filter = new TimeFilter(LocalTime.of(23,0), LocalTime.of(1,0), + ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 3, 10), new FixedTimeClock()); + filter.start(); + assertTrue(filter.isStarted()); + ZonedDateTime date = ZonedDateTime.of(2020, 3, 10, 23, 30, 30, 0, ZoneId.of("America/Los_Angeles")).withEarlierOffsetAtOverlap(); + CLOCKTIME = date.toInstant().toEpochMilli(); + LogEvent event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.plusHours(1); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.plusHours(1); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.DENY, filter.filter(event), + "Time " + CLOCKTIME + " is within range: " + filter.toString()); + date = date.plusDays(1).withHour(0); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + } + + @Test + public void overnightForward() { + final TimeFilter filter = new TimeFilter(LocalTime.of(23,0), LocalTime.of(2,0), + ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 3, 7), new FixedTimeClock()); + filter.start(); + assertTrue(filter.isStarted()); + ZonedDateTime date = ZonedDateTime.of(2020, 3, 7, 23, 30, 30, 0, ZoneId.of("America/Los_Angeles")).withEarlierOffsetAtOverlap(); + CLOCKTIME = date.toInstant().toEpochMilli(); + LogEvent event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.plusHours(1); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.plusHours(2); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.DENY, filter.filter(event), + "Time " + CLOCKTIME + " is within range: " + filter.toString()); + date = date.plusDays(1).withHour(0); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + } + + + @Test + public void overnightFallback() { + final TimeFilter filter = new TimeFilter(LocalTime.of(23,0), LocalTime.of(2,0), + ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 10, 31), new FixedTimeClock()); + filter.start(); + assertTrue(filter.isStarted()); + ZonedDateTime date = ZonedDateTime.of(2020, 10, 31, 23, 30, 30, 0, ZoneId.of("America/Los_Angeles")).withEarlierOffsetAtOverlap(); + CLOCKTIME = date.toInstant().toEpochMilli(); + LogEvent event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.plusHours(1); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + date = date.plusHours(2); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.DENY, filter.filter(event), + "Time " + CLOCKTIME + " is within range: " + filter.toString()); + date = date.plusDays(1).withHour(0); + CLOCKTIME = date.toInstant().toEpochMilli(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + } + + @Test + public void testTime() { + // https://garygregory.wordpress.com/2013/06/18/what-are-the-java-timezone-ids/ + final TimeFilter filter = TimeFilter.newBuilder() + .setStart("02:00:00") + .setEnd("03:00:00") + .setTimezone(ZoneId.of("America/Los_Angeles")) + .setClock(new FixedTimeClock()) + .get(); + filter.start(); + assertTrue(filter.isStarted()); + final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")); + cal.set(Calendar.HOUR_OF_DAY, 2); + CLOCKTIME = cal.getTimeInMillis(); + LogEvent event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + //assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null)); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + + cal.add(Calendar.DATE, 1); + cal.set(Calendar.HOUR_OF_DAY, 2); + CLOCKTIME = cal.getTimeInMillis(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + assertSame(Filter.Result.NEUTRAL, filter.filter(event), + "Time " + CLOCKTIME + " is not within range: " + filter.toString()); + //assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null)); + + cal.set(Calendar.HOUR_OF_DAY, 4); + CLOCKTIME = cal.getTimeInMillis(); + event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build(); + //assertSame(Filter.Result.DENY, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null)); + assertSame(Filter.Result.DENY, filter.filter(event), + "Time " + CLOCKTIME + " is within range: " + filter.toString()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java similarity index 75% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java index 47c9c148a7e..e499c144b1e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java @@ -16,26 +16,23 @@ */ package org.apache.logging.log4j.core.impl; -import java.lang.reflect.Field; - import org.apache.logging.log4j.util.SortedArrayStringMap; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests the ContextDataFactory class. */ +@SetSystemProperty(key = Log4jProperties.THREAD_CONTEXT_DATA_CLASS_NAME, value = "org.apache.logging.log4j.core.impl.FactoryTestStringMapWithoutIntConstructor") public class ContextDataFactoryPropertySetMissingConstructorTest { @Test public void intArgReturnsSortedArrayStringMapIfPropertySpecifiedButMissingIntConstructor() throws Exception { - System.setProperty("log4j2.ContextData", FactoryTestStringMapWithoutIntConstructor.class.getName()); assertTrue(ContextDataFactory.createContextData(2) instanceof SortedArrayStringMap); final SortedArrayStringMap actual = (SortedArrayStringMap) ContextDataFactory.createContextData(2); - final Field thresholdField = SortedArrayStringMap.class.getDeclaredField("threshold"); - thresholdField.setAccessible(true); - assertEquals(2, thresholdField.getInt(actual)); - System.clearProperty("log4j2.ContextData"); + assertEquals(2, actual.getThreshold()); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java index dc308dc1c43..d706f30cf34 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java @@ -16,34 +16,31 @@ */ package org.apache.logging.log4j.core.impl; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests the ContextDataFactory class. */ +@SetSystemProperty(key = Log4jProperties.THREAD_CONTEXT_DATA_CLASS_NAME, value = "org.apache.logging.log4j.core.impl.FactoryTestStringMap") public class ContextDataFactoryPropertySetTest { @Test public void noArgReturnsSpecifiedImplIfPropertySpecified() throws Exception { - System.setProperty("log4j2.ContextData", FactoryTestStringMap.class.getName()); assertTrue(ContextDataFactory.createContextData() instanceof FactoryTestStringMap); - System.clearProperty("log4j2.ContextData"); } @Test public void intArgReturnsSpecifiedImplIfPropertySpecified() throws Exception { - System.setProperty("log4j2.ContextData", FactoryTestStringMap.class.getName()); assertTrue(ContextDataFactory.createContextData(2) instanceof FactoryTestStringMap); - System.clearProperty("log4j2.ContextData"); } @Test public void intArgSetsCapacityIfPropertySpecified() throws Exception { - System.setProperty("log4j2.ContextData", FactoryTestStringMap.class.getName()); final FactoryTestStringMap actual = (FactoryTestStringMap) ContextDataFactory.createContextData(2); assertEquals(2, actual.initialCapacity); - System.clearProperty("log4j2.ContextData"); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java index 5da12673aa4..8e94bb00d8c 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java @@ -16,12 +16,10 @@ */ package org.apache.logging.log4j.core.impl; -import java.lang.reflect.Field; - import org.apache.logging.log4j.util.SortedArrayStringMap; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the ContextDataFactory class. @@ -40,8 +38,6 @@ public void intArgReturnsSortedArrayStringMapIfNoPropertySpecified() throws Exce @Test public void intArgSetsCapacityIfNoPropertySpecified() throws Exception { final SortedArrayStringMap actual = (SortedArrayStringMap) ContextDataFactory.createContextData(2); - final Field thresholdField = SortedArrayStringMap.class.getDeclaredField("threshold"); - thresholdField.setAccessible(true); - assertEquals(2, thresholdField.getInt(actual)); + assertEquals(2, actual.getThreshold()); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMap.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMap.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMap.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMap.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMapWithoutIntConstructor.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMapWithoutIntConstructor.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMapWithoutIntConstructor.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/FactoryTestStringMapWithoutIntConstructor.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java index c8c8db11a6a..0f2d229addc 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java @@ -26,18 +26,18 @@ import org.apache.logging.log4j.util.BiConsumer; import org.apache.logging.log4j.util.TriConsumer; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the JdkMapAdapterStringMap class. */ public class JdkMapAdapterStringMapTest { - @Test(expected = NullPointerException.class) - public void testConstructorDisallowsNull() throws Exception { - new JdkMapAdapterStringMap(null); + @Test + public void testConstructorDisallowsNull() { + assertThrows(NullPointerException.class, () -> new JdkMapAdapterStringMap(null)); } @Test @@ -70,6 +70,7 @@ private byte[] serialize(final JdkMapAdapterStringMap data) throws IOException { return arr.toByteArray(); } + @SuppressWarnings("BanSerializableRead") private JdkMapAdapterStringMap deserialize(final byte[] binary) throws IOException, ClassNotFoundException { final ByteArrayInputStream inArr = new ByteArrayInputStream(binary); final ObjectInputStream in = new ObjectInputStream(inArr); @@ -114,7 +115,7 @@ public void testPutAll_overwritesSameKeys2() throws Exception { other.putValue("c", "cc"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cc", original.getValue("c")); @@ -139,7 +140,7 @@ public void testPutAll_nullKeyInLargeOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cORIG", original.getValue("c")); @@ -163,7 +164,7 @@ public void testPutAll_nullKeyInSmallOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("11", original.getValue("1")); @@ -187,7 +188,7 @@ public void testPutAll_nullKeyInSmallAdditional() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cORIG", original.getValue("c")); @@ -211,7 +212,7 @@ public void testPutAll_nullKeyInLargeAdditional() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("11", original.getValue("1")); @@ -236,7 +237,7 @@ public void testPutAll_nullKeyInBoth_LargeOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 7, original.size()); + assertEquals(7, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cORIG", original.getValue("c")); @@ -261,7 +262,7 @@ public void testPutAll_nullKeyInBoth_SmallOriginal() throws Exception { other.putValue("a", "aa"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("11", original.getValue("1")); @@ -284,7 +285,7 @@ public void testPutAll_overwritesSameKeys1() throws Exception { other.putValue("c", "cc"); original.putAll(other); - assertEquals("size after put other", 5, original.size()); + assertEquals(5, original.size(), "size after put other"); assertEquals("aa", original.getValue("a")); assertEquals("bORIG", original.getValue("b")); assertEquals("cc", original.getValue("c")); @@ -347,11 +348,11 @@ public void testPutAll_KeepsExistingValues() { original.putValue("a", "aaa"); original.putValue("b", "bbb"); original.putValue("c", "ccc"); - assertEquals("size", 3, original.size()); + assertEquals(3, original.size(), "size"); // add empty context data original.putAll(new JdkMapAdapterStringMap()); - assertEquals("size after put empty", 3, original.size()); + assertEquals(3, original.size(), "size after put empty"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -362,7 +363,7 @@ public void testPutAll_KeepsExistingValues() { other.putValue("3", "333"); original.putAll(other); - assertEquals("size after put other", 6, original.size()); + assertEquals(6, original.size(), "size after put other"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -378,11 +379,11 @@ public void testPutAll_sizePowerOfTwo() { original.putValue("b", "bbb"); original.putValue("c", "ccc"); original.putValue("d", "ddd"); - assertEquals("size", 4, original.size()); + assertEquals(4, original.size(), "size"); // add empty context data original.putAll(new JdkMapAdapterStringMap()); - assertEquals("size after put empty", 4, original.size()); + assertEquals(4, original.size(), "size after put empty"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -395,7 +396,7 @@ public void testPutAll_sizePowerOfTwo() { other.putValue("4", "444"); original.putAll(other); - assertEquals("size after put other", 8, original.size()); + assertEquals(8, original.size(), "size after put other"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -414,7 +415,7 @@ public void testPutAll_largeAddition() { original.putValue("b", "bbb"); original.putValue("c", "ccc"); original.putValue("d", "ddd"); - assertEquals("size", 5, original.size()); + assertEquals(5, original.size(), "size"); final JdkMapAdapterStringMap other = new JdkMapAdapterStringMap(); for (int i = 0 ; i < 500; i++) { @@ -423,7 +424,7 @@ public void testPutAll_largeAddition() { other.putValue(null, "otherVal"); original.putAll(other); - assertEquals("size after put other", 505, original.size()); + assertEquals(505, original.size(), "size after put other"); assertEquals("otherVal", original.getValue(null)); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); @@ -440,11 +441,11 @@ public void testPutAllSelfDoesNotModify() { original.putValue("a", "aaa"); original.putValue("b", "bbb"); original.putValue("c", "ccc"); - assertEquals("size", 3, original.size()); + assertEquals(3, original.size(), "size"); // putAll with self original.putAll(original); - assertEquals("size after put empty", 3, original.size()); + assertEquals(3, original.size(), "size after put empty"); assertEquals("aaa", original.getValue("a")); assertEquals("bbb", original.getValue("b")); assertEquals("ccc", original.getValue("c")); @@ -458,12 +459,7 @@ public void testNoConcurrentModificationBiConsumerPut() { original.putValue("c", "aaa"); original.putValue("d", "aaa"); original.putValue("e", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.putValue("c" + s, "other"); - } - }); + original.forEach((s, o) -> original.putValue("c" + s, "other")); } @Test @@ -474,12 +470,7 @@ public void testNoConcurrentModificationBiConsumerPutValue() { original.putValue("c", "aaa"); original.putValue("d", "aaa"); original.putValue("e", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.putValue("c" + s, "other"); - } - }); + original.forEach((s, o) -> original.putValue("c" + s, "other")); } @Test @@ -488,12 +479,7 @@ public void testNoConcurrentModificationBiConsumerRemove() { original.putValue("a", "aaa"); original.putValue("b", "aaa"); original.putValue("c", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.remove("a"); - } - }); + original.forEach((s, o) -> original.remove("a")); } @Test @@ -504,12 +490,7 @@ public void testNoConcurrentModificationBiConsumerClear() { original.putValue("c", "aaa"); original.putValue("d", "aaa"); original.putValue("e", "aaa"); - original.forEach(new BiConsumer() { - @Override - public void accept(final String s, final Object o) { - original.clear(); - } - }); + original.forEach((s, o) -> original.clear()); } @Test @@ -519,12 +500,7 @@ public void testNoConcurrentModificationTriConsumerPut() { original.putValue("b", "aaa"); original.putValue("d", "aaa"); original.putValue("e", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.putValue("c", "other"); - } - }, null); + original.forEach((s, o, o2) -> original.putValue("c", "other"), null); } @Test @@ -535,12 +511,7 @@ public void testNoConcurrentModificationTriConsumerPutValue() { original.putValue("c", "aaa"); original.putValue("d", "aaa"); original.putValue("e", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.putValue("c" + s, "other"); - } - }, null); + original.forEach((s, o, o2) -> original.putValue("c" + s, "other"), null); } @Test @@ -549,12 +520,7 @@ public void testNoConcurrentModificationTriConsumerRemove() { original.putValue("a", "aaa"); original.putValue("b", "aaa"); original.putValue("c", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.remove("a"); - } - }, null); + original.forEach((s, o, o2) -> original.remove("a"), null); } @Test @@ -564,12 +530,7 @@ public void testNoConcurrentModificationTriConsumerClear() { original.putValue("b", "aaa"); original.putValue("c", "aaa"); original.putValue("d", "aaa"); - original.forEach(new TriConsumer() { - @Override - public void accept(final String s, final Object o, final Object o2) { - original.clear(); - } - }, null); + original.forEach((s, o, o2) -> original.clear(), null); } @Test @@ -580,24 +541,24 @@ public void testInitiallyNotFrozen() { @Test public void testIsFrozenAfterCallingFreeze() { final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); - assertFalse("before freeze", original.isFrozen()); + assertFalse(original.isFrozen(), "before freeze"); original.freeze(); - assertTrue("after freeze", original.isFrozen()); + assertTrue(original.isFrozen(), "after freeze"); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testFreezeProhibitsPutValue() { final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); original.freeze(); - original.putValue("a", "aaa"); + assertThrows(UnsupportedOperationException.class, () -> original.putValue("a", "aaa")); } - @Test(expected = UnsupportedOperationException.class) + @Test public void testFreezeProhibitsRemove() { final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); original.putValue("b", "bbb"); original.freeze(); - original.remove("b"); // existing key: modifies the collection + assertThrows(UnsupportedOperationException.class, () -> original.remove("b")); // existing key: modifies the collection } @Test @@ -615,12 +576,12 @@ public void testFreezeAllowsRemoveIfEmpty() { original.remove("a"); // no exception } - @Test(expected = UnsupportedOperationException.class) + @Test public void testFreezeProhibitsClear() { final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); original.putValue("a", "aaa"); original.freeze(); - original.clear(); + assertThrows(UnsupportedOperationException.class, original::clear); } @Test @@ -674,27 +635,27 @@ public void testNullKeysCopiedToAsMap() { expected.put("3", "3value"); expected.put("c", "cvalue"); expected.put("d", "dvalue"); - assertEquals("initial", expected, original.toMap()); + assertEquals(expected, original.toMap(), "initial"); original.putValue(null, "nullvalue"); expected.put(null, "nullvalue"); assertEquals(6, original.size()); - assertEquals("with null key", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key"); original.putValue(null, "otherNullvalue"); expected.put(null, "otherNullvalue"); assertEquals(6, original.size()); - assertEquals("with null key value2", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key value2"); original.putValue(null, "nullvalue"); expected.put(null, "nullvalue"); assertEquals(6, original.size()); - assertEquals("with null key value1 again", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key value1 again"); original.putValue(null, "abc"); expected.put(null, "abc"); assertEquals(6, original.size()); - assertEquals("with null key value3", expected, original.toMap()); + assertEquals(expected, original.toMap(), "with null key value3"); } @Test @@ -706,11 +667,11 @@ public void testRemove() { original.remove("a"); assertEquals(0, original.size()); - assertNull("no a val", original.getValue("a")); + assertNull(original.getValue("a"), "no a val"); original.remove("B"); assertEquals(0, original.size()); - assertNull("no B val", original.getValue("B")); + assertNull(original.getValue("B"), "no B val"); } @Test @@ -732,11 +693,11 @@ public void testNullValuesArePreserved() { original.putValue("a", null); assertEquals(1, original.size()); - assertNull("no a val", original.getValue("a")); + assertNull(original.getValue("a"), "no a val"); original.putValue("B", null); assertEquals(2, original.size()); - assertNull("no B val", original.getValue("B")); + assertNull(original.getValue("B"), "no B val"); } @Test @@ -772,65 +733,65 @@ public void testClear() throws Exception { @Test public void testContainsKey() throws Exception { final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); - assertFalse("a", original.containsKey("a")); - assertFalse("B", original.containsKey("B")); - assertFalse("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertFalse(original.containsKey("a"), "a"); + assertFalse(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("a", "avalue"); - assertTrue("a", original.containsKey("a")); - assertFalse("B", original.containsKey("B")); - assertFalse("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertFalse(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("B", "Bvalue"); - assertTrue("a", original.containsKey("a")); - assertTrue("B", original.containsKey("B")); - assertFalse("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertFalse(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("3", "3value"); - assertTrue("a", original.containsKey("a")); - assertTrue("B", original.containsKey("B")); - assertTrue("3", original.containsKey("3")); - assertFalse("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertFalse(original.containsKey("A"), "A"); original.putValue("A", "AAA"); - assertTrue("a", original.containsKey("a")); - assertTrue("B", original.containsKey("B")); - assertTrue("3", original.containsKey("3")); - assertTrue("A", original.containsKey("A")); + assertTrue(original.containsKey("a"), "a"); + assertTrue(original.containsKey("B"), "B"); + assertTrue(original.containsKey("3"), "3"); + assertTrue(original.containsKey("A"), "A"); } @Test public void testSizeAndIsEmpty() throws Exception { final JdkMapAdapterStringMap original = new JdkMapAdapterStringMap(); assertEquals(0, original.size()); - assertTrue("initial", original.isEmpty()); + assertTrue(original.isEmpty(), "initial"); original.putValue("a", "avalue"); assertEquals(1, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.putValue("B", "Bvalue"); assertEquals(2, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.putValue("3", "3value"); assertEquals(3, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.remove("B"); assertEquals(2, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.remove("3"); assertEquals(1, original.size()); - assertFalse("size=" + original.size(), original.isEmpty()); + assertFalse(original.isEmpty(), "size=" + original.size()); original.remove("a"); assertEquals(0, original.size()); - assertTrue("size=" + original.size(), original.isEmpty()); + assertTrue(original.isEmpty(), "size=" + original.size()); } @Test @@ -847,7 +808,7 @@ public void accept(final String key, final String value) { // assertEquals("key", key, original.getKeyAt(count)); // assertEquals("val", value, original.getValueAt(count)); count++; - assertTrue("count should not exceed size but was " + count, count <= original.size()); + assertTrue(count <= original.size(), "count should not exceed size but was " + count); } }); } @@ -856,15 +817,12 @@ static class State { JdkMapAdapterStringMap data; int count; } - static TriConsumer COUNTER = new TriConsumer() { - @Override - public void accept(final String key, final String value, final JdkMapAdapterStringMapTest.State state) { + static TriConsumer COUNTER = (key, value, state) -> { // assertEquals("key", key, state.data.getKeyAt(state.count)); // assertEquals("val", value, state.data.getValueAt(state.count)); - state.count++; - assertTrue("count should not exceed size but was " + state.count, - state.count <= state.data.size()); - } + state.count++; + assertTrue( + state.count <= state.data.size(), "count should not exceed size but was " + state.count); }; @Test @@ -880,4 +838,4 @@ public void testForEachTriConsumer() throws Exception { assertEquals(state.count, original.size()); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jContextFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jContextFactoryTest.java new file mode 100644 index 00000000000..2d64c58e856 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jContextFactoryTest.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.logging.log4j.core.selector.BasicContextSelector; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.Test; + +public class Log4jContextFactoryTest { + + /** + * Tests whether the constructor parameters take priority over the default + * injector bindings. + */ + @Test + public void testParameterPriority() { + final Injector injector = DI.createInjector(); + injector.init(); + Log4jContextFactory factory = new Log4jContextFactory(new BasicContextSelector(injector)); + assertEquals(BasicContextSelector.class, factory.getSelector().getClass()); + factory = new Log4jContextFactory(factory); + assertEquals(Log4jContextFactory.class, factory.getShutdownCallbackRegistry().getClass()); + factory = new Log4jContextFactory(new BasicContextSelector(injector), factory); + assertEquals(BasicContextSelector.class, factory.getSelector().getClass()); + assertEquals(Log4jContextFactory.class, factory.getShutdownCallbackRegistry().getClass()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventNanoTimeTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventNanoTimeTest.java new file mode 100644 index 00000000000..35d1933015c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventNanoTimeTest.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.time.NanoClock; +import org.apache.logging.log4j.core.time.SystemNanoClock; +import org.apache.logging.log4j.core.time.internal.DummyNanoClock; +import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.plugins.Singleton; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class Log4jLogEventNanoTimeTest { + + @LoggerContextSource("NanoTimeToFileTest.xml") + static class DefaultTest { + @Test + void usesActualTimeByDefault(final LoggerContext context, @Named final ListAppender list) { + final Logger log = context.getLogger("com.foo.Bar"); + final long before = System.nanoTime(); + log.info("Use actual System.nanoTime()"); + final Configuration configuration = context.getConfiguration(); + assertThat(configuration.getNanoClock()).isInstanceOf(SystemNanoClock.class); + final List messages = list.getMessages(); + assertThat(messages).hasSize(1); + final String line = messages.get(0); + final String[] parts = line.split(" AND "); + assertThat(parts[0]).isEqualTo(parts[1]); + assertThat(parts[2]).isEqualTo("Use actual System.nanoTime()"); + final long loggedNanoTime = Long.parseLong(parts[0]); + assertThat(loggedNanoTime - before).isLessThan(TimeUnit.SECONDS.toNanos(1)); + } + } + + @LoggerContextSource("NanoTimeToFileTest.xml") + static class OverrideTest { + private static final long DUMMYNANOTIME = 123; + + @Singleton + @Factory + NanoClock nanoClock() { + return new DummyNanoClock(DUMMYNANOTIME); + } + + @Test + void useOverriddenTime(final LoggerContext context, @Named final ListAppender list) { + final Logger log = context.getLogger("com.foo.Bar"); + final Configuration configuration = context.getConfiguration(); + log.info("Use dummy nano clock"); + assertThat(configuration.getNanoClock()).isInstanceOf(DummyNanoClock.class); + final List messages = list.getMessages(); + assertThat(messages).hasSize(1); + final String line = messages.get(0); + final String[] parts = line.split(" AND "); + assertThat(parts[0]).isEqualTo(parts[1]).isEqualTo(String.valueOf(DUMMYNANOTIME)); + assertThat(parts[2]).isEqualTo("Use dummy nano clock"); + + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java index 9a8fa7caad8..54431b63e01 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java @@ -16,32 +16,13 @@ */ package org.apache.logging.log4j.core.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.lang.reflect.Field; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.ThreadContext.ContextStack; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.convert.Base64Converter; -import org.apache.logging.log4j.core.util.Clock; -import org.apache.logging.log4j.core.util.ClockFactory; -import org.apache.logging.log4j.core.util.ClockFactoryTest; -import org.apache.logging.log4j.core.util.DummyNanoClock; +import org.apache.logging.log4j.core.test.util.FixedTimeClock; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.ObjectMessage; import org.apache.logging.log4j.message.ReusableMessage; @@ -51,50 +32,34 @@ import org.apache.logging.log4j.util.SortedArrayStringMap; import org.apache.logging.log4j.util.StringMap; import org.apache.logging.log4j.util.Strings; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class Log4jLogEventTest { +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Field; +import java.util.Base64; - /** Helper class */ - public static class FixedTimeClock implements Clock { - public static final long FIXED_TIME = 1234567890L; - - /* - * (non-Javadoc) - * - * @see org.apache.logging.log4j.core.helpers.Clock#currentTimeMillis() - */ - @Override - public long currentTimeMillis() { - return FIXED_TIME; - } - } +import static org.junit.jupiter.api.Assertions.*; - @BeforeClass - public static void beforeClass() { - System.setProperty(ClockFactory.PROPERTY_NAME, FixedTimeClock.class.getName()); - } +public class Log4jLogEventTest { - @AfterClass - public static void afterClass() throws IllegalAccessException { - ClockFactoryTest.resetClocks(); - } + private static final Base64.Decoder decoder = Base64.getDecoder(); @Test public void testToImmutableSame() { - final LogEvent logEvent = new Log4jLogEvent(); - Assert.assertSame(logEvent, logEvent.toImmutable()); + final LogEvent logEvent = Log4jLogEvent.newBuilder().build(); + assertSame(logEvent, logEvent.toImmutable()); } @Test public void testToImmutableNotSame() { final LogEvent logEvent = new Log4jLogEvent.Builder().setMessage(new ReusableObjectMessage()).build(); final LogEvent immutable = logEvent.toImmutable(); - Assert.assertSame(logEvent, immutable); - Assert.assertFalse(immutable.getMessage() instanceof ReusableMessage); + assertSame(logEvent, immutable); + assertFalse(immutable.getMessage() instanceof ReusableMessage); } @Test @@ -114,7 +79,6 @@ public void testJavaIoSerializable() throws Exception { assertEquals(evt.getLevel(), evt2.getLevel()); assertEquals(evt.getLoggerName(), evt2.getLoggerName()); assertEquals(evt.getMarker(), evt2.getMarker()); - assertEquals(evt.getContextMap(), evt2.getContextMap()); assertEquals(evt.getContextData(), evt2.getContextData()); assertEquals(evt.getContextStack(), evt2.getContextStack()); assertEquals(evt.getMessage(), evt2.getMessage()); @@ -144,7 +108,6 @@ public void testJavaIoSerializableWithThrown() throws Exception { assertEquals(evt.getLevel(), evt2.getLevel()); assertEquals(evt.getLoggerName(), evt2.getLoggerName()); assertEquals(evt.getMarker(), evt2.getMarker()); - assertEquals(evt.getContextMap(), evt2.getContextMap()); assertEquals(evt.getContextData(), evt2.getContextData()); assertEquals(evt.getContextStack(), evt2.getContextStack()); assertEquals(evt.getMessage(), evt2.getMessage()); @@ -164,6 +127,7 @@ private byte[] serialize(final Log4jLogEvent event) throws IOException { return arr.toByteArray(); } + @SuppressWarnings("BanSerializableRead") private Log4jLogEvent deserialize(final byte[] binary) throws IOException, ClassNotFoundException { final ByteArrayInputStream inArr = new ByteArrayInputStream(binary); final ObjectInputStream in = new FilteredObjectInputStream(inArr); @@ -201,7 +165,7 @@ public void testJavaIoSerializableWithUnknownThrowable() throws Exception { final String base64 = "rO0ABXNyAD5vcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkxvZzRqTG9nRXZlbnQkTG9nRXZlbnRQcm94eYgtmn+yXsP9AwAQWgAMaXNFbmRPZkJhdGNoWgASaXNMb2NhdGlvblJlcXVpcmVkSgAIdGhyZWFkSWRJAA50aHJlYWRQcmlvcml0eUoACnRpbWVNaWxsaXNMAAtjb250ZXh0RGF0YXQAKUxvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovdXRpbC9TdHJpbmdNYXA7TAAMY29udGV4dFN0YWNrdAA1TG9yZy9hcGFjaGUvbG9nZ2luZy9sb2c0ai9UaHJlYWRDb250ZXh0JENvbnRleHRTdGFjaztMAAVsZXZlbHQAIExvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovTGV2ZWw7TAAKbG9nZ2VyRlFDTnQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACmxvZ2dlck5hbWVxAH4ABEwABm1hcmtlcnQAIUxvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovTWFya2VyO0wAEW1hcnNoYWxsZWRNZXNzYWdldAAbTGphdmEvcm1pL01hcnNoYWxsZWRPYmplY3Q7TAANbWVzc2FnZVN0cmluZ3EAfgAETAAGc291cmNldAAdTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMAAp0aHJlYWROYW1lcQB+AARMAAt0aHJvd25Qcm94eXQAM0xvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL1Rocm93YWJsZVByb3h5O3hwAAAAAAAAAAAAAQAAAAUAAAAASZYC0nNyADJvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGoudXRpbC5Tb3J0ZWRBcnJheVN0cmluZ01hcLA3yJFz7CvcAwACWgAJaW1tdXRhYmxlSQAJdGhyZXNob2xkeHABAAAAAXcIAAAAAQAAAAB4c3IAPm9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5UaHJlYWRDb250ZXh0JEVtcHR5VGhyZWFkQ29udGV4dFN0YWNrAAAAAAAAAAECAAB4cHNyAB5vcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouTGV2ZWwAAAAAABggGgIAA0kACGludExldmVsTAAEbmFtZXEAfgAETAANc3RhbmRhcmRMZXZlbHQALExvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovc3BpL1N0YW5kYXJkTGV2ZWw7eHAAAAGQdAAESU5GT35yACpvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouc3BpLlN0YW5kYXJkTGV2ZWwAAAAAAAAAABIAAHhyAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AARJTkZPdAAAdAAJc29tZS50ZXN0cHNyABlqYXZhLnJtaS5NYXJzaGFsbGVkT2JqZWN0fL0el+1j/D4CAANJAARoYXNoWwAIbG9jQnl0ZXN0AAJbQlsACG9iakJ5dGVzcQB+ABl4cJNvO+xwdXIAAltCrPMX+AYIVOACAAB4cAAAAGms7QAFc3IALm9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5tZXNzYWdlLlNpbXBsZU1lc3NhZ2WLdE0wYLeiqAMAAUwAB21lc3NhZ2V0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAA2FiY3h0AANhYmNwdAAEbWFpbnNyADFvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLlRocm93YWJsZVByb3h52cww1Zp7rPoCAAdJABJjb21tb25FbGVtZW50Q291bnRMAApjYXVzZVByb3h5cQB+AAhbABJleHRlbmRlZFN0YWNrVHJhY2V0AD9bTG9yZy9hcGFjaGUvbG9nZ2luZy9sb2c0ai9jb3JlL2ltcGwvRXh0ZW5kZWRTdGFja1RyYWNlRWxlbWVudDtMABBsb2NhbGl6ZWRNZXNzYWdlcQB+AARMAAdtZXNzYWdlcQB+AARMAARuYW1lcQB+AARbABFzdXBwcmVzc2VkUHJveGllc3QANFtMb3JnL2FwYWNoZS9sb2dnaW5nL2xvZzRqL2NvcmUvaW1wbC9UaHJvd2FibGVQcm94eTt4cAAAAABwdXIAP1tMb3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5FeHRlbmRlZFN0YWNrVHJhY2VFbGVtZW50O8rPiCOlx8+8AgAAeHAAAAAec3IAPG9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuRXh0ZW5kZWRTdGFja1RyYWNlRWxlbWVudOHez7rGtpAHAgACTAAOZXh0cmFDbGFzc0luZm90ADZMb3JnL2FwYWNoZS9sb2dnaW5nL2xvZzRqL2NvcmUvaW1wbC9FeHRlbmRlZENsYXNzSW5mbztMABFzdGFja1RyYWNlRWxlbWVudHEAfgAHeHBzcgA0b3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5FeHRlbmRlZENsYXNzSW5mbwAAAAAAAAABAgADWgAFZXhhY3RMAAhsb2NhdGlvbnEAfgAETAAHdmVyc2lvbnEAfgAEeHABdAANdGVzdC1jbGFzc2VzL3QAAT9zcgAbamF2YS5sYW5nLlN0YWNrVHJhY2VFbGVtZW50YQnFmiY23YUCAARJAApsaW5lTnVtYmVyTAAOZGVjbGFyaW5nQ2xhc3NxAH4ABEwACGZpbGVOYW1lcQB+AARMAAptZXRob2ROYW1lcQB+AAR4cAAAAKx0ADRvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkxvZzRqTG9nRXZlbnRUZXN0dAAWTG9nNGpMb2dFdmVudFRlc3QuamF2YXQAKnRlc3RKYXZhSW9TZXJpYWxpemFibGVXaXRoVW5rbm93blRocm93YWJsZXNxAH4AJXNxAH4AKABxAH4AK3QACDEuNy4wXzU1c3EAfgAs/////nQAJHN1bi5yZWZsZWN0Lk5hdGl2ZU1ldGhvZEFjY2Vzc29ySW1wbHQAHU5hdGl2ZU1ldGhvZEFjY2Vzc29ySW1wbC5qYXZhdAAHaW52b2tlMHNxAH4AJXNxAH4AKABxAH4AK3EAfgAzc3EAfgAsAAAAOXEAfgA1cQB+ADZ0AAZpbnZva2VzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAACt0AChzdW4ucmVmbGVjdC5EZWxlZ2F0aW5nTWV0aG9kQWNjZXNzb3JJbXBsdAAhRGVsZWdhdGluZ01ldGhvZEFjY2Vzc29ySW1wbC5qYXZhcQB+ADtzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAAl50ABhqYXZhLmxhbmcucmVmbGVjdC5NZXRob2R0AAtNZXRob2QuamF2YXEAfgA7c3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFydAAENC4xMnNxAH4ALAAAADJ0AClvcmcuanVuaXQucnVubmVycy5tb2RlbC5GcmFtZXdvcmtNZXRob2QkMXQAFEZyYW1ld29ya01ldGhvZC5qYXZhdAARcnVuUmVmbGVjdGl2ZUNhbGxzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAAx0ADNvcmcuanVuaXQuaW50ZXJuYWwucnVubmVycy5tb2RlbC5SZWZsZWN0aXZlQ2FsbGFibGV0ABdSZWZsZWN0aXZlQ2FsbGFibGUuamF2YXQAA3J1bnNxAH4AJXNxAH4AKAF0AA5qdW5pdC00LjEyLmphcnEAfgBJc3EAfgAsAAAAL3QAJ29yZy5qdW5pdC5ydW5uZXJzLm1vZGVsLkZyYW1ld29ya01ldGhvZHEAfgBMdAARaW52b2tlRXhwbG9zaXZlbHlzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAABF0ADJvcmcuanVuaXQuaW50ZXJuYWwucnVubmVycy5zdGF0ZW1lbnRzLkludm9rZU1ldGhvZHQAEUludm9rZU1ldGhvZC5qYXZhdAAIZXZhbHVhdGVzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAUV0AB5vcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXJ0ABFQYXJlbnRSdW5uZXIuamF2YXQAB3J1bkxlYWZzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAE50AChvcmcuanVuaXQucnVubmVycy5CbG9ja0pVbml0NENsYXNzUnVubmVydAAbQmxvY2tKVW5pdDRDbGFzc1J1bm5lci5qYXZhdAAIcnVuQ2hpbGRzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAADlxAH4AbXEAfgBucQB+AG9zcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAASJ0ACBvcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXIkM3EAfgBncQB+AFRzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAEd0ACBvcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXIkMXEAfgBndAAIc2NoZWR1bGVzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAASBxAH4AZnEAfgBndAALcnVuQ2hpbGRyZW5zcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAADpxAH4AZnEAfgBndAAKYWNjZXNzJDAwMHNxAH4AJXNxAH4AKAF0AA5qdW5pdC00LjEyLmphcnEAfgBJc3EAfgAsAAABDHQAIG9yZy5qdW5pdC5ydW5uZXJzLlBhcmVudFJ1bm5lciQycQB+AGdxAH4AYXNxAH4AJXNxAH4AKAF0AA5qdW5pdC00LjEyLmphcnEAfgBJc3EAfgAsAAAAGnQAMG9yZy5qdW5pdC5pbnRlcm5hbC5ydW5uZXJzLnN0YXRlbWVudHMuUnVuQmVmb3Jlc3QAD1J1bkJlZm9yZXMuamF2YXEAfgBhc3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFycQB+AElzcQB+ACwAAAAbdAAvb3JnLmp1bml0LmludGVybmFsLnJ1bm5lcnMuc3RhdGVtZW50cy5SdW5BZnRlcnN0AA5SdW5BZnRlcnMuamF2YXEAfgBhc3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFycQB+AElzcQB+ACwAAAFrcQB+AGZxAH4AZ3EAfgBUc3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFycQB+AElzcQB+ACwAAACJdAAab3JnLmp1bml0LnJ1bm5lci5KVW5pdENvcmV0AA5KVW5pdENvcmUuamF2YXEAfgBUc3EAfgAlc3EAfgAoAXQADGp1bml0LXJ0LmphcnEAfgArc3EAfgAsAAAAdXQAKGNvbS5pbnRlbGxpai5qdW5pdDQuSlVuaXQ0SWRlYVRlc3RSdW5uZXJ0ABlKVW5pdDRJZGVhVGVzdFJ1bm5lci5qYXZhdAATc3RhcnRSdW5uZXJXaXRoQXJnc3NxAH4AJXNxAH4AKAF0AAxqdW5pdC1ydC5qYXJxAH4AK3NxAH4ALAAAACpxAH4AqHEAfgCpcQB+AKpzcQB+ACVzcQB+ACgBdAAManVuaXQtcnQuamFycQB+ACtzcQB+ACwAAAEGdAAsY29tLmludGVsbGlqLnJ0LmV4ZWN1dGlvbi5qdW5pdC5KVW5pdFN0YXJ0ZXJ0ABFKVW5pdFN0YXJ0ZXIuamF2YXQAFnByZXBhcmVTdHJlYW1zQW5kU3RhcnRzcQB+ACVzcQB+ACgBdAAManVuaXQtcnQuamFycQB+ACtzcQB+ACwAAABUcQB+ALNxAH4AtHQABG1haW5zcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALP////5xAH4ANXEAfgA2cQB+ADdzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAADlxAH4ANXEAfgA2cQB+ADtzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAACtxAH4AP3EAfgBAcQB+ADtzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAAl5xAH4ARHEAfgBFcQB+ADtzcQB+ACVzcQB+ACgBdAALaWRlYV9ydC5qYXJxAH4AK3NxAH4ALAAAAJN0AC1jb20uaW50ZWxsaWoucnQuZXhlY3V0aW9uLmFwcGxpY2F0aW9uLkFwcE1haW50AAxBcHBNYWluLmphdmFxAH4AunQAFk9NRyBJJ3ZlIGJlZW4gZGVsZXRlZCFxAH4AzXQARW9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuTG9nNGpMb2dFdmVudFRlc3QkRGVsZXRlZEV4Y2VwdGlvbnVyADRbTG9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuVGhyb3dhYmxlUHJveHk7+u0B4IWi6zkCAAB4cAAAAAB4"; - final byte[] binaryDecoded = Base64Converter.parseBase64Binary(base64); + final byte[] binaryDecoded = decoder.decode(base64); final Log4jLogEvent evt2 = deserialize(binaryDecoded); assertEquals(loggerFQN, evt2.getLoggerFqcn()); @@ -210,7 +174,7 @@ public void testJavaIoSerializableWithUnknownThrowable() throws Exception { assertEquals(marker, evt2.getMarker()); assertEquals(msg, evt2.getMessage()); assertEquals(threadName, evt2.getThreadName()); - assertEquals(null, evt2.getThrown()); + assertNull(evt2.getThrown()); assertEquals(this.getClass().getName() + "$DeletedException", evt2.getThrownProxy().getName()); assertEquals(errorMessage, evt2.getThrownProxy().getMessage()); } @@ -224,40 +188,10 @@ public void testNullLevelReplacedWithOFF() throws Exception { @Test public void testTimestampGeneratedByClock() { - final LogEvent evt = Log4jLogEvent.newBuilder().build(); + final LogEvent evt = Log4jLogEvent.newBuilder().setClock(new FixedTimeClock()).build(); assertEquals(FixedTimeClock.FIXED_TIME, evt.getTimeMillis()); } - @Test - public void testInitiallyDummyNanoClock() { - assertTrue(Log4jLogEvent.getNanoClock() instanceof DummyNanoClock); - assertEquals("initial dummy nanotime", 0, Log4jLogEvent.getNanoClock().nanoTime()); - } - - @Test - public void testNanoTimeGeneratedByNanoClock() { - Log4jLogEvent.setNanoClock(new DummyNanoClock(123)); - verifyNanoTimeWithAllConstructors(123); - Log4jLogEvent.setNanoClock(new DummyNanoClock(87654)); - verifyNanoTimeWithAllConstructors(87654); - } - - @SuppressWarnings("deprecation") - private void verifyNanoTimeWithAllConstructors(final long expected) { - assertEquals(expected, Log4jLogEvent.getNanoClock().nanoTime()); - - assertEquals("No-arg constructor", expected, new Log4jLogEvent().getNanoTime()); - assertEquals("1-arg constructor", expected, new Log4jLogEvent(98).getNanoTime()); - assertEquals("6-arg constructor", expected, new Log4jLogEvent("l", null, "a", null, null, null).getNanoTime()); - assertEquals("7-arg constructor", expected, new Log4jLogEvent("l", null, "a", null, null, null, null) - .getNanoTime()); - assertEquals("11-arg constructor", expected, new Log4jLogEvent("l", null, "a", null, null, null, null, null, - null, null, 0).getNanoTime()); - assertEquals("12-arg factory method", expected, Log4jLogEvent.createEvent("l", null, "a", null, null, null, - null, null, null, null, null, 0).getNanoTime()); - } - - @SuppressWarnings("deprecation") @Test public void testBuilderCorrectlyCopiesAllEventAttributes() { final StringMap contextData = ContextDataFactory.createContextData(); @@ -289,8 +223,8 @@ public void testBuilderCorrectlyCopiesAllEventAttributes() { assertEquals(contextData, event.getContextData()); assertSame(contextStack, event.getContextStack()); - assertEquals(true, event.isEndOfBatch()); - assertEquals(true, event.isIncludeLocation()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); assertSame(Level.FATAL, event.getLevel()); assertSame(fqcn, event.getLoggerFqcn()); assertSame(name, event.getLoggerName()); @@ -303,8 +237,8 @@ public void testBuilderCorrectlyCopiesAllEventAttributes() { assertEquals(987654321L, event.getTimeMillis()); final LogEvent event2 = new Log4jLogEvent.Builder(event).build(); - assertEquals("copy constructor builder", event2, event); - assertEquals("same hashCode", event2.hashCode(), event.hashCode()); + assertEquals(event2, event, "copy constructor builder"); + assertEquals(event2.hashCode(), event.hashCode(), "same hashCode"); } @Test @@ -338,8 +272,8 @@ public void testBuilderCorrectlyCopiesAllEventAttributesInclContextData() { assertSame(contextData, event.getContextData()); assertSame(contextStack, event.getContextStack()); - assertEquals(true, event.isEndOfBatch()); - assertEquals(true, event.isIncludeLocation()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); assertSame(Level.FATAL, event.getLevel()); assertSame(fqcn, event.getLoggerFqcn()); assertSame(name, event.getLoggerName()); @@ -352,8 +286,8 @@ public void testBuilderCorrectlyCopiesAllEventAttributesInclContextData() { assertEquals(987654321L, event.getTimeMillis()); final LogEvent event2 = new Log4jLogEvent.Builder(event).build(); - assertEquals("copy constructor builder", event2, event); - assertEquals("same hashCode", event2.hashCode(), event.hashCode()); + assertEquals(event2, event, "copy constructor builder"); + assertEquals(event2.hashCode(), event.hashCode(), "same hashCode"); } @Test @@ -386,8 +320,8 @@ public void testBuilderCorrectlyCopiesMutableLogEvent() throws Exception { assertSame(contextData, event.getContextData()); assertSame(contextStack, event.getContextStack()); - assertEquals(true, event.isEndOfBatch()); - assertEquals(true, event.isIncludeLocation()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); assertSame(Level.FATAL, event.getLevel()); assertSame(fqcn, event.getLoggerFqcn()); assertSame(name, event.getLoggerName()); @@ -402,8 +336,8 @@ public void testBuilderCorrectlyCopiesMutableLogEvent() throws Exception { final LogEvent e2 = new Log4jLogEvent.Builder(event).build(); assertEquals(contextData, e2.getContextData()); assertSame(contextStack, e2.getContextStack()); - assertEquals(true, e2.isEndOfBatch()); - assertEquals(true, e2.isIncludeLocation()); + assertTrue(e2.isEndOfBatch()); + assertTrue(e2.isIncludeLocation()); assertSame(Level.FATAL, e2.getLevel()); assertSame(fqcn, e2.getLoggerFqcn()); assertSame(name, e2.getLoggerName()); @@ -420,10 +354,9 @@ public void testBuilderCorrectlyCopiesMutableLogEvent() throws Exception { final Field fieldSource = Log4jLogEvent.class.getDeclaredField("source"); fieldSource.setAccessible(true); final Object value = fieldSource.get(e2); - assertNull("source in copy", value); + assertNull(value, "source in copy"); } - @SuppressWarnings("deprecation") @Test public void testEquals() { final StringMap contextData = ContextDataFactory.createContextData(); @@ -456,8 +389,8 @@ public void testEquals() { assertEquals(contextData, event.getContextData()); assertSame(contextStack, event.getContextStack()); - assertEquals(true, event.isEndOfBatch()); - assertEquals(true, event.isIncludeLocation()); + assertTrue(event.isEndOfBatch()); + assertTrue(event.isIncludeLocation()); assertSame(Level.FATAL, event.getLevel()); assertSame(fqcn, event.getLoggerFqcn()); assertSame(name, event.getLoggerName()); @@ -470,13 +403,13 @@ public void testEquals() { assertEquals(987654321L, event.getTimeMillis()); final LogEvent event2 = builder(event).build(); - assertEquals("copy constructor builder", event2, event); - assertEquals("same hashCode", event2.hashCode(), event.hashCode()); + assertEquals(event2, event, "copy constructor builder"); + assertEquals(event2.hashCode(), event.hashCode(), "same hashCode"); assertEquals(contextData, event2.getContextData()); assertSame(contextStack, event2.getContextStack()); - assertEquals(true, event2.isEndOfBatch()); - assertEquals(true, event2.isIncludeLocation()); + assertTrue(event2.isEndOfBatch()); + assertTrue(event2.isIncludeLocation()); assertSame(Level.FATAL, event2.getLevel()); assertSame(fqcn, event2.getLoggerFqcn()); assertSame(name, event2.getLoggerName()); @@ -507,21 +440,13 @@ public void testEquals() { different("null fqcn", builder(event).setLoggerFqcn(null), event); different("different name", builder(event).setLoggerName("different"), event); - try { // TODO null logger name throws NPE in equals. Use Objects.requireNonNull in constructor? - different("null name", builder(event).setLoggerName(null), event); - fail("Expected NullPointerException"); - } catch (final NullPointerException ok) { - } + assertThrows(NullPointerException.class, () -> different("null name", builder(event).setLoggerName(null), event)); different("different marker", builder(event).setMarker(MarkerManager.getMarker("different")), event); different("null marker", builder(event).setMarker(null), event); different("different message", builder(event).setMessage(new ObjectMessage("different")), event); - try { // TODO null message throws NPE in equals(). Use Objects.requireNonNull in constructor? - different("null message", builder(event).setMessage(null), event); - fail("Expected NullPointerException"); - } catch (final NullPointerException ok) { - } + assertThrows(NullPointerException.class, () -> different("null message", builder(event).setMessage(null), event)); different("different nanoTime", builder(event).setNanoTime(135), event); different("different milliTime", builder(event).setTimeMillis(137), event); @@ -543,13 +468,13 @@ private static Log4jLogEvent.Builder builder(final LogEvent event) { private void different(final String reason, final Log4jLogEvent.Builder builder, final LogEvent event) { final LogEvent other = builder.build(); - assertNotEquals(reason, other, event); - assertNotEquals(reason + " hashCode", other.hashCode(), event.hashCode()); + assertNotEquals(other, event, reason); + assertNotEquals(other.hashCode(), event.hashCode(), reason + " hashCode"); } @Test public void testToString() { // Throws an NPE in 2.6.2 - assertNotNull(new Log4jLogEvent().toString()); + assertNotNull(Log4jLogEvent.newBuilder().build().toString()); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java new file mode 100644 index 00000000000..d6639b15755 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java @@ -0,0 +1,377 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.impl; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.ReusableMessageFactory; +import org.apache.logging.log4j.message.ReusableSimpleMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.FilteredObjectInputStream; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.apache.logging.log4j.spi.MutableThreadContextStack; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the MutableLogEvent class. + */ +public class MutableLogEventTest { + private static final StringMap CONTEXT_DATA = createContextData(); + private static final ThreadContext.ContextStack STACK = new MutableThreadContextStack(Arrays.asList("abc", "xyz")); + + static boolean useObjectInputStream = false; + + private static StringMap createContextData() { + final StringMap result = new SortedArrayStringMap(); + result.putValue("a", "1"); + result.putValue("b", "2"); + return result; + } + + @BeforeAll + public static void setupClass() { + try { + Class.forName("java.io.ObjectInputFilter"); + useObjectInputStream = true; + } catch (ClassNotFoundException ex) { + // Ignore the exception + } + } + + @Test + public void testToImmutable() { + final LogEvent logEvent = new MutableLogEvent(); + assertNotSame(logEvent, logEvent.toImmutable()); + } + + @Test + public void testInitFromCopiesAllFields() { +// private ThrowableProxy thrownProxy; + final Log4jLogEvent source = Log4jLogEvent.newBuilder() // + .setContextData(CONTEXT_DATA) // + .setContextStack(STACK) // + .setEndOfBatch(true) // + .setIncludeLocation(true) // + .setLevel(Level.FATAL) // + .setLoggerFqcn("a.b.c.d.e") // + .setLoggerName("my name is Logger") // + .setMarker(MarkerManager.getMarker("on your marks")) // + .setMessage(new SimpleMessage("msg in a bottle")) // + .setNanoTime(1234567) // + .setSource(new StackTraceElement("myclass", "mymethod", "myfile", 123)) // + .setThreadId(100).setThreadName("threadname").setThreadPriority(10) // + .setThrown(new RuntimeException("run")) // + .setTimeMillis(987654321) + .build(); + final MutableLogEvent mutable = new MutableLogEvent(); + mutable.initFrom(source); + assertEquals(CONTEXT_DATA, mutable.getContextData(), "contextMap"); + assertEquals(STACK, mutable.getContextStack(), "stack"); + assertTrue(mutable.isEndOfBatch(), "endOfBatch"); + assertTrue(mutable.isIncludeLocation(), "IncludeLocation()"); + assertEquals(Level.FATAL, mutable.getLevel(), "level"); + assertEquals(source.getLoggerFqcn(), mutable.getLoggerFqcn(), "LoggerFqcn()"); + assertEquals(source.getLoggerName(), mutable.getLoggerName(), "LoggerName"); + assertEquals(source.getMarker(), mutable.getMarker(), "marker"); + assertEquals(source.getMessage(), mutable.getMessage(), "msg"); + assertEquals(source.getNanoTime(), mutable.getNanoTime(), "nano"); + assertEquals(source.getSource(), mutable.getSource(), "src"); + assertEquals(source.getThreadId(), mutable.getThreadId(), "tid"); + assertEquals(source.getThreadName(), mutable.getThreadName(), "tname"); + assertEquals(source.getThreadPriority(), mutable.getThreadPriority(), "tpriority"); + assertEquals(source.getThrown(), mutable.getThrown(), "throwns"); + assertEquals(source.getThrownProxy(), mutable.getThrownProxy(), "proxy"); + assertEquals(source.getTimeMillis(), mutable.getTimeMillis(), "millis"); + } + + @Test + public void testInitFromReusableCopiesFormatString() { + Message message = ReusableMessageFactory.INSTANCE.newMessage("msg in a {}", "bottle"); + final Log4jLogEvent source = Log4jLogEvent.newBuilder() // + .setContextData(CONTEXT_DATA) // + .setContextStack(STACK) // + .setEndOfBatch(true) // + .setIncludeLocation(true) // + .setLevel(Level.FATAL) // + .setLoggerFqcn("a.b.c.d.e") // + .setLoggerName("my name is Logger") // + .setMarker(MarkerManager.getMarker("on your marks")) // + .setMessage(message) // + .setNanoTime(1234567) // + .setSource(new StackTraceElement("myclass", "mymethod", "myfile", 123)) // + .setThreadId(100).setThreadName("threadname").setThreadPriority(10) // + .setThrown(new RuntimeException("run")) // + .setTimeMillis(987654321) + .build(); + final MutableLogEvent mutable = new MutableLogEvent(); + mutable.initFrom(source); + assertEquals("msg in a {}", mutable.getFormat(), "format"); + assertEquals("msg in a bottle", mutable.getFormattedMessage(), "formatted"); + assertArrayEquals(new String[] {"bottle"}, mutable.getParameters(), "parameters"); + Message memento = mutable.memento(); + assertEquals("msg in a {}", memento.getFormat(), "format"); + assertEquals("msg in a bottle", memento.getFormattedMessage(), "formatted"); + assertArrayEquals(new String[] {"bottle"}, memento.getParameters(), "parameters"); + + Message eventMementoMessage = mutable.createMemento().getMessage(); + assertEquals("msg in a {}", eventMementoMessage.getFormat(), "format"); + assertEquals("msg in a bottle", eventMementoMessage.getFormattedMessage(), "formatted"); + assertArrayEquals(new String[] {"bottle"}, eventMementoMessage.getParameters(), "parameters"); + + Message log4JLogEventMessage = new Log4jLogEvent.Builder(mutable).build().getMessage(); + assertEquals("msg in a {}", log4JLogEventMessage.getFormat(), "format"); + assertEquals("msg in a bottle", log4JLogEventMessage.getFormattedMessage(), "formatted"); + assertArrayEquals(new String[] {"bottle"}, log4JLogEventMessage.getParameters(), "parameters"); + } + + @Test + public void testInitFromReusableObjectCopiesParameter() { + Object param = new Object(); + Message message = ReusableMessageFactory.INSTANCE.newMessage(param); + final Log4jLogEvent source = Log4jLogEvent.newBuilder() + .setContextData(CONTEXT_DATA) + .setContextStack(STACK) + .setEndOfBatch(true) + .setIncludeLocation(true) + .setLevel(Level.FATAL) + .setLoggerFqcn("a.b.c.d.e") + .setLoggerName("my name is Logger") + .setMarker(MarkerManager.getMarker("on your marks")) + .setMessage(message) + .setNanoTime(1234567) + .setSource(new StackTraceElement("myclass", "mymethod", "myfile", 123)) + .setThreadId(100).setThreadName("threadname") + .setThreadPriority(10) + .setThrown(new RuntimeException("run")) + .setTimeMillis(987654321) + .build(); + final MutableLogEvent mutable = new MutableLogEvent(); + mutable.initFrom(source); + assertNull(mutable.getFormat(), "format"); + assertEquals(param.toString(), mutable.getFormattedMessage(), "formatted"); + assertArrayEquals(new Object[] {param}, mutable.getParameters(), "parameters"); + Message memento = mutable.memento(); + assertNull(memento.getFormat(), "format"); + assertEquals(param.toString(), memento.getFormattedMessage(), "formatted"); + assertArrayEquals(new Object[] {param}, memento.getParameters(), "parameters"); + } + + @Test + public void testClear() { + final MutableLogEvent mutable = new MutableLogEvent(); + // initialize the event with an empty message + ReusableSimpleMessage simpleMessage = new ReusableSimpleMessage(); + simpleMessage.set(""); + mutable.setMessage(simpleMessage); + assertEquals(0, mutable.getContextData().size(), "context data"); + assertNull(mutable.getContextStack(), "context stack"); + assertFalse(mutable.isEndOfBatch(), "end of batch"); + assertFalse(mutable.isIncludeLocation(), "incl loc"); + assertSame(Level.OFF, mutable.getLevel(), "level"); + assertNull(mutable.getLoggerFqcn(), "fqcn"); + assertNull(mutable.getLoggerName(), "logger"); + assertNull(mutable.getMarker(), "marker"); + assertEquals(mutable, mutable.getMessage(), "msg"); + assertEquals(0, mutable.getNanoTime(), "nanoTm"); + assertEquals(0, mutable.getThreadId(), "tid"); + assertNull(mutable.getThreadName(), "tname"); + assertEquals(0, mutable.getThreadPriority(), "tpriority"); + assertNull(mutable.getThrown(), "thrwn"); + assertEquals(0, mutable.getTimeMillis(), "timeMs"); + + assertNull(mutable.getSource(), "source"); + assertNull(mutable.getThrownProxy(), "thrownProxy"); + + mutable.setContextData(CONTEXT_DATA); + mutable.setContextStack(STACK); + mutable.setEndOfBatch(true); + mutable.setIncludeLocation(true); + mutable.setLevel(Level.WARN); + mutable.setLoggerFqcn(getClass().getName()); + mutable.setLoggerName("loggername"); + mutable.setMarker(MarkerManager.getMarker("marked man")); + mutable.setMessage(new ParameterizedMessage("message in a {}", "bottle")); + mutable.setNanoTime(1234); + mutable.setThreadId(987); + mutable.setThreadName("ito"); + mutable.setThreadPriority(9); + mutable.setThrown(new Exception()); + mutable.setTimeMillis(56789); + + assertNotNull(mutable.getContextStack(), "context stack"); + assertTrue(mutable.isEndOfBatch(), "end of batch"); + assertTrue(mutable.isIncludeLocation(), "incl loc"); + assertNotNull(mutable.getLevel(), "level"); + assertNotNull(mutable.getLoggerFqcn(), "fqcn"); + assertNotNull(mutable.getLoggerName(), "logger"); + assertNotNull(mutable.getMarker(), "marker"); + assertEquals(new ParameterizedMessage("message in a {}", "bottle"), mutable.getMessage(), "msg"); + assertNotEquals(0, mutable.getNanoTime(), "nanoTm"); + assertNotEquals(0, mutable.getThreadId(), "tid"); + assertNotNull(mutable.getThreadName(), "tname"); + assertNotEquals(0, mutable.getThreadPriority(), "tpriority"); + assertNotNull(mutable.getThrown(), "thrwn"); + assertNotEquals(0, mutable.getTimeMillis(), "timeMs"); + + assertNotNull(mutable.getSource(), "source"); + assertNotNull(mutable.getThrownProxy(), "thrownProxy"); + + mutable.clear(); + assertEquals(0, mutable.getContextData().size(), "context map"); + assertNull(mutable.getContextStack(), "context stack"); + assertSame(Level.OFF, mutable.getLevel(), "level"); + assertNull(mutable.getLoggerFqcn(), "fqcn"); + assertNull(mutable.getLoggerName(), "logger"); + assertNull(mutable.getMarker(), "marker"); + assertEquals(mutable, mutable.getMessage(), "msg"); + assertNull(mutable.getThrown(), "thrwn"); + + assertNull(mutable.getSource(), "source"); + assertNull(mutable.getThrownProxy(), "thrownProxy"); + + // primitive fields are NOT reset: + assertTrue(mutable.isEndOfBatch(), "end of batch"); + assertTrue(mutable.isIncludeLocation(), "incl loc"); + assertNotEquals(0, mutable.getNanoTime(), "nanoTm"); + assertNotEquals(0, mutable.getTimeMillis(), "timeMs"); + + // thread-local fields are NOT reset: + assertNotEquals(0, mutable.getThreadId(), "tid"); + assertNotNull(mutable.getThreadName(), "tname"); + assertNotEquals(0, mutable.getThreadPriority(), "tpriority"); + } + + @Test + public void testJavaIoSerializable() throws Exception { + final MutableLogEvent evt = new MutableLogEvent(); + evt.setContextData(CONTEXT_DATA); + evt.setContextStack(STACK); + evt.setEndOfBatch(true); + evt.setIncludeLocation(true); + evt.setLevel(Level.WARN); + evt.setLoggerFqcn(getClass().getName()); + evt.setLoggerName("loggername"); + evt.setMarker(MarkerManager.getMarker("marked man")); + //evt.setMessage(new ParameterizedMessage("message in a {}", "bottle")); // TODO ParameterizedMessage serialization + evt.setMessage(new SimpleMessage("peace for all")); + evt.setNanoTime(1234); + evt.setThreadId(987); + evt.setThreadName("ito"); + evt.setThreadPriority(9); + evt.setTimeMillis(56789); + + final byte[] binary = serialize(evt); + final Log4jLogEvent evt2 = deserialize(binary); + + assertEquals(evt.getTimeMillis(), evt2.getTimeMillis()); + assertEquals(evt.getLoggerFqcn(), evt2.getLoggerFqcn()); + assertEquals(evt.getLevel(), evt2.getLevel()); + assertEquals(evt.getLoggerName(), evt2.getLoggerName()); + assertEquals(evt.getMarker(), evt2.getMarker()); + assertEquals(evt.getContextData(), evt2.getContextData()); + assertEquals(evt.getContextStack(), evt2.getContextStack()); + assertEquals(evt.getMessage(), evt2.getMessage()); + assertNotNull(evt2.getSource()); + assertEquals(evt.getSource(), evt2.getSource()); + assertEquals(evt.getThreadName(), evt2.getThreadName()); + assertNull(evt2.getThrown()); + assertNull(evt2.getThrownProxy()); + assertEquals(evt.isEndOfBatch(), evt2.isEndOfBatch()); + assertEquals(evt.isIncludeLocation(), evt2.isIncludeLocation()); + + assertNotEquals(evt.getNanoTime(), evt2.getNanoTime()); // nano time is transient in log4j log event + assertEquals(0, evt2.getNanoTime()); + } + + @Test + public void testJavaIoSerializableWithThrown() throws Exception { + final MutableLogEvent evt = new MutableLogEvent(); + evt.setContextData(CONTEXT_DATA); + evt.setContextStack(STACK); + evt.setEndOfBatch(true); + evt.setIncludeLocation(true); + evt.setLevel(Level.WARN); + evt.setLoggerFqcn(getClass().getName()); + evt.setLoggerName("loggername"); + evt.setMarker(MarkerManager.getMarker("marked man")); + //evt.setMessage(new ParameterizedMessage("message in a {}", "bottle")); // TODO ParameterizedMessage serialization + evt.setMessage(new SimpleMessage("peace for all")); + evt.setNanoTime(1234); + evt.setThreadId(987); + evt.setThreadName("ito"); + evt.setThreadPriority(9); + evt.setThrown(new Exception()); + evt.setTimeMillis(56789); + + final byte[] binary = serialize(evt); + final Log4jLogEvent evt2 = deserialize(binary); + + assertEquals(evt.getTimeMillis(), evt2.getTimeMillis()); + assertEquals(evt.getLoggerFqcn(), evt2.getLoggerFqcn()); + assertEquals(evt.getLevel(), evt2.getLevel()); + assertEquals(evt.getLoggerName(), evt2.getLoggerName()); + assertEquals(evt.getMarker(), evt2.getMarker()); + assertEquals(evt.getContextData(), evt2.getContextData()); + assertEquals(evt.getContextStack(), evt2.getContextStack()); + assertEquals(evt.getMessage(), evt2.getMessage()); + assertNotNull(evt2.getSource()); + assertEquals(evt.getSource(), evt2.getSource()); + assertEquals(evt.getThreadName(), evt2.getThreadName()); + assertNull(evt2.getThrown()); + assertNotNull(evt2.getThrownProxy()); + assertEquals(evt.getThrownProxy(), evt2.getThrownProxy()); + assertEquals(evt.isEndOfBatch(), evt2.isEndOfBatch()); + assertEquals(evt.isIncludeLocation(), evt2.isIncludeLocation()); + + assertNotEquals(evt.getNanoTime(), evt2.getNanoTime()); // nano time is transient in log4j log event + assertEquals(0, evt2.getNanoTime()); + } + + private byte[] serialize(final MutableLogEvent event) throws IOException { + final ByteArrayOutputStream arr = new ByteArrayOutputStream(); + final ObjectOutputStream out = new ObjectOutputStream(arr); + out.writeObject(event); + return arr.toByteArray(); + } + + @SuppressWarnings("BanSerializableRead") + private Log4jLogEvent deserialize(final byte[] binary) throws IOException, ClassNotFoundException { + final ByteArrayInputStream inArr = new ByteArrayInputStream(binary); + final ObjectInputStream in = useObjectInputStream ? new ObjectInputStream(inArr) : + new FilteredObjectInputStream(inArr); + final Log4jLogEvent result = (Log4jLogEvent) in.readObject(); + return result; + } + + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromThrowableMessageTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromThrowableMessageTest.java new file mode 100644 index 00000000000..22362d3ecca --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromThrowableMessageTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.CoreLoggerContexts; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Test for LOG4J2-2368. + */ +public class NestedLoggingFromThrowableMessageTest { + + private static File file1 = new File("target/NestedLoggerTest1.log"); + private static File file2 = new File("target/NestedLoggerTest2.log"); + + @BeforeClass + public static void beforeClass() { + file1.delete(); + file2.delete(); + } + + @Rule + public LoggerContextRule context = new LoggerContextRule("log4j-nested-logging-throwable-message.xml"); + private Logger logger; + + @Before + public void before() { + logger = context.getLogger(NestedLoggingFromThrowableMessageTest.class); + } + + class ThrowableLogsInGetMessage extends RuntimeException { + + @Override + public String getMessage() { + logger.info("Logging in getMessage"); + return "message"; + } + } + + @Test + public void testNestedLoggingInLastArgument() throws Exception { + logger.error("Test", new ThrowableLogsInGetMessage()); + // stop async thread + CoreLoggerContexts.stopLoggerContext(false, file1); + CoreLoggerContexts.stopLoggerContext(false, file2); + + Set lines1 = readUniqueLines(file1); + Set lines2 = readUniqueLines(file2); + + assertEquals("Expected the same data from both appenders", lines1, lines2); + assertEquals(2, lines1.size()); + assertTrue(lines1.contains("INFO NestedLoggingFromThrowableMessageTest Logging in getMessage ")); + assertTrue(lines1.contains("ERROR NestedLoggingFromThrowableMessageTest Test message")); + } + + private static Set readUniqueLines(File input) throws IOException { + Set lines = new HashSet<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(input)))) { + String line; + while ((line = reader.readLine()) != null) { + assertTrue("Read duplicate line: " + line, lines.add(line)); + } + } + return lines; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java index d0c35cb5315..9a9474f5248 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java @@ -16,18 +16,17 @@ */ package org.apache.logging.log4j.core.impl; -import java.util.List; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; -import static org.junit.Assert.*; +import java.util.List; + +import static org.junit.Assert.assertEquals; /** * There are two logger.info() calls here. @@ -46,11 +45,6 @@ */ public class NestedLoggingFromToStringTest { - @BeforeClass - public static void beforeClass() { - System.setProperty("log4j2.is.webapp", "false"); - } - @Rule public LoggerContextRule context = new LoggerContextRule("log4j-sync-to-list.xml"); private ListAppender listAppender; @@ -135,4 +129,4 @@ public void testDoublyNestedLogging() { assertEquals(expect4, list.get(3)); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java new file mode 100644 index 00000000000..ad44828a541 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the ReusableLogEventFactory class. + */ +public class ReusableLogEventFactoryTest { + + private final Injector injector = DI.createInjector(); + + @BeforeEach + void setUp() { + injector.init(); + } + + @Test + public void testCreateEventReturnsDifferentInstanceIfNotReleased() throws Exception { + final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); + final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); + final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + assertNotSame(event1, event2); + ReusableLogEventFactory.release(event1); + ReusableLogEventFactory.release(event2); + } + + @Test + public void testCreateEventReturnsSameInstance() throws Exception { + final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); + final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); + ReusableLogEventFactory.release(event1); + final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + assertSame(event1, event2); + + ReusableLogEventFactory.release(event2); + final LogEvent event3 = callCreateEvent(factory, "c", Level.INFO, new SimpleMessage("123"), null); + assertSame(event2, event3); + ReusableLogEventFactory.release(event3); + } + + @Test + public void testCreateEventOverwritesFields() throws Exception { + final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); + final LogEvent event1 = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); + assertEquals("a", event1.getLoggerName(), "logger"); + assertEquals(Level.DEBUG, event1.getLevel(), "level"); + assertEquals(new SimpleMessage("abc"), event1.getMessage(), "msg"); + + ReusableLogEventFactory.release(event1); + final LogEvent event2 = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + assertSame(event1, event2); + + assertEquals("b", event1.getLoggerName(), "logger"); + assertEquals(Level.INFO, event1.getLevel(), "level"); + assertEquals(new SimpleMessage("xyz"), event1.getMessage(), "msg"); + assertEquals("b", event2.getLoggerName(), "logger"); + assertEquals(Level.INFO, event2.getLevel(), "level"); + assertEquals(new SimpleMessage("xyz"), event2.getMessage(), "msg"); + } + + private LogEvent callCreateEvent(final ReusableLogEventFactory factory, final String logger, final Level level, + final Message message, final Throwable thrown) { + return factory.createEvent(logger, null, getClass().getName(), level, message, null, thrown); + } + + @Test + public void testCreateEventReturnsThreadLocalInstance() throws Exception { + final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); + final LogEvent[] event1 = new LogEvent[1]; + final LogEvent[] event2 = new LogEvent[1]; + final Thread t1 = new Thread("THREAD 1") { + @Override + public void run() { + event1[0] = callCreateEvent(factory, "a", Level.DEBUG, new SimpleMessage("abc"), null); + } + }; + final Thread t2 = new Thread("Thread 2") { + @Override + public void run() { + event2[0] = callCreateEvent(factory, "b", Level.INFO, new SimpleMessage("xyz"), null); + } + }; + t1.start(); + t2.start(); + t1.join(); + t2.join(); + assertNotNull(event1[0]); + assertNotNull(event2[0]); + assertNotSame(event1[0], event2[0]); + assertEquals("a", event1[0].getLoggerName(), "logger"); + assertEquals(Level.DEBUG, event1[0].getLevel(), "level"); + assertEquals(new SimpleMessage("abc"), event1[0].getMessage(), "msg"); + assertEquals("THREAD 1", event1[0].getThreadName(), "thread name"); + assertEquals(t1.getId(), event1[0].getThreadId(), "tid"); + + assertEquals("b", event2[0].getLoggerName(), "logger"); + assertEquals(Level.INFO, event2[0].getLevel(), "level"); + assertEquals(new SimpleMessage("xyz"), event2[0].getMessage(), "msg"); + assertEquals("Thread 2", event2[0].getThreadName(), "thread name"); + assertEquals(t2.getId(), event2[0].getThreadId(), "tid"); + ReusableLogEventFactory.release(event1[0]); + ReusableLogEventFactory.release(event2[0]); + } + + @Test + public void testCreateEventInitFieldsProperly() throws Exception { + final ReusableLogEventFactory factory = injector.getInstance(ReusableLogEventFactory.class); + final LogEvent event = callCreateEvent(factory, "logger", Level.INFO, new SimpleMessage("xyz"), null); + try { + assertNotNull(event.getContextData()); + assertNotNull(event.getContextStack()); + } finally { + ReusableLogEventFactory.release(event); + } + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java new file mode 100644 index 00000000000..78f071df37a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import java.util.Collection; +import java.util.concurrent.ExecutionException; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.ContextDataInjector; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; +import org.apache.logging.log4j.test.ThreadContextUtilityClass; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static java.util.Arrays.asList; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.apache.logging.log4j.ThreadContext.getThreadContextMap; +import static org.apache.logging.log4j.core.impl.ContextDataInjectorFactory.createInjector; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@RunWith(Parameterized.class) +public class ThreadContextDataInjectorTest { + @Parameters(name = "{0}") + public static Collection threadContextMapClassNames() { + return asList(new String[][] { + { "org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap", "org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap" }, + { "org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap", "org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap" }, + { "org.apache.logging.log4j.spi.DefaultThreadContextMap", null } + }); + } + + @Parameter + public String threadContextMapClassName; + + @Parameter(value = 1) + public String readOnlythreadContextMapClassName; + + @Before + public void before() { + System.setProperty(LoggingSystemProperties.THREAD_CONTEXT_MAP_CLASS, threadContextMapClassName); + } + + @After + public void after() { + ThreadContext.remove("foo"); + ThreadContext.remove("baz"); + System.clearProperty(LoggingSystemProperties.THREAD_CONTEXT_MAP_CLASS); + System.clearProperty(LoggingSystemProperties.THREAD_CONTEXT_MAP_INHERITABLE); + } + + private void testContextDataInjector() { + ReadOnlyThreadContextMap readOnlythreadContextMap = getThreadContextMap(); + assertThat("thread context map class name", + (readOnlythreadContextMap == null) ? null : readOnlythreadContextMap.getClass().getName(), + is(equalTo(readOnlythreadContextMapClassName))); + + ContextDataInjector contextDataInjector = createInjector(); + StringMap stringMap = contextDataInjector.injectContextData(null, new SortedArrayStringMap()); + + assertThat("thread context map", ThreadContext.getContext(), allOf(hasEntry("foo", "bar"), not(hasKey("baz")))); + assertThat("context map", stringMap.toMap(), allOf(hasEntry("foo", "bar"), not(hasKey("baz")))); + + if (!stringMap.isFrozen()) { + stringMap.clear(); + assertThat("thread context map", ThreadContext.getContext(), allOf(hasEntry("foo", "bar"), not(hasKey("baz")))); + assertThat("context map", stringMap.toMap().entrySet(), is(empty())); + } + + ThreadContext.put("foo", "bum"); + ThreadContext.put("baz", "bam"); + + assertThat("thread context map", ThreadContext.getContext(), allOf(hasEntry("foo", "bum"), hasEntry("baz", "bam"))); + if (stringMap.isFrozen()) { + assertThat("context map", stringMap.toMap(), allOf(hasEntry("foo", "bar"), not(hasKey("baz")))); + } else { + assertThat("context map", stringMap.toMap().entrySet(), is(empty())); + } + } + + private void prepareThreadContext(boolean isThreadContextMapInheritable) { + System.setProperty(LoggingSystemProperties.THREAD_CONTEXT_MAP_INHERITABLE, Boolean.toString(isThreadContextMapInheritable)); + ((PropertiesUtil) PropertiesUtil.getProperties()).reload(); + ThreadContextUtilityClass.reset(); + ThreadContext.remove("baz"); + ThreadContext.put("foo", "bar"); + } + + @Test + public void testThreadContextImmutability() { + prepareThreadContext(false); + testContextDataInjector(); + } + + @Test + public void testInheritableThreadContextImmutability() throws Throwable { + prepareThreadContext(true); + try { + newSingleThreadExecutor().submit(new Runnable() { + @Override + public void run() { + testContextDataInjector(); + } + }).get(); + } catch (ExecutionException ee) { + throw ee.getCause(); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java similarity index 82% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java index d099da1d7dc..4b0a55aebd8 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableFormatOptionsTest.java @@ -16,10 +16,8 @@ */ package org.apache.logging.log4j.core.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -27,8 +25,9 @@ import org.apache.logging.log4j.core.pattern.TextRenderer; import org.apache.logging.log4j.util.Strings; import org.fusesource.jansi.AnsiRenderer.Code; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * Unit tests for {@code ThrowableFormatOptions}. @@ -37,7 +36,7 @@ public final class ThrowableFormatOptionsTest { /** * Runs a given test comparing against the expected values. - * + * * @param options * The list of options to parse. * @param expectedLines @@ -50,15 +49,15 @@ public final class ThrowableFormatOptionsTest { private static ThrowableFormatOptions test(final String[] options, final int expectedLines, final String expectedSeparator, final List expectedPackages) { final ThrowableFormatOptions tfo = ThrowableFormatOptions.newInstance(options); - assertEquals("getLines", expectedLines, tfo.getLines()); - assertEquals("getSeparator", expectedSeparator, tfo.getSeparator()); - assertEquals("getPackages", expectedPackages, tfo.getIgnorePackages()); - assertEquals("allLines", expectedLines == Integer.MAX_VALUE, tfo.allLines()); - assertEquals("anyLines", expectedLines != 0, tfo.anyLines()); - assertEquals("minLines", 0, tfo.minLines(0)); - assertEquals("minLines", expectedLines, tfo.minLines(Integer.MAX_VALUE)); - assertEquals("hasPackages", expectedPackages != null && !expectedPackages.isEmpty(), tfo.hasPackages()); - assertNotNull("toString", tfo.toString()); + assertEquals(expectedLines, tfo.getLines(), "getLines"); + assertEquals(expectedSeparator, tfo.getSeparator(), "getSeparator"); + assertEquals(expectedPackages, tfo.getIgnorePackages(), "getPackages"); + assertEquals(expectedLines == Integer.MAX_VALUE, tfo.allLines(), "allLines"); + assertEquals(expectedLines != 0, tfo.anyLines(), "anyLines"); + assertEquals(0, tfo.minLines(0), "minLines"); + assertEquals(expectedLines, tfo.minLines(Integer.MAX_VALUE), "minLines"); + assertEquals(expectedPackages != null && !expectedPackages.isEmpty(), tfo.hasPackages(), "hasPackages"); + assertNotNull(tfo.toString(), "toString"); return tfo; } @@ -124,13 +123,13 @@ public void testFullAnsiEmptyConfig() { private void testFullAnsiEmptyConfig(final ThrowableFormatOptions tfo) { final TextRenderer textRenderer = tfo.getTextRenderer(); - Assert.assertNotNull(textRenderer); - Assert.assertTrue(textRenderer instanceof JAnsiTextRenderer); + assertNotNull(textRenderer); + assertTrue(textRenderer instanceof JAnsiTextRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; final Map styleMap = jansiRenderer.getStyleMap(); // We have defaults - Assert.assertFalse(styleMap.isEmpty()); - Assert.assertNotNull(styleMap.get("Name")); + assertFalse(styleMap.isEmpty()); + assertNotNull(styleMap.get("Name")); } /** @@ -141,11 +140,11 @@ public void testFullAnsiWithCustomStyle() { final ThrowableFormatOptions tfo = test(new String[] { "full", "ansi(Warning=red)" }, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); final TextRenderer textRenderer = tfo.getTextRenderer(); - Assert.assertNotNull(textRenderer); - Assert.assertTrue(textRenderer instanceof JAnsiTextRenderer); + assertNotNull(textRenderer); + assertTrue(textRenderer instanceof JAnsiTextRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; final Map styleMap = jansiRenderer.getStyleMap(); - Assert.assertArrayEquals(new Code[] { Code.RED }, styleMap.get("Warning")); + assertArrayEquals(new Code[] { Code.RED }, styleMap.get("Warning")); } /** @@ -156,13 +155,13 @@ public void testFullAnsiWithCustomStyles() { final ThrowableFormatOptions tfo = test(new String[] { "full", "ansi(Warning=red Key=blue Value=cyan)" }, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); final TextRenderer textRenderer = tfo.getTextRenderer(); - Assert.assertNotNull(textRenderer); - Assert.assertTrue(textRenderer instanceof JAnsiTextRenderer); + assertNotNull(textRenderer); + assertTrue(textRenderer instanceof JAnsiTextRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; final Map styleMap = jansiRenderer.getStyleMap(); - Assert.assertArrayEquals(new Code[] { Code.RED }, styleMap.get("Warning")); - Assert.assertArrayEquals(new Code[] { Code.BLUE }, styleMap.get("Key")); - Assert.assertArrayEquals(new Code[] { Code.CYAN }, styleMap.get("Value")); + assertArrayEquals(new Code[] { Code.RED }, styleMap.get("Warning")); + assertArrayEquals(new Code[] { Code.BLUE }, styleMap.get("Key")); + assertArrayEquals(new Code[] { Code.CYAN }, styleMap.get("Value")); } /** @@ -174,13 +173,13 @@ public void testFullAnsiWithCustomComplexStyles() { new String[] { "full", "ansi(Warning=red Key=blue,bg_red Value=cyan,bg_black,underline)" }, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, null); final TextRenderer textRenderer = tfo.getTextRenderer(); - Assert.assertNotNull(textRenderer); - Assert.assertTrue(textRenderer instanceof JAnsiTextRenderer); + assertNotNull(textRenderer); + assertTrue(textRenderer instanceof JAnsiTextRenderer); final JAnsiTextRenderer jansiRenderer = (JAnsiTextRenderer) textRenderer; final Map styleMap = jansiRenderer.getStyleMap(); - Assert.assertArrayEquals(new Code[] { Code.RED }, styleMap.get("Warning")); - Assert.assertArrayEquals(new Code[] { Code.BLUE, Code.BG_RED }, styleMap.get("Key")); - Assert.assertArrayEquals(new Code[] { Code.CYAN, Code.BG_BLACK, Code.UNDERLINE }, styleMap.get("Value")); + assertArrayEquals(new Code[] { Code.RED }, styleMap.get("Warning")); + assertArrayEquals(new Code[] { Code.BLUE, Code.BG_RED }, styleMap.get("Key")); + assertArrayEquals(new Code[] { Code.CYAN, Code.BG_BLACK, Code.UNDERLINE }, styleMap.get("Value")); } /** @@ -253,7 +252,8 @@ public void testFullAndSeparator() { */ @Test public void testFullAndFiltersAndSeparator() { - test(new String[] { "full", "filters(org.junit)", "separator(|)" }, Integer.MAX_VALUE, "|", Arrays.asList("org.junit")); + test(new String[] { "full", "filters(org.junit)", "separator(|)" }, Integer.MAX_VALUE, "|", + Collections.singletonList("org.junit")); } /** @@ -286,7 +286,7 @@ public void testDepthAndSeparator() { @Test public void testFilters() { test(new String[] { "filters(packages)" }, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, - Arrays.asList("packages")); + Collections.singletonList("packages")); } /** @@ -312,7 +312,7 @@ public void testFiltersAsMultiplePackages() { @Test public void testFullAndFilters() { test(new String[] { "full", "filters(packages)" }, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, - Arrays.asList("packages")); + Collections.singletonList("packages")); } /** @@ -320,7 +320,7 @@ public void testFullAndFilters() { */ @Test public void testNoneAndFilters() { - test(new String[] { "none", "filters(packages)" }, 0, Strings.LINE_SEPARATOR, Arrays.asList("packages")); + test(new String[] { "none", "filters(packages)" }, 0, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } /** @@ -328,7 +328,7 @@ public void testNoneAndFilters() { */ @Test public void testShortAndFilters() { - test(new String[] { "short", "filters(packages)" }, 2, Strings.LINE_SEPARATOR, Arrays.asList("packages")); + test(new String[] { "short", "filters(packages)" }, 2, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } /** @@ -336,7 +336,7 @@ public void testShortAndFilters() { */ @Test public void testDepthAndFilters() { - test(new String[] { "10", "filters(packages)" }, 10, Strings.LINE_SEPARATOR, Arrays.asList("packages")); + test(new String[] { "10", "filters(packages)" }, 10, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } /** @@ -345,7 +345,7 @@ public void testDepthAndFilters() { @Test public void testFullAndSeparatorAndFilter() { test(new String[] { "full", "separator(|)", "filters(packages)" }, Integer.MAX_VALUE, "|", - Arrays.asList("packages")); + Collections.singletonList("packages")); } /** @@ -362,7 +362,7 @@ public void testFullAndSeparatorAndFilters() { */ @Test public void testNoneAndSeparatorAndFilters() { - test(new String[] { "none", "separator(|)", "filters(packages)" }, 0, "|", Arrays.asList("packages")); + test(new String[] { "none", "separator(|)", "filters(packages)" }, 0, "|", Collections.singletonList("packages")); } /** @@ -370,7 +370,7 @@ public void testNoneAndSeparatorAndFilters() { */ @Test public void testShortAndSeparatorAndFilters() { - test(new String[] { "short", "separator(|)", "filters(packages)" }, 2, "|", Arrays.asList("packages")); + test(new String[] { "short", "separator(|)", "filters(packages)" }, 2, "|", Collections.singletonList("packages")); } /** @@ -378,7 +378,7 @@ public void testShortAndSeparatorAndFilters() { */ @Test public void testDepthAndSeparatorAndFilters() { - test(new String[] { "10", "separator(|)", "filters(packages)" }, 10, "|", Arrays.asList("packages")); + test(new String[] { "10", "separator(|)", "filters(packages)" }, 10, "|", Collections.singletonList("packages")); } /** @@ -387,7 +387,7 @@ public void testDepthAndSeparatorAndFilters() { @Test public void testSingleOptionFullAndFilters() { test(new String[] { "full,filters(packages)" }, Integer.MAX_VALUE, Strings.LINE_SEPARATOR, - Arrays.asList("packages")); + Collections.singletonList("packages")); } /** @@ -395,7 +395,7 @@ public void testSingleOptionFullAndFilters() { */ @Test public void testSingleOptionNoneAndFilters() { - test(new String[] { "none,filters(packages)" }, 0, Strings.LINE_SEPARATOR, Arrays.asList("packages")); + test(new String[] { "none,filters(packages)" }, 0, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } /** @@ -403,7 +403,7 @@ public void testSingleOptionNoneAndFilters() { */ @Test public void testSingleOptionShortAndFilters() { - test(new String[] { "short,filters(packages)" }, 2, Strings.LINE_SEPARATOR, Arrays.asList("packages")); + test(new String[] { "short,filters(packages)" }, 2, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } /** @@ -411,7 +411,7 @@ public void testSingleOptionShortAndFilters() { */ @Test public void testSingleOptionDepthAndFilters() { - test(new String[] { "10,filters(packages)" }, 10, Strings.LINE_SEPARATOR, Arrays.asList("packages")); + test(new String[] { "10,filters(packages)" }, 10, Strings.LINE_SEPARATOR, Collections.singletonList("packages")); } /** diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyRendererTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyRendererTest.java new file mode 100644 index 00000000000..62725dab908 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyRendererTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import java.util.ArrayList; + +import org.apache.logging.log4j.core.pattern.PlainTextRenderer; +import org.junit.jupiter.api.Test; + +/** + * Tests ThrowableProxyRenderer. + */ +public class ThrowableProxyRendererTest { + + @Test + public void test_formatExtendedStackTraceTo() { + ThrowableProxyRenderer.formatExtendedStackTraceTo(new ThrowableProxy(), new StringBuilder(), new ArrayList<>(), + new PlainTextRenderer(), "", System.lineSeparator()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java index 813a5b76464..e1b3179a1a3 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java @@ -16,12 +16,20 @@ */ package org.apache.logging.log4j.core.impl; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.pattern.PlainTextRenderer; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -32,30 +40,21 @@ import java.nio.channels.ServerSocketChannel; import java.security.Permission; import java.security.SecureRandom; +import java.util.ArrayDeque; +import java.util.Base64; +import java.util.Deque; import java.util.HashMap; import java.util.Map; -import java.util.Stack; -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.spec.IvParameterSpec; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.convert.Base64Converter; -import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; -import org.apache.logging.log4j.core.jackson.Log4jXmlObjectMapper; -import org.apache.logging.log4j.core.pattern.PlainTextRenderer; -import org.apache.logging.log4j.util.Strings; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; /** * */ public class ThrowableProxyTest { + private static final Base64.Decoder decoder = Base64.getDecoder(); + public static class AlwaysThrowsError { static { if (true) { @@ -69,6 +68,7 @@ static class Fixture { ThrowableProxy proxy = new ThrowableProxy(new IOException("test")); } + @SuppressWarnings("BanSerializableRead") private ThrowableProxy deserialize(final byte[] binary) throws IOException, ClassNotFoundException { final ByteArrayInputStream inArr = new ByteArrayInputStream(binary); final ObjectInputStream in = new ObjectInputStream(inArr); @@ -113,45 +113,31 @@ private void testIoContainer(final ObjectMapper objectMapper ) throws IOExceptio assertEquals(expected.proxy, actual.proxy); } - @Test - public void testIoContainerAsJson() throws IOException { - testIoContainer(new Log4jJsonObjectMapper()); - } - - @Test - public void testIoContainerAsXml() throws IOException { - testIoContainer(new Log4jXmlObjectMapper()); - } - /** * Attempts to instantiate a class that cannot initialize and then logs the stack trace of the Error. The logger * must not fail when using {@link ThrowableProxy} to inspect the frames of the stack trace. */ @Test public void testLogStackTraceWithClassThatCannotInitialize() { - try { - // Try to create the object, which will always fail during class initialization - final AlwaysThrowsError error = new AlwaysThrowsError(); - - // If the error was not triggered then fail - fail("Test did not throw expected error: " + error); - } catch (final Throwable e) { - // Print the stack trace to System.out for informational purposes - // System.err.println("### Here's the stack trace that we'll log with log4j ###"); - // e.printStackTrace(); - // System.err.println("### End stack trace ###"); + final Error e = assertThrows(Error.class, AlwaysThrowsError::new); + // Print the stack trace to System.out for informational purposes + // System.err.println("### Here's the stack trace that we'll log with log4j ###"); + // e.printStackTrace(); + // System.err.println("### End stack trace ###"); - final Logger logger = LogManager.getLogger(getClass()); + final Logger logger = LogManager.getLogger(getClass()); + assertDoesNotThrow(() -> { // This is the critical portion of the test. The log message must be printed without // throwing a java.lang.Error when introspecting the AlwaysThrowError class in the // stack trace. logger.error(e.getMessage(), e); logger.error(e); - } + }); } @Test + @DisabledForJreRange(min = JRE.JAVA_18) // custom SecurityManager instances throw UnsupportedOperationException public void testLogStackTraceWithClassThatWillCauseSecurityException() throws IOException { final SecurityManager sm = System.getSecurityManager(); try { @@ -167,11 +153,11 @@ public void checkPermission(final Permission perm) { } } }); - ServerSocketChannel.open().socket().bind(new InetSocketAddress("localhost", 9300)); - ServerSocketChannel.open().socket().bind(new InetSocketAddress("localhost", 9300)); - fail("expected a java.net.BindException"); - } catch (final BindException e) { - new ThrowableProxy(e); + final BindException e = assertThrows(BindException.class, () -> { + ServerSocketChannel.open().socket().bind(new InetSocketAddress("localhost", 9300)); + ServerSocketChannel.open().socket().bind(new InetSocketAddress("localhost", 9300)); + }); + assertDoesNotThrow(() -> new ThrowableProxy(e)); } finally { // restore the security manager System.setSecurityManager(sm); @@ -179,6 +165,7 @@ public void checkPermission(final Permission perm) { } @Test + @DisabledForJreRange(min = JRE.JAVA_18) // custom SecurityManager instances throw UnsupportedOperationException public void testLogStackTraceWithClassLoaderThatWithCauseSecurityException() throws Exception { final SecurityManager sm = System.getSecurityManager(); try { @@ -207,10 +194,8 @@ public void checkPermission(final Permission perm) { final byte[] encrypted = ec.doFinal(raw); final Cipher dc = Cipher.getInstance(algorithm); dc.init(Cipher.DECRYPT_MODE, generator.generateKey(), algorithmParameterSpec, secureRandom); - dc.doFinal(encrypted); - fail("expected a javax.crypto.BadPaddingException"); - } catch (final BadPaddingException e) { - new ThrowableProxy(e); + final BadPaddingException e = assertThrows(BadPaddingException.class, () -> dc.doFinal(encrypted)); + assertDoesNotThrow(() -> new ThrowableProxy(e)); } finally { // restore the existing security manager System.setSecurityManager(sm); @@ -293,7 +278,7 @@ public void testSerializationWithUnknownThrowable() throws Exception { final String base64 = "rO0ABXNyADFvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLlRocm93YWJsZVByb3h52cww1Zp7rPoCAAdJABJjb21tb25FbGVtZW50Q291bnRMAApjYXVzZVByb3h5dAAzTG9yZy9hcGFjaGUvbG9nZ2luZy9sb2c0ai9jb3JlL2ltcGwvVGhyb3dhYmxlUHJveHk7WwASZXh0ZW5kZWRTdGFja1RyYWNldAA/W0xvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL0V4dGVuZGVkU3RhY2tUcmFjZUVsZW1lbnQ7TAAQbG9jYWxpemVkTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAB21lc3NhZ2VxAH4AA0wABG5hbWVxAH4AA1sAEXN1cHByZXNzZWRQcm94aWVzdAA0W0xvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL1Rocm93YWJsZVByb3h5O3hwAAAAAHB1cgA/W0xvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkV4dGVuZGVkU3RhY2tUcmFjZUVsZW1lbnQ7ys+II6XHz7wCAAB4cAAAABhzcgA8b3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5FeHRlbmRlZFN0YWNrVHJhY2VFbGVtZW504d7Pusa2kAcCAAJMAA5leHRyYUNsYXNzSW5mb3QANkxvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL0V4dGVuZGVkQ2xhc3NJbmZvO0wAEXN0YWNrVHJhY2VFbGVtZW50dAAdTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDt4cHNyADRvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkV4dGVuZGVkQ2xhc3NJbmZvAAAAAAAAAAECAANaAAVleGFjdEwACGxvY2F0aW9ucQB+AANMAAd2ZXJzaW9ucQB+AAN4cAF0AA10ZXN0LWNsYXNzZXMvdAABP3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgADTAAIZmlsZU5hbWVxAH4AA0wACm1ldGhvZE5hbWVxAH4AA3hwAAAAaHQANW9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuVGhyb3dhYmxlUHJveHlUZXN0dAAXVGhyb3dhYmxlUHJveHlUZXN0LmphdmF0ACV0ZXN0U2VyaWFsaXphdGlvbldpdGhVbmtub3duVGhyb3dhYmxlc3EAfgAIc3EAfgAMAHEAfgAPdAAIMS43LjBfNTVzcQB+ABD////+dAAkc3VuLnJlZmxlY3QuTmF0aXZlTWV0aG9kQWNjZXNzb3JJbXBscHQAB2ludm9rZTBzcQB+AAhzcQB+AAwAcQB+AA9xAH4AF3NxAH4AEP////9xAH4AGXB0AAZpbnZva2VzcQB+AAhzcQB+AAwAcQB+AA9xAH4AF3NxAH4AEP////90AChzdW4ucmVmbGVjdC5EZWxlZ2F0aW5nTWV0aG9kQWNjZXNzb3JJbXBscHEAfgAec3EAfgAIc3EAfgAMAHEAfgAPcQB+ABdzcQB+ABD/////dAAYamF2YS5sYW5nLnJlZmxlY3QuTWV0aG9kcHEAfgAec3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAAvdAApb3JnLmp1bml0LnJ1bm5lcnMubW9kZWwuRnJhbWV3b3JrTWV0aG9kJDF0ABRGcmFtZXdvcmtNZXRob2QuamF2YXQAEXJ1blJlZmxlY3RpdmVDYWxsc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAAMdAAzb3JnLmp1bml0LmludGVybmFsLnJ1bm5lcnMubW9kZWwuUmVmbGVjdGl2ZUNhbGxhYmxldAAXUmVmbGVjdGl2ZUNhbGxhYmxlLmphdmF0AANydW5zcQB+AAhzcQB+AAwBdAAOanVuaXQtNC4xMS5qYXJxAH4AD3NxAH4AEAAAACx0ACdvcmcuanVuaXQucnVubmVycy5tb2RlbC5GcmFtZXdvcmtNZXRob2RxAH4ALHQAEWludm9rZUV4cGxvc2l2ZWx5c3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAARdAAyb3JnLmp1bml0LmludGVybmFsLnJ1bm5lcnMuc3RhdGVtZW50cy5JbnZva2VNZXRob2R0ABFJbnZva2VNZXRob2QuamF2YXQACGV2YWx1YXRlc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAEPdAAeb3JnLmp1bml0LnJ1bm5lcnMuUGFyZW50UnVubmVydAARUGFyZW50UnVubmVyLmphdmF0AAdydW5MZWFmc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAABGdAAob3JnLmp1bml0LnJ1bm5lcnMuQmxvY2tKVW5pdDRDbGFzc1J1bm5lcnQAG0Jsb2NrSlVuaXQ0Q2xhc3NSdW5uZXIuamF2YXQACHJ1bkNoaWxkc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAAycQB+AE1xAH4ATnEAfgBPc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAADudAAgb3JnLmp1bml0LnJ1bm5lcnMuUGFyZW50UnVubmVyJDNxAH4AR3EAfgA0c3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAA/dAAgb3JnLmp1bml0LnJ1bm5lcnMuUGFyZW50UnVubmVyJDFxAH4AR3QACHNjaGVkdWxlc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAADscQB+AEZxAH4AR3QAC3J1bkNoaWxkcmVuc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAA1cQB+AEZxAH4AR3QACmFjY2VzcyQwMDBzcQB+AAhzcQB+AAwBdAAOanVuaXQtNC4xMS5qYXJxAH4AD3NxAH4AEAAAAOV0ACBvcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXIkMnEAfgBHcQB+AEFzcQB+AAhzcQB+AAwBdAAOanVuaXQtNC4xMS5qYXJxAH4AD3NxAH4AEAAAATVxAH4ARnEAfgBHcQB+ADRzcQB+AAhzcQB+AAwBdAAELmNwL3EAfgAPc3EAfgAQAAAAMnQAOm9yZy5lY2xpcHNlLmpkdC5pbnRlcm5hbC5qdW5pdDQucnVubmVyLkpVbml0NFRlc3RSZWZlcmVuY2V0ABhKVW5pdDRUZXN0UmVmZXJlbmNlLmphdmFxAH4ANHNxAH4ACHNxAH4ADAF0AAQuY3AvcQB+AA9zcQB+ABAAAAAmdAAzb3JnLmVjbGlwc2UuamR0LmludGVybmFsLmp1bml0LnJ1bm5lci5UZXN0RXhlY3V0aW9udAASVGVzdEV4ZWN1dGlvbi5qYXZhcQB+ADRzcQB+AAhzcQB+AAwBdAAELmNwL3EAfgAPc3EAfgAQAAAB03QANm9yZy5lY2xpcHNlLmpkdC5pbnRlcm5hbC5qdW5pdC5ydW5uZXIuUmVtb3RlVGVzdFJ1bm5lcnQAFVJlbW90ZVRlc3RSdW5uZXIuamF2YXQACHJ1blRlc3Rzc3EAfgAIc3EAfgAMAXQABC5jcC9xAH4AD3NxAH4AEAAAAqtxAH4AgnEAfgCDcQB+AIRzcQB+AAhzcQB+AAwBdAAELmNwL3EAfgAPc3EAfgAQAAABhnEAfgCCcQB+AINxAH4ANHNxAH4ACHNxAH4ADAF0AAQuY3AvcQB+AA9zcQB+ABAAAADFcQB+AIJxAH4Ag3QABG1haW50ABZPTUcgSSd2ZSBiZWVuIGRlbGV0ZWQhcQB+AJJ0AEZvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLlRocm93YWJsZVByb3h5VGVzdCREZWxldGVkRXhjZXB0aW9udXIANFtMb3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5UaHJvd2FibGVQcm94eTv67QHghaLrOQIAAHhwAAAAAA=="; - final byte[] binaryDecoded = Base64Converter.parseBase64Binary(base64); + final byte[] binaryDecoded = decoder.decode(base64); final ThrowableProxy proxy2 = deserialize(binaryDecoded); assertEquals(this.getClass().getName() + "$DeletedException", proxy2.getName()); @@ -308,7 +293,7 @@ public void testSeparator_getExtendedStackTraceAsString() throws Exception { final String separator = " | "; final String extendedStackTraceAsString = proxy.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), " | ", Strings.EMPTY); - assertTrue(extendedStackTraceAsString, allLinesContain(extendedStackTraceAsString, separator)); + assertTrue(allLinesContain(extendedStackTraceAsString, separator), extendedStackTraceAsString); } @Test @@ -318,7 +303,7 @@ public void testSuffix_getExtendedStackTraceAsString() throws Exception { final String suffix = "some suffix"; final String extendedStackTraceAsString = proxy.getExtendedStackTraceAsString(suffix); - assertTrue(extendedStackTraceAsString, lastLineContains(extendedStackTraceAsString, suffix)); + assertTrue(lastLineContains(extendedStackTraceAsString, suffix), extendedStackTraceAsString); } @Test @@ -372,13 +357,13 @@ public void testSuffix_getCauseStackTraceAsStringWithSuppressedThrowable() throw @Test public void testStack() { - final Map map = new HashMap<>(); - final Stack> stack = new Stack<>(); + final Map map = new HashMap<>(); + final Deque> stack = new ArrayDeque<>(); final Throwable throwable = new IllegalStateException("This is a test"); final ThrowableProxy proxy = new ThrowableProxy(throwable); - final ExtendedStackTraceElement[] callerPackageData = proxy.toExtendedStackTrace(stack, map, null, + final ExtendedStackTraceElement[] callerPackageData = ThrowableProxyHelper.toExtendedStackTrace(proxy, stack, map, null, throwable.getStackTrace()); - assertNotNull("No package data returned", callerPackageData); + assertNotNull(callerPackageData, "No package data returned"); } /** @@ -386,20 +371,20 @@ public void testStack() { * unloaded known class (already compiled and available as a test resource: * org.apache.logging.log4j.core.impl.ForceNoDefClassFoundError.class). */ + @SuppressWarnings("BanSerializableRead") @Test public void testStackWithUnloadableClass() throws Exception { - final Stack> stack = new Stack<>(); - final Map map = new HashMap<>(); + final Deque> stack = new ArrayDeque<>(); + final Map map = new HashMap<>(); final String runtimeExceptionThrownAtUnloadableClass_base64 = "rO0ABXNyABpqYXZhLmxhbmcuUnVudGltZUV4Y2VwdGlvbp5fBkcKNIPlAgAAeHIAE2phdmEubGFuZy5FeGNlcHRpb27Q/R8+GjscxAIAAHhyABNqYXZhLmxhbmcuVGhyb3dhYmxl1cY1Jzl3uMsDAANMAAVjYXVzZXQAFUxqYXZhL2xhbmcvVGhyb3dhYmxlO0wADWRldGFpbE1lc3NhZ2V0ABJMamF2YS9sYW5nL1N0cmluZztbAApzdGFja1RyYWNldAAeW0xqYXZhL2xhbmcvU3RhY2tUcmFjZUVsZW1lbnQ7eHBxAH4ABnB1cgAeW0xqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnQ7AkYqPDz9IjkCAAB4cAAAAAFzcgAbamF2YS5sYW5nLlN0YWNrVHJhY2VFbGVtZW50YQnFmiY23YUCAARJAApsaW5lTnVtYmVyTAAOZGVjbGFyaW5nQ2xhc3NxAH4ABEwACGZpbGVOYW1lcQB+AARMAAptZXRob2ROYW1lcQB+AAR4cAAAAAZ0ADxvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkZvcmNlTm9EZWZDbGFzc0ZvdW5kRXJyb3J0AB5Gb3JjZU5vRGVmQ2xhc3NGb3VuZEVycm9yLmphdmF0AARtYWlueA=="; - final byte[] binaryDecoded = Base64Converter - .parseBase64Binary(runtimeExceptionThrownAtUnloadableClass_base64); + final byte[] binaryDecoded = decoder.decode(runtimeExceptionThrownAtUnloadableClass_base64); final ByteArrayInputStream inArr = new ByteArrayInputStream(binaryDecoded); final ObjectInputStream in = new ObjectInputStream(inArr); final Throwable throwable = (Throwable) in.readObject(); final ThrowableProxy subject = new ThrowableProxy(throwable); - subject.toExtendedStackTrace(stack, map, null, throwable.getStackTrace()); + ThrowableProxyHelper.toExtendedStackTrace(subject, stack, map, null, throwable.getStackTrace()); } /** diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java index 253386922f1..e90f94308c3 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/jmx/ServerTest.java @@ -19,9 +19,9 @@ import javax.management.ObjectName; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Unit tests for the Server class. @@ -33,7 +33,7 @@ public void testEscapeQuotesButDoesNotEscapeEquals() throws Exception { final String ctx = "WebAppClassLoader=1320771902@4eb9613e"; // LOG4J2-492 final String ctxName = Server.escape(ctx); assertEquals("\"WebAppClassLoader=1320771902@4eb9613e\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -42,7 +42,7 @@ public void testEscapeQuotesButDoesNotEscapeComma() throws Exception { final String ctx = "a,b,c"; final String ctxName = Server.escape(ctx); assertEquals("\"a,b,c\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -51,7 +51,7 @@ public void testEscapeQuotesButDoesNotEscapeColon() throws Exception { final String ctx = "a:b:c"; final String ctxName = Server.escape(ctx); assertEquals("\"a:b:c\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -60,7 +60,7 @@ public void testEscapeQuotesAndEscapesQuestion() throws Exception { final String ctx = "a?c"; final String ctxName = Server.escape(ctx); assertEquals("\"a\\?c\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -69,7 +69,7 @@ public void testEscapeQuotesAndEscapesStar() throws Exception { final String ctx = "a*c"; final String ctxName = Server.escape(ctx); assertEquals("\"a\\*c\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -78,7 +78,7 @@ public void testEscapeQuotesAndEscapesBackslash() throws Exception { final String ctx = "a\\c"; final String ctxName = Server.escape(ctx); assertEquals("\"a\\\\c\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -87,7 +87,7 @@ public void testEscapeQuotesAndEscapesQuote() throws Exception { final String ctx = "a\"c"; final String ctxName = Server.escape(ctx); assertEquals("\"a\\\"c\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -96,7 +96,7 @@ public void testEscapeIgnoresSpaces() throws Exception { final String ctx = "a c"; final String ctxName = Server.escape(ctx); assertEquals("a c", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -105,7 +105,7 @@ public void testEscapeEscapesLineFeed() throws Exception { final String ctx = "a\rc"; final String ctxName = Server.escape(ctx); assertEquals("ac", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } @@ -114,7 +114,7 @@ public void testEscapeEscapesCarriageReturn() throws Exception { final String ctx = "a\nc"; final String ctxName = Server.escape(ctx); assertEquals("\"a\\nc\"", ctxName); - new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); + new ObjectName(String.format(LoggerContextAdminMBean.PATTERN, ctxName)); // no MalformedObjectNameException = success } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java new file mode 100644 index 00000000000..65855348f3d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java @@ -0,0 +1,80 @@ +package org.apache.logging.log4j.core.layout;/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +import java.nio.charset.Charset; + +import org.apache.logging.log4j.core.LogEvent; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests AbstractStringLayout. + */ +public class AbstractStringLayoutTest { + static class ConcreteStringLayout extends AbstractStringLayout { + public static int DEFAULT_STRING_BUILDER_SIZE = AbstractStringLayout.DEFAULT_STRING_BUILDER_SIZE; + public static int MAX_STRING_BUILDER_SIZE = AbstractStringLayout.MAX_STRING_BUILDER_SIZE; + + public ConcreteStringLayout() { + super(Charset.defaultCharset()); + } + + public static StringBuilder getStringBuilder() { + return AbstractStringLayout.getStringBuilder(); + } + + @Override + public String toSerializable(final LogEvent event) { + return null; + } + } + + @Test + public void testGetStringBuilderCapacityRestrictedToMax() throws Exception { + final StringBuilder sb = ConcreteStringLayout.getStringBuilder(); + final int initialCapacity = sb.capacity(); + assertEquals(ConcreteStringLayout.DEFAULT_STRING_BUILDER_SIZE, sb.capacity(), "initial capacity"); + + final int SMALL = 100; + final String smallMessage = new String(new char[SMALL]); + sb.append(smallMessage); + assertEquals(initialCapacity, sb.capacity(), "capacity not grown"); + assertEquals(SMALL, sb.length(), "length=msg length"); + + final StringBuilder sb2 = ConcreteStringLayout.getStringBuilder(); + assertEquals(sb2.capacity(), initialCapacity, "capacity unchanged"); + assertEquals(0, sb2.length(), "empty, ready for use"); + + final int LARGE = ConcreteStringLayout.MAX_STRING_BUILDER_SIZE * 2; + final String largeMessage = new String(new char[LARGE]); + sb2.append(largeMessage); + assertTrue(sb2.capacity() >= LARGE, "capacity grown to fit msg length"); + assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, + "capacity is now greater than max length"); + assertEquals(LARGE, sb2.length(), "length=msg length"); + sb2.setLength(0); // set 0 before next getStringBuilder() call + assertEquals(0, sb2.length(), "empty, cleared"); + assertTrue(sb2.capacity() >= ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, "capacity remains very large"); + + final StringBuilder sb3 = ConcreteStringLayout.getStringBuilder(); + assertEquals(ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity(), + "capacity, trimmed to MAX_STRING_BUILDER_SIZE"); + assertEquals(0, sb3.length(), "empty, ready for use"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.java new file mode 100644 index 00000000000..6eafb87efb7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/ConcurrentLoggingWithGelfLayoutTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test for LOG4J2-1769, kind of. + * + * @since 2.8 + */ +@Tag("concurrency") +public class ConcurrentLoggingWithGelfLayoutTest { + private static final Path PATH = Paths.get("target", "test-gelf-layout.log"); + + @AfterAll + static void after() throws IOException { + // on Windows, this will need to happen after the LoggerContext is stopped + Files.deleteIfExists(PATH); + } + + @Test + @LoggerContextSource("log4j2-gelf-layout.xml") + public void testConcurrentLogging(final LoggerContext context) throws Throwable { + final Logger log = context.getLogger(ConcurrentLoggingWithGelfLayoutTest.class); + final int threadCount = Runtime.getRuntime().availableProcessors() * 2; + final CountDownLatch latch = new CountDownLatch(threadCount); + final List thrown = Collections.synchronizedList(new ArrayList<>()); + + for (int x = 0; x < threadCount; x++) { + final Thread t = new Thread(() -> { + log.info(latch.getCount()); + try { + for (int i = 0; i < 64; i++) { + log.info("First message."); + log.info("Second message."); + } + } finally { + latch.countDown(); + } + }); + + // Appender is configured with ignoreExceptions="false"; + // any exceptions are propagated to the caller, so we can catch them here. + t.setUncaughtExceptionHandler((t1, e) -> thrown.add(e)); + t.start(); + } + + latch.await(); + + // if any error occurred, fail this test + if (!thrown.isEmpty()) { + throw thrown.get(0); + } + + // simple test to ensure content is not corrupted + if (Files.exists(PATH)) { + try (Stream lines = Files.lines(PATH, Charset.defaultCharset())) { + lines.forEach(line -> assertThat(line, + both(startsWith("{\"version\":\"1.1\",\"host\":\"myself\",\"timestamp\":")).and(endsWith("\"}")))); + } + } + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout2Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout2Test.java new file mode 100644 index 00000000000..897e99ccdda --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout2Test.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.lookup.JavaLookup; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("GelfLayout2Test.xml") +public class GelfLayout2Test { + + @Test + public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException { + list.clear(); + final Logger logger = context.getLogger(getClass()); + logger.info("Message"); + final String gelf = list.getMessages().get(0); + final ObjectMapper mapper = new ObjectMapper(); + final JsonNode json = mapper.readTree(gelf); + assertEquals("Message", json.get("short_message").asText()); + assertEquals("myhost", json.get("host").asText()); + assertEquals("FOO", json.get("_foo").asText()); + assertEquals(new JavaLookup().getRuntime(), json.get("_runtime").asText()); + } + +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java new file mode 100644 index 00000000000..cf430db23a7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.message.StringMapMessage; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("GelfLayout3Test.xml") +@UsingAnyThreadContext +@Tag("json") +public class GelfLayout3Test { + + @Test + public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException { + list.clear(); + final Logger logger = context.getLogger(getClass()); + ThreadContext.put("loginId", "rgoers"); + ThreadContext.put("internalId", "12345"); + logger.info("My Test Message"); + final String gelf = list.getMessages().get(0); + final ObjectMapper mapper = new ObjectMapper(); + final JsonNode json = mapper.readTree(gelf); + assertEquals("My Test Message", json.get("short_message").asText()); + assertEquals("myhost", json.get("host").asText()); + assertNotNull(json.get("_mdc.loginId")); + assertEquals("rgoers", json.get("_mdc.loginId").asText()); + assertNull(json.get("_mdc.internalId")); + assertNull(json.get("_mdc.requestId")); + String message = json.get("full_message").asText(); + assertTrue(message.contains("loginId=rgoers")); + assertTrue(message.contains("GelfLayout3Test")); + assertNull(json.get("_map.arg1")); + assertNull(json.get("_map.arg2")); + assertNull(json.get("_empty")); + assertEquals("FOO", json.get("_foo").asText()); + } + + @Test + public void mapMessage(final LoggerContext context, @Named final ListAppender list) throws IOException { + list.clear(); + final Logger logger = context.getLogger(getClass()); + ThreadContext.put("loginId", "rgoers"); + ThreadContext.put("internalId", "12345"); + StringMapMessage message = new StringMapMessage(); + message.put("arg1", "test1"); + message.put("arg2", ""); + message.put("arg3", "test3"); + message.put("message", "My Test Message"); + logger.info(message); + final String gelf = list.getMessages().get(0); + final ObjectMapper mapper = new ObjectMapper(); + final JsonNode json = mapper.readTree(gelf); + assertEquals("arg1=\"test1\" arg2=\"\" arg3=\"test3\" message=\"My Test Message\"", + json.get("short_message").asText()); + assertEquals("myhost", json.get("host").asText()); + assertNotNull(json.get("_mdc.loginId")); + assertEquals("rgoers", json.get("_mdc.loginId").asText()); + assertNull(json.get("_mdc.internalId")); + assertNull(json.get("_mdc.requestId")); + String msg = json.get("full_message").asText(); + assertTrue(msg.contains("loginId=rgoers")); + assertTrue(msg.contains("GelfLayout3Test")); + assertTrue(msg.contains("arg1=\"test1\"")); + assertNull(json.get("_map.arg2")); + assertEquals("test1", json.get("_map.arg1").asText()); + assertEquals("test3", json.get("_map.arg3").asText()); + assertNull(json.get("_empty")); + assertEquals("FOO", json.get("_foo").asText()); + } + +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutPatternSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutPatternSelectorTest.java new file mode 100644 index 00000000000..042c4eff93f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutPatternSelectorTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import java.io.IOException; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.spi.AbstractLogger; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@LoggerContextSource("GelfLayoutPatternSelectorTest.xml") +@UsingAnyThreadContext +@Tag("json") +public class GelfLayoutPatternSelectorTest { + + @Test + public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException { + list.clear(); + final Logger logger = context.getLogger(getClass()); + ThreadContext.put("loginId", "rgoers"); + ThreadContext.put("internalId", "12345"); + logger.info("My Test Message"); + logger.info(AbstractLogger.FLOW_MARKER, "My Test Message"); + String gelf = list.getMessages().get(0); + final ObjectMapper mapper = new ObjectMapper(); + JsonNode json = mapper.readTree(gelf); + assertEquals("My Test Message", json.get("short_message").asText()); + assertEquals("myhost", json.get("host").asText()); + assertNotNull(json.get("_loginId")); + assertEquals("rgoers", json.get("_loginId").asText()); + assertNull(json.get("_internalId")); + assertNull(json.get("_requestId")); + String message = json.get("full_message").asText(); + assertFalse(message.contains("=====")); + assertTrue(message.contains("loginId=rgoers")); + assertTrue(message.contains("GelfLayoutPatternSelectorTest")); + gelf = list.getMessages().get(1); + json = mapper.readTree(gelf); + assertEquals("My Test Message", json.get("short_message").asText()); + assertEquals("myhost", json.get("host").asText()); + assertNotNull(json.get("_loginId")); + assertEquals("rgoers", json.get("_loginId").asText()); + assertNull(json.get("_internalId")); + assertNull(json.get("_requestId")); + message = json.get("full_message").asText(); + assertTrue(message.contains("=====")); + assertTrue(message.contains("loginId=rgoers")); + assertTrue(message.contains("GelfLayoutPatternSelectorTest")); + } + +} + diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java new file mode 100644 index 00000000000..0b63af7f98a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java @@ -0,0 +1,322 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.layout.GelfLayout.CompressionType; +import org.apache.logging.log4j.core.lookup.JavaLookup; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.EncodingListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.util.Chars; +import org.apache.logging.log4j.util.Lazy; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.core.io.JsonStringEncoder; + +import static net.javacrumbs.jsonunit.JsonAssert.assertJsonEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@UsingAnyThreadContext +public class GelfLayoutTest { + + private static final String HOSTNAME = "TheHost"; + private static final String KEY1 = "Key1"; + private static final String KEY2 = "Key2"; + private static final String LINE1 = "empty mdc"; + private static final String LINE2 = "filled mdc"; + private static final String LINE3 = "error message"; + private static final String MDCKEY1 = "MdcKey1"; + private static final String MDCKEY2 = "MdcKey2"; + private static final String MDCVALUE1 = "MdcValue1"; + private static final String MDCVALUE2 = "MdcValue2"; + private static final String VALUE1 = "Value1"; + + @AfterAll + public static void cleanupClass() { + LoggerContext.getContext().getInjector().removeBinding(ConfigurationFactory.KEY); + } + + @BeforeAll + public static void setupClass() { + final LoggerContext ctx = LoggerContext.getContext(); + ctx.getInjector().registerBinding(ConfigurationFactory.KEY, Lazy.lazy(BasicConfigurationFactory::new)::value); + ctx.reconfigure(); + } + + LoggerContext ctx = LoggerContext.getContext(); + + Logger root = ctx.getRootLogger(); + + private void testCompressedLayout(final CompressionType compressionType, final boolean includeStacktrace, + final boolean includeThreadContext, String host, final boolean includeNullDelimiter, + final boolean includeNewLineDelimiter) throws IOException { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + // set up appenders + final GelfLayout layout = GelfLayout.newBuilder() + .setConfiguration(ctx.getConfiguration()) + .setHost(host) + .setAdditionalFields(new KeyValuePair[]{ + new KeyValuePair(KEY1, VALUE1), + new KeyValuePair(KEY2, "${java:runtime}"),}) + .setCompressionType(compressionType) + .setCompressionThreshold(1024) + .setIncludeStacktrace(includeStacktrace) + .setIncludeThreadContext(includeThreadContext) + .setIncludeNullDelimiter(includeNullDelimiter) + .setIncludeNewLineDelimiter(includeNewLineDelimiter) + .build(); + final ListAppender eventAppender = new ListAppender("Events", null, null, true, false); + final ListAppender rawAppender = new ListAppender("Raw", null, layout, true, true); + final ListAppender formattedAppender = new ListAppender("Formatted", null, layout, true, false); + final EncodingListAppender encodedAppender = new EncodingListAppender("Encoded", null, layout, false, true); + eventAppender.start(); + rawAppender.start(); + formattedAppender.start(); + encodedAppender.start(); + + if (host == null) { + host = NetUtils.getLocalHostname(); + } + + final JavaLookup javaLookup = new JavaLookup(); + + // set appenders on root and set level to debug + root.addAppender(eventAppender); + root.addAppender(rawAppender); + root.addAppender(formattedAppender); + root.addAppender(encodedAppender); + root.setLevel(Level.DEBUG); + + root.debug(LINE1); + + ThreadContext.put(MDCKEY1, MDCVALUE1); + ThreadContext.put(MDCKEY2, MDCVALUE2); + + root.info(LINE2); + + final Exception exception = new RuntimeException("some error"); + root.error(LINE3, exception); + + formattedAppender.stop(); + + final List events = eventAppender.getEvents(); + final List raw = rawAppender.getData(); + final List messages = formattedAppender.getMessages(); + final List raw2 = encodedAppender.getData(); + final String threadName = Thread.currentThread().getName(); + + //@formatter:off + String message = messages.get(0); + if (includeNullDelimiter) { + assertThat(message.indexOf(Chars.NUL)).isEqualTo(message.length() - 1); + message = message.replace(Chars.NUL, Chars.LF); + } + assertJsonEquals("{" + + "\"version\": \"1.1\"," + + "\"host\": \"" + host + "\"," + + "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(0).getTimeMillis()) + "," + + "\"level\": 7," + + "\"_thread\": \"" + threadName + "\"," + + "\"_logger\": \"\"," + + "\"short_message\": \"" + LINE1 + "\"," + + "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," + + "\"_" + KEY2 + "\": \"" + javaLookup.getRuntime() + "\"" + + "}", + message); + + message = messages.get(1); + if (includeNullDelimiter) { + assertThat(message.indexOf(Chars.NUL)).isEqualTo(message.length() - 1); + message = message.replace(Chars.NUL, Chars.LF); + } + assertJsonEquals("{" + + "\"version\": \"1.1\"," + + "\"host\": \"" + host + "\"," + + "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(1).getTimeMillis()) + "," + + "\"level\": 6," + + "\"_thread\": \"" + threadName + "\"," + + "\"_logger\": \"\"," + + "\"short_message\": \"" + LINE2 + "\"," + + (includeThreadContext ? + "\"_" + MDCKEY1 + "\": \"" + MDCVALUE1 + "\"," + + "\"_" + MDCKEY2 + "\": \"" + MDCVALUE2 + "\"," + : + "") + + "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," + + "\"_" + KEY2 + "\": \"" + javaLookup.getRuntime() + "\"" + + "}", + message); + //@formatter:on + final byte[] compressed = raw.get(2); + final byte[] compressed2 = raw2.get(2); + final ByteArrayInputStream bais = new ByteArrayInputStream(compressed); + final ByteArrayInputStream bais2 = new ByteArrayInputStream(compressed2); + InputStream inflaterStream; + InputStream inflaterStream2; + switch (compressionType) { + case GZIP: + inflaterStream = new GZIPInputStream(bais); + inflaterStream2 = new GZIPInputStream(bais2); + break; + case ZLIB: + inflaterStream = new InflaterInputStream(bais); + inflaterStream2 = new InflaterInputStream(bais2); + break; + case OFF: + inflaterStream = bais; + inflaterStream2 = bais2; + break; + default: + throw new IllegalStateException("Missing test case clause"); + } + final byte[] uncompressed = IOUtils.toByteArray(inflaterStream); + final byte[] uncompressed2 = IOUtils.toByteArray(inflaterStream2); + inflaterStream.close(); + inflaterStream2.close(); + String uncompressedString = new String(uncompressed, layout.getCharset()); + String uncompressedString2 = new String(uncompressed2, layout.getCharset()); + //@formatter:off + final String expected = "{" + + "\"version\": \"1.1\"," + + "\"host\": \"" + host + "\"," + + "\"timestamp\": " + GelfLayout.formatTimestamp(events.get(2).getTimeMillis()) + "," + + "\"level\": 3," + + "\"_thread\": \"" + threadName + "\"," + + "\"_logger\": \"\"," + + "\"short_message\": \"" + LINE3 + "\"," + + "\"full_message\": \"" + String.valueOf(JsonStringEncoder.getInstance().quoteAsString( + includeStacktrace ? GelfLayout.formatThrowable(exception).toString() : exception.toString())) + "\"," + + (includeThreadContext ? + "\"_" + MDCKEY1 + "\": \"" + MDCVALUE1 + "\"," + + "\"_" + MDCKEY2 + "\": \"" + MDCVALUE2 + "\"," + : "") + + "\"_" + KEY1 + "\": \"" + VALUE1 + "\"," + + "\"_" + KEY2 + "\": \"" + javaLookup.getRuntime() + "\"" + + "}"; + //@formatter:on + if (includeNullDelimiter) { + assertEquals(uncompressedString.indexOf(Chars.NUL), uncompressedString.length() - 1); + assertEquals(uncompressedString2.indexOf(Chars.NUL), uncompressedString2.length() - 1); + uncompressedString = uncompressedString.replace(Chars.NUL, Chars.LF); + uncompressedString2 = uncompressedString2.replace(Chars.NUL, Chars.LF); + } + if (includeNewLineDelimiter) { + assertEquals(uncompressedString.indexOf(Chars.LF), uncompressedString.length() - 1); + assertEquals(uncompressedString2.indexOf(Chars.LF), uncompressedString2.length() - 1); + } + assertJsonEquals(expected, uncompressedString); + assertJsonEquals(expected, uncompressedString2); + } + + @Test + public void testLayoutGzipCompression() throws Exception { + testCompressedLayout(CompressionType.GZIP, true, true, HOSTNAME, false, false); + } + + @Test + public void testLayoutNoCompression() throws Exception { + testCompressedLayout(CompressionType.OFF, true, true, HOSTNAME, false, false); + } + + @Test + public void testLayoutZlibCompression() throws Exception { + testCompressedLayout(CompressionType.ZLIB, true, true, HOSTNAME, false, false); + } + + @Test + public void testLayoutNoStacktrace() throws Exception { + testCompressedLayout(CompressionType.OFF, false, true, HOSTNAME, false, false); + } + + @Test + public void testLayoutNoThreadContext() throws Exception { + testCompressedLayout(CompressionType.OFF, true, false, HOSTNAME, false, false); + } + + @Test + public void testLayoutNoHost() throws Exception { + testCompressedLayout(CompressionType.OFF, true, true, null, false, false); + } + + @Test + public void testLayoutNullDelimiter() throws Exception { + testCompressedLayout(CompressionType.OFF, false, true, HOSTNAME, true, false); + } + + @Test + public void testLayoutNewLineDelimiter() throws Exception { + testCompressedLayout(CompressionType.OFF, true, true, HOSTNAME, false, true); + } + + @Test + public void testFormatTimestamp() { + assertEquals("0", GelfLayout.formatTimestamp(0L).toString()); + assertEquals("1.000", GelfLayout.formatTimestamp(1000L).toString()); + assertEquals("1.001", GelfLayout.formatTimestamp(1001L).toString()); + assertEquals("1.010", GelfLayout.formatTimestamp(1010L).toString()); + assertEquals("1.100", GelfLayout.formatTimestamp(1100L).toString()); + assertEquals("1458741206.653", GelfLayout.formatTimestamp(1458741206653L).toString()); + assertEquals("9223372036854775.807", GelfLayout.formatTimestamp(Long.MAX_VALUE).toString()); + } + + private void testRequiresLocation(String messagePattern, Boolean requiresLocation) { + + GelfLayout layout = GelfLayout.newBuilder() + .setMessagePattern(messagePattern) + .build(); + + assertEquals(layout.requiresLocation(), requiresLocation); + } + + @Test + public void testRequiresLocationPatternNotSet() { + testRequiresLocation(null, false); + } + + @Test + public void testRequiresLocationPatternNotContainsLocation() { + testRequiresLocation("%m %n", false); + } + + @Test + public void testRequiresLocationPatternContainsLocation() { + testRequiresLocation("%C %m %t", true); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java new file mode 100644 index 00000000000..0a45fca98b1 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java @@ -0,0 +1,289 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import java.lang.management.ManagementFactory; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.*; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.util.Lazy; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat; +import static org.junit.jupiter.api.Assertions.*; + +@UsingAnyThreadContext +public class HtmlLayoutTest { + private static class MyLogEvent extends AbstractLogEvent { + private static final long serialVersionUID = 0; + + @Override + public Instant getInstant() { + MutableInstant result = new MutableInstant(); + result.initFromEpochMilli(getTimeMillis(), 456789); + return result; + } + + @Override + public long getTimeMillis() { + final Calendar cal = Calendar.getInstance(); + cal.set(2012, Calendar.NOVEMBER, 02, 14, 34, 02); + cal.set(Calendar.MILLISECOND, 123); + return cal.getTimeInMillis(); + } + + @Override + public Level getLevel() { + return Level.DEBUG; + } + + @Override + public Message getMessage() { + return new SimpleMessage("msg"); + } + } + + private final LoggerContext ctx = LoggerContext.getContext(); + private final Logger root = ctx.getRootLogger(); + + @BeforeAll + public static void setupClass() { + final LoggerContext ctx = LoggerContext.getContext(); + ctx.getInjector().registerBinding(ConfigurationFactory.KEY, Lazy.lazy(BasicConfigurationFactory::new)::value); + ctx.reconfigure(); + } + + @AfterAll + public static void cleanupClass() { + LoggerContext.getContext().getInjector().removeBinding(ConfigurationFactory.KEY); + } + + private static final String body = "java.lang.NullPointerException: test"; + + private static final String multiLine = "First line
Second line"; + + @Test + public void testDefaultContentType() { + final HtmlLayout layout = HtmlLayout.createDefaultLayout(); + assertEquals("text/html; charset=UTF-8", layout.getContentType()); + } + + @Test + public void testContentType() { + final HtmlLayout layout = HtmlLayout.newBuilder() + .setContentType("text/html; charset=UTF-16") + .build(); + assertEquals("text/html; charset=UTF-16", layout.getContentType()); + // TODO: make sure this following bit works as well +// assertEquals(Charset.forName("UTF-16"), layout.getCharset()); + } + + @Test + public void testDefaultCharset() { + final HtmlLayout layout = HtmlLayout.createDefaultLayout(); + assertEquals(StandardCharsets.UTF_8, layout.getCharset()); + } + + /** + * Test case for MDC conversion pattern. + */ + @Test + public void testLayoutIncludeLocationNo() throws Exception { + testLayout(false); + } + + @Test + public void testLayoutIncludeLocationYes() throws Exception { + testLayout(true); + } + + private void testLayout(final boolean includeLocation) throws Exception { + final Map appenders = root.getAppenders(); + for (final Appender appender : appenders.values()) { + root.removeAppender(appender); + } + // set up appender + final HtmlLayout layout = HtmlLayout.newBuilder() + .setLocationInfo(includeLocation) + .build(); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + // output starting message + root.debug("starting mdc pattern test"); + + root.debug("empty mdc"); + + root.debug("First line\nSecond line"); + + ThreadContext.put("key1", "value1"); + ThreadContext.put("key2", "value2"); + + root.debug("filled mdc"); + + ThreadContext.remove("key1"); + ThreadContext.remove("key2"); + + root.error("finished mdc pattern test", new NullPointerException("test")); + + appender.stop(); + + final List list = appender.getMessages(); + final StringBuilder sb = new StringBuilder(); + for (final String string : list) { + sb.append(string); + } + final String html = sb.toString(); + assertTrue(list.size() > 85, "Incorrect number of lines. Require at least 85 " + list.size()); + final String string = list.get(3); + assertEquals("", string, "Incorrect header: " + string); + assertEquals("Log4j Log Messages", list.get(4), "Incorrect title"); + assertEquals("", list.get(list.size() - 1), "Incorrect footer"); + if (includeLocation) { + assertEquals(list.get(50), multiLine, "Incorrect multiline"); + assertTrue(html.contains("HtmlLayoutTest.java:"), "Missing location"); + assertEquals(list.get(71), body, "Incorrect body"); + } else { + assertFalse(html.contains("HtmlLayoutTest.java:"), "Location should not be in the output table"); + } + for (final Appender app : appenders.values()) { + root.addAppender(app); + } + } + + @Test + public void testLayoutWithoutDataPattern() { + final HtmlLayout layout = HtmlLayout.newBuilder().build(); + + MyLogEvent event = new MyLogEvent(); + String actual = getDateLine(layout.toSerializable(event)); + + long jvmStratTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + assertEquals("" + (event.getTimeMillis() - jvmStratTime) + "", actual, "Incorrect date:" + actual); + } + + @Test + public void testLayoutWithDatePatternJvmElapseTime() { + final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("JVM_ELAPSE_TIME").build(); + + MyLogEvent event = new MyLogEvent(); + String actual = getDateLine(layout.toSerializable(event)); + + long jvmStratTime = ManagementFactory.getRuntimeMXBean().getStartTime(); + assertEquals("" + (event.getTimeMillis() - jvmStratTime) + "", actual, "Incorrect date:" + actual); + } + + @Test + public void testLayoutWithDatePatternUnix() { + final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("UNIX").build(); + + MyLogEvent event = new MyLogEvent(); + String actual = getDateLine(layout.toSerializable(event)); + + assertEquals("" + event.getInstant().getEpochSecond() + "", actual, "Incorrect date:" + actual); + } + + @Test + public void testLayoutWithDatePatternUnixMillis() { + final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern("UNIX_MILLIS").build(); + + MyLogEvent event = new MyLogEvent(); + String actual = getDateLine(layout.toSerializable(event)); + + assertEquals("" + event.getTimeMillis() + "", actual, "Incorrect date:" + actual); + } + + @Test + public void testLayoutWithDatePatternFixedFormat() { + for (final String timeZone : new String[] {"GMT+8", "GMT+0530", "UTC", null}) { + for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { + testLayoutWithDatePatternFixedFormat(format, timeZone); + } + } + } + + private String getDateLine(String logEventString) { + return logEventString.split(System.lineSeparator())[2]; + } + + private void testLayoutWithDatePatternFixedFormat(FixedFormat format, String timezone) { + final HtmlLayout layout = HtmlLayout.newBuilder().setDatePattern(format.name()).setTimezone(timezone).build(); + + LogEvent event = new MyLogEvent(); + String actual = getDateLine(layout.toSerializable(event)); + + // build expected date string + java.time.Instant instant = + java.time.Instant.ofEpochSecond(event.getInstant().getEpochSecond(), event.getInstant().getNanoOfSecond()); + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()); + if (timezone != null) { + zonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of(timezone)); + } + + // LOG4J2-3019 HtmlLayoutTest.testLayoutWithDatePatternFixedFormat test fails on windows + // https://issues.apache.org/jira/browse/LOG4J2-3019 + // java.time.format.DateTimeFormatterBuilder.toFormatter() defaults to using + // Locale.getDefault(Locale.Category.FORMAT) + final Locale formatLocale = Locale.getDefault(Locale.Category.FORMAT); + final Locale locale = Locale.getDefault().equals(formatLocale) ? formatLocale : Locale.getDefault(); + + // For DateTimeFormatter of jdk, + // Pattern letter 'S' means fraction-of-second, 'n' means nano-of-second. Log4j2 needs S. + // Pattern letter 'X' (upper case) will output 'Z' when the offset to be output would be zero, + // whereas pattern letter 'x' (lower case) will output '+00', '+0000', or '+00:00'. Log4j2 needs x. + DateTimeFormatter dateTimeFormatter = + DateTimeFormatter.ofPattern(format.getPattern().replace('n', 'S').replace('X', 'x'), locale); + String expected = zonedDateTime.format(dateTimeFormatter); + + String offset = zonedDateTime.getOffset().toString(); + + //Truncate minutes if timeZone format is HH and timeZone has minutes. This is required because according to DateTimeFormatter, + //One letter outputs just the hour, such as '+01', unless the minute is non-zero in which case the minute is also output, such as '+0130' + //ref : https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html + if (FixedDateFormat.FixedTimeZoneFormat.HH.equals(format.getTimeZoneFormat()) && offset.contains(":") && !"00".equals(offset.split(":")[1])) { + expected = expected.substring(0, expected.length() - 2); + } + + assertEquals("" + expected + "", actual, + MessageFormat.format("Incorrect date={0}, format={1}, timezone={2}", actual, format.name(), timezone)); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_CoreTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_CoreTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_CoreTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_CoreTest.java index 0a23fd276bc..da6ddf1472f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_CoreTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_CoreTest.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.test.layout.Log4j2_1482_Test; public class Log4j2_1482_CoreTest extends Log4j2_1482_Test { diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java new file mode 100644 index 00000000000..42c3b97e836 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.layout; + +import java.io.Serializable; +import java.util.List; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("LOG4J-2195/log4j2.xml") +public class Log4j2_2195_Test { + + @Test + public void test(final LoggerContext context, @Named("ListAppender") final ListAppender listAppender) { + listAppender.clear(); + context.getLogger(getClass()).info("This is a test.", new Exception("Test exception!")); + assertNotNull(listAppender); + List events = listAppender.getMessages(); + assertNotNull(events); + assertEquals(1, events.size()); + String logEvent = events.get(0); + assertNotNull(logEvent); + assertFalse(logEvent.contains("org.junit"), "\"org.junit\" should not be here"); + assertFalse(logEvent.contains("org.eclipse"), "\"org.eclipse\" should not be here"); + // + Layout layout = listAppender.getLayout(); + PatternLayout pLayout = (PatternLayout) layout; + assertNotNull(pLayout); + Serializer eventSerializer = pLayout.getEventSerializer(); + assertNotNull(eventSerializer); + // + assertTrue(logEvent.contains("|"), "Missing \"|\""); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java new file mode 100644 index 00000000000..c622c20fea8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel. + * + * This shows the behavior this user wants to disable. + */ +@LoggerContextSource("log4j-list-lookups.xml") +public class PatternLayoutLookupDateTest { + + @Test + public void testDateLookupInMessage(final LoggerContext context, @Named("List") final ListAppender listAppender) { + listAppender.clear(); + final String template = "${date:YYYY-MM-dd}"; + context.getLogger(PatternLayoutLookupDateTest.class.getName()).info(template); + final String string = listAppender.getMessages().get(0); + assertTrue(string.contains(template), string); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java new file mode 100644 index 00000000000..d7416da5cfe --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.layout; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.lookup.MainMapLookup; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.junit.ReconfigurationPolicy; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * Tests LOG4j2-962. + */ +@LoggerContextSource(value = "log4j2-962.xml", reconfigure = ReconfigurationPolicy.BEFORE_EACH) +public class PatternLayoutMainMapLookupTest { + + static { + // Must be set before Log4j writes the header to the appenders. + MainMapLookup.setMainArguments("value0", "value1", "value2"); + } + + @Test + public void testFileName(@Named("File") final FileAppender fileApp) { + final String name = fileApp.getFileName(); + assertEquals("target/value0.log", name); + } + + @Test + public void testHeader(final LoggerContext context, @Named("List") final ListAppender listApp) { + final Logger logger = context.getLogger(getClass()); + logger.info("Hello World"); + final List initialMessages = listApp.getMessages(); + assertFalse(initialMessages.isEmpty()); + final String messagesStr = initialMessages.toString(); + assertEquals("Header: value0", initialMessages.get(0), messagesStr); + listApp.stop(); + final List finalMessages = listApp.getMessages(); + assertEquals(3, finalMessages.size()); + assertEquals("Footer: value1", finalMessages.get(2)); + listApp.clear(); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java new file mode 100644 index 00000000000..e544f6cf0fb --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel. + */ +@LoggerContextSource("log4j-list.xml") +public class PatternLayoutNoLookupDateTest { + + @Test + public void testDateLookupInMessage(final LoggerContext context, @Named("List") final ListAppender listAppender) { + listAppender.clear(); + final String template = "${date:YYYY-MM-dd}"; + context.getLogger(PatternLayoutNoLookupDateTest.class).info(template); + final String string = listAppender.getMessages().get(0); + Assertions.assertTrue(string.contains(template), string); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutRepeatTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutRepeatTest.java new file mode 100644 index 00000000000..217c8785dd6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutRepeatTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel. + */ +@LoggerContextSource("PatternLayoutRepeat.xml") +public class PatternLayoutRepeatTest { + + @Test + public void testRepeatSymbol(final LoggerContext context, @Named("List") final ListAppender listAppender) { + listAppender.clear(); + context.getLogger(PatternLayoutRepeatTest.class).info("Hello world"); + final String string = listAppender.getMessages().get(0); + Assertions.assertTrue(string.contains("##########"), "Incorrect result: " + string); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java index 4749546811e..3cc58cc5ced 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java @@ -16,11 +16,6 @@ */ package org.apache.logging.log4j.core.layout; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -28,7 +23,6 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.BasicConfigurationFactory; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; @@ -36,37 +30,29 @@ import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.lookup.MainMapLookup; -import org.apache.logging.log4j.junit.ThreadContextRule; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.util.Lazy; import org.apache.logging.log4j.util.Strings; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; -/** - * - */ +import static org.junit.jupiter.api.Assertions.*; + +@UsingAnyThreadContext public class PatternLayoutTest { - public class FauxLogger { + public static class FauxLogger { public String formatEvent(final LogEvent event, final Layout layout) { return new String(layout.toByteArray(event)); } } - static ConfigurationFactory cf = new BasicConfigurationFactory(); - static String msgPattern = "%m%n"; - static String OUTPUT_FILE = "target/output/PatternParser"; static final String regexPattern = "%replace{%logger %msg}{\\.}{/}"; - static String WITNESS_FILE = "witness/PatternParser"; - - public static void cleanupClass() { - ConfigurationFactory.removeConfigurationFactory(cf); - } - - @BeforeClass + @BeforeAll public static void setupClass() { - ConfigurationFactory.setConfigurationFactory(cf); final LoggerContext ctx = LoggerContext.getContext(); + ctx.getInjector().registerBinding(ConfigurationFactory.KEY, Lazy.lazy(BasicConfigurationFactory::new)); ctx.reconfigure(); } @@ -74,9 +60,6 @@ public static void setupClass() { Logger root = ctx.getRootLogger(); - @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); - private static class Destination implements ByteBufferDestination { ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[2048]); @Override @@ -117,8 +100,8 @@ private void assertEncode(final String expectedStr, final PatternLayout layout, @Test public void testEqualsEmptyMarker() throws Exception { // replace "[]" with the empty string - final PatternLayout layout = PatternLayout.newBuilder().withPattern("[%logger]%equals{[%marker]}{[]}{} %msg") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("[%logger]%equals{[%marker]}{[]}{} %msg") + .setConfiguration(ctx.getConfiguration()).build(); // Not empty marker final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // @@ -142,27 +125,27 @@ public void testEqualsEmptyMarker() throws Exception { public void testHeaderFooterJavaLookup() throws Exception { // % does not work here. final String pattern = "%d{UNIX} MyApp%n${java:version}%n${java:runtime}%n${java:vm}%n${java:os}%n${java:hw}"; - final PatternLayout layout = PatternLayout.newBuilder().withConfiguration(ctx.getConfiguration()) - .withHeader("Header: " + pattern).withFooter("Footer: " + pattern).build(); + final PatternLayout layout = PatternLayout.newBuilder().setConfiguration(ctx.getConfiguration()) + .setHeader("Header: " + pattern).setFooter("Footer: " + pattern).build(); final byte[] header = layout.getHeader(); - assertNotNull("No header", header); + assertNotNull(header, "No header"); final String headerStr = new String(header); - assertTrue(headerStr, headerStr.contains("Header: ")); - assertTrue(headerStr, headerStr.contains("Java version ")); - assertTrue(headerStr, headerStr.contains("(build ")); - assertTrue(headerStr, headerStr.contains(" from ")); - assertTrue(headerStr, headerStr.contains(" architecture: ")); - assertFalse(headerStr, headerStr.contains("%d{UNIX}")); + assertTrue(headerStr.contains("Header: "), headerStr); + assertTrue(headerStr.contains("Java version "), headerStr); + assertTrue(headerStr.contains("(build "), headerStr); + assertTrue(headerStr.contains(" from "), headerStr); + assertTrue(headerStr.contains(" architecture: "), headerStr); + assertFalse(headerStr.contains("%d{UNIX}"), headerStr); // final byte[] footer = layout.getFooter(); - assertNotNull("No footer", footer); + assertNotNull(footer, "No footer"); final String footerStr = new String(footer); - assertTrue(footerStr, footerStr.contains("Footer: ")); - assertTrue(footerStr, footerStr.contains("Java version ")); - assertTrue(footerStr, footerStr.contains("(build ")); - assertTrue(footerStr, footerStr.contains(" from ")); - assertTrue(footerStr, footerStr.contains(" architecture: ")); - assertFalse(footerStr, footerStr.contains("%d{UNIX}")); + assertTrue(footerStr.contains("Footer: "), footerStr); + assertTrue(footerStr.contains("Java version "), footerStr); + assertTrue(footerStr.contains("(build "), footerStr); + assertTrue(footerStr.contains(" from "), footerStr); + assertTrue(footerStr.contains(" architecture: "), footerStr); + assertFalse(footerStr.contains("%d{UNIX}"), footerStr); } /** @@ -171,36 +154,36 @@ public void testHeaderFooterJavaLookup() throws Exception { @Test public void testHeaderFooterMainLookup() { MainMapLookup.setMainArguments("value0", "value1", "value2"); - final PatternLayout layout = PatternLayout.newBuilder().withConfiguration(ctx.getConfiguration()) - .withHeader("${main:0}").withFooter("${main:2}").build(); + final PatternLayout layout = PatternLayout.newBuilder().setConfiguration(ctx.getConfiguration()) + .setHeader("${main:0}").setFooter("${main:2}").build(); final byte[] header = layout.getHeader(); - assertNotNull("No header", header); + assertNotNull(header, "No header"); final String headerStr = new String(header); - assertTrue(headerStr, headerStr.contains("value0")); + assertTrue(headerStr.contains("value0"), headerStr); // final byte[] footer = layout.getFooter(); - assertNotNull("No footer", footer); + assertNotNull(footer, "No footer"); final String footerStr = new String(footer); - assertTrue(footerStr, footerStr.contains("value2")); + assertTrue(footerStr.contains("value2"), footerStr); } @Test public void testHeaderFooterThreadContext() throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d{UNIX} %m") - .withConfiguration(ctx.getConfiguration()).withHeader("${ctx:header}").withFooter("${ctx:footer}") + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d{UNIX} %m") + .setConfiguration(ctx.getConfiguration()).setHeader("${ctx:header}").setFooter("${ctx:footer}") .build(); ThreadContext.put("header", "Hello world Header"); ThreadContext.put("footer", "Hello world Footer"); final byte[] header = layout.getHeader(); - assertNotNull("No header", header); - assertTrue("expected \"Hello world Header\", actual " + Strings.dquote(new String(header)), - new String(header).equals(new String("Hello world Header"))); + assertNotNull(header, "No header"); + assertEquals("Hello world Header", new String(header), + "expected \"Hello world Header\", actual " + Strings.dquote(new String(header))); } private void testMdcPattern(final String patternStr, final String expectedStr, final boolean useThreadContext) throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern(patternStr) - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern(patternStr) + .setConfiguration(ctx.getConfiguration()).build(); if (useThreadContext) { ThreadContext.put("key1", "value1"); ThreadContext.put("key2", "value2"); @@ -247,9 +230,17 @@ public void testMdcPattern5() throws Exception { public void testPatternSelector() throws Exception { final PatternMatch[] patterns = new PatternMatch[1]; patterns[0] = new PatternMatch("FLOW", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n"); - final PatternSelector selector = MarkerPatternSelector.createSelector(patterns, "%d %-5p [%t]: %m%n", true, true, ctx.getConfiguration()); - final PatternLayout layout = PatternLayout.newBuilder().withPatternSelector(selector) - .withConfiguration(ctx.getConfiguration()).build(); + // @formatter:off + final PatternSelector selector = MarkerPatternSelector.newBuilder() + .setProperties(patterns) + .setDefaultPattern("%d %-5p [%t]: %m%n") + .setAlwaysWriteExceptions(true) + .setNoConsoleNoAnsi(true) + .setConfiguration(ctx.getConfiguration()) + .build(); + // @formatter:on + final PatternLayout layout = PatternLayout.newBuilder().setPatternSelector(selector) + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternLayoutTest$FauxLogger") .setMarker(MarkerManager.getMarker("FLOW")) @@ -258,20 +249,20 @@ public void testPatternSelector() throws Exception { .setMessage(new SimpleMessage("entry")).build(); final String result1 = new FauxLogger().formatEvent(event1, layout); final String expectPattern1 = String.format(".*====== PatternLayoutTest.testPatternSelector:\\d+ entry ======%n"); - assertTrue("Unexpected result: " + result1, result1.matches(expectPattern1)); + assertTrue(result1.matches(expectPattern1), "Unexpected result: " + result1); final LogEvent event2 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // .setLevel(Level.INFO) // .setMessage(new SimpleMessage("Hello, world 1!")).build(); final String result2 = new String(layout.toByteArray(event2)); final String expectSuffix2 = String.format("Hello, world 1!%n"); - assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2)); + assertTrue(result2.endsWith(expectSuffix2), "Unexpected result: " + result2); } @Test public void testRegex() throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern(regexPattern) - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern(regexPattern) + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // .setLevel(Level.INFO) // @@ -283,8 +274,8 @@ public void testRegex() throws Exception { @Test public void testRegexEmptyMarker() throws Exception { // replace "[]" with the empty string - final PatternLayout layout = PatternLayout.newBuilder().withPattern("[%logger]%replace{[%marker]}{\\[\\]}{} %msg") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("[%logger]%replace{[%marker]}{\\[\\]}{} %msg") + .setConfiguration(ctx.getConfiguration()).build(); // Not empty marker final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // @@ -308,8 +299,8 @@ public void testRegexEmptyMarker() throws Exception { @Test public void testEqualsMarkerWithMessageSubstitution() throws Exception { // replace "[]" with the empty string - final PatternLayout layout = PatternLayout.newBuilder().withPattern("[%logger]%equals{[%marker]}{[]}{[%msg]}") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("[%logger]%equals{[%marker]}{[]}{[%msg]}") + .setConfiguration(ctx.getConfiguration()).build(); // Not empty marker final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // @@ -329,8 +320,8 @@ public void testEqualsMarkerWithMessageSubstitution() throws Exception { @Test public void testSpecialChars() throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("\\\\%level\\t%msg\\n\\t%logger\\r\\n\\f") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("\\\\%level\\t%msg\\n\\t%logger\\r\\n\\f") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // .setLevel(Level.INFO) // @@ -345,8 +336,8 @@ public void testSpecialChars() throws Exception { @Test public void testUnixTime() throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d{UNIX} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d{UNIX} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // .setLevel(Level.INFO) // @@ -365,8 +356,8 @@ public void testUnixTime() throws Exception { @SuppressWarnings("unused") private void testUnixTime(final String pattern) throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern(pattern + " %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern(pattern + " %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // .setLevel(Level.INFO) // @@ -385,8 +376,8 @@ private void testUnixTime(final String pattern) throws Exception { @Test public void testUnixTimeMillis() throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d{UNIX_MILLIS} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d{UNIX_MILLIS} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() // .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // .setLevel(Level.INFO) // @@ -405,23 +396,23 @@ public void testUnixTimeMillis() throws Exception { @Test public void testUsePlatformDefaultIfNoCharset() throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m") + .setConfiguration(ctx.getConfiguration()).build(); assertEquals(Charset.defaultCharset(), layout.getCharset()); } @Test public void testUseSpecifiedCharsetIfExists() throws Exception { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%m") - .withConfiguration(ctx.getConfiguration()).withCharset(StandardCharsets.UTF_8).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m") + .setConfiguration(ctx.getConfiguration()).setCharset(StandardCharsets.UTF_8).build(); assertEquals(StandardCharsets.UTF_8, layout.getCharset()); } @Test public void testLoggerNameTruncationByRetainingPartsFromEnd() throws Exception { { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{1} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{1} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -430,8 +421,8 @@ public void testLoggerNameTruncationByRetainingPartsFromEnd() throws Exception { assertEquals(this.getClass().getName().substring(this.getClass().getName().lastIndexOf(".") + 1) + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{2} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{2} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -442,8 +433,8 @@ public void testLoggerNameTruncationByRetainingPartsFromEnd() throws Exception { assertEquals(this.getClass().getName().substring(name.length() + 1) + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{20} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{20} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -456,8 +447,8 @@ public void testLoggerNameTruncationByRetainingPartsFromEnd() throws Exception { @Test public void testCallersFqcnTruncationByRetainingPartsFromEnd() throws Exception { { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{1} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{1} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -468,8 +459,8 @@ public void testCallersFqcnTruncationByRetainingPartsFromEnd() throws Exception assertEquals(this.getClass().getName().substring(this.getClass().getName().lastIndexOf(".") + 1) + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{2} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{2} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -482,8 +473,8 @@ public void testCallersFqcnTruncationByRetainingPartsFromEnd() throws Exception assertEquals(this.getClass().getName().substring(name.length() + 1) + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{20} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{20} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -494,8 +485,8 @@ public void testCallersFqcnTruncationByRetainingPartsFromEnd() throws Exception assertEquals(this.getClass().getName() + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%class{1} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%class{1} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -510,8 +501,8 @@ public void testCallersFqcnTruncationByRetainingPartsFromEnd() throws Exception @Test public void testLoggerNameTruncationByDroppingPartsFromFront() throws Exception { { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{-1} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{-1} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -521,8 +512,8 @@ public void testLoggerNameTruncationByDroppingPartsFromFront() throws Exception assertEquals(name + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{-3} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{-3} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -534,8 +525,8 @@ public void testLoggerNameTruncationByDroppingPartsFromFront() throws Exception assertEquals(name + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%logger{-3} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%logger{-3} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -547,8 +538,8 @@ public void testLoggerNameTruncationByDroppingPartsFromFront() throws Exception assertEquals(name + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{-20} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{-20} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -562,8 +553,8 @@ public void testLoggerNameTruncationByDroppingPartsFromFront() throws Exception @Test public void testCallersFqcnTruncationByDroppingPartsFromFront() throws Exception { { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{-1} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{-1} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -575,8 +566,8 @@ public void testCallersFqcnTruncationByDroppingPartsFromFront() throws Exception assertEquals(name + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{-3} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{-3} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -590,8 +581,8 @@ public void testCallersFqcnTruncationByDroppingPartsFromFront() throws Exception assertEquals(name + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%class{-3} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%class{-3} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) @@ -605,8 +596,8 @@ public void testCallersFqcnTruncationByDroppingPartsFromFront() throws Exception assertEquals(name + " Hello, world 1!", new String(result1)); } { - final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{-20} %m") - .withConfiguration(ctx.getConfiguration()).build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{-20} %m") + .setConfiguration(ctx.getConfiguration()).build(); final LogEvent event1 = Log4jLogEvent.newBuilder() .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") .setLevel(Level.INFO) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java new file mode 100644 index 00000000000..eb5b3b5400d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PatternSelectorTest { + + public static class FauxLogger { + public String formatEvent(final LogEvent event, final Layout layout) { + return new String(layout.toByteArray(event)); + } + } + + LoggerContext ctx = LoggerContext.getContext(); + + @Test + public void testMarkerPatternSelector() throws Exception { + final PatternMatch[] patterns = new PatternMatch[1]; + patterns[0] = new PatternMatch("FLOW", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n"); + // @formatter:off + final PatternSelector selector = MarkerPatternSelector.newBuilder() + .setProperties(patterns) + .setDefaultPattern("%d %-5p [%t]: %m%n") + .setAlwaysWriteExceptions(true) + .setNoConsoleNoAnsi(true) + .setConfiguration(ctx.getConfiguration()) + .build(); + // @formatter:on + final PatternLayout layout = PatternLayout.newBuilder().setPatternSelector(selector) + .setConfiguration(ctx.getConfiguration()).build(); + final LogEvent event1 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternSelectorTest$FauxLogger") + .setMarker(MarkerManager.getMarker("FLOW")) + .setLevel(Level.TRACE) // + .setIncludeLocation(true) + .setMessage(new SimpleMessage("entry")).build(); + final String result1 = new FauxLogger().formatEvent(event1, layout); + final String expectSuffix1 = String.format("====== PatternSelectorTest.testMarkerPatternSelector:61 entry ======%n"); + assertTrue(result1.endsWith(expectSuffix1), "Unexpected result: " + result1); + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world 1!")).build(); + final String result2 = new String(layout.toByteArray(event2)); + final String expectSuffix2 = String.format("Hello, world 1!%n"); + assertTrue(result2.endsWith(expectSuffix2), "Unexpected result: " + result2); + } + + @Test + public void testLevelPatternSelector() throws Exception { + final PatternMatch[] patterns = new PatternMatch[1]; + patterns[0] = new PatternMatch("TRACE", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n"); + final PatternSelector selector = LevelPatternSelector.createSelector(patterns, "%d %-5p [%t]: %m%n", true, true, ctx.getConfiguration()); + final PatternLayout layout = PatternLayout.newBuilder().setPatternSelector(selector) + .setConfiguration(ctx.getConfiguration()).build(); + final LogEvent event1 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternSelectorTest$FauxLogger") + .setLevel(Level.TRACE) // + .setIncludeLocation(true) + .setMessage(new SimpleMessage("entry")).build(); + final String result1 = new FauxLogger().formatEvent(event1, layout); + final String expectSuffix1 = String.format("====== PatternSelectorTest.testLevelPatternSelector:85 entry ======%n"); + assertTrue(result1.endsWith(expectSuffix1), "Unexpected result: " + result1); + final LogEvent event2 = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world 1!")).build(); + final String result2 = new String(layout.toByteArray(event2)); + final String expectSuffix2 = String.format("Hello, world 1!%n"); + assertTrue(result2.endsWith(expectSuffix2), "Unexpected result: " + result2); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java new file mode 100644 index 00000000000..d053b92e51d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java @@ -0,0 +1,618 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.function.Function; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.net.Facility; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.core.util.ProcessIdUtil; +import org.apache.logging.log4j.message.StructuredDataCollectionMessage; +import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@UsingAnyThreadContext +public class Rfc5424LayoutTest { + LoggerContext ctx = LoggerContext.getContext(); + Logger root = ctx.getRootLogger(); + + private static final String PROCESSID = ProcessIdUtil.getProcessId(); + private static final String line1 = String.format("ATM %s - [RequestContext@3692 loginId=\"JohnDoe\"] starting mdc pattern test", PROCESSID); + private static final String line2 = String.format("ATM %s - [RequestContext@3692 loginId=\"JohnDoe\"] empty mdc", PROCESSID); + private static final String line3 = String.format("ATM %s - [RequestContext@3692 loginId=\"JohnDoe\"] filled mdc", PROCESSID); + private static final String line4 = + String.format("ATM %s Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"]" + + "[RequestContext@3692 ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete", PROCESSID); + private static final String lineEscaped3 = + String.format("ATM %s - [RequestContext@3692 escaped=\"Testing escaping #012 \\\" \\] \\\"\" loginId=\"JohnDoe\"] filled mdc", PROCESSID); + private static final String lineEscaped4 = + String.format("ATM %s Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"]" + + "[RequestContext@3692 escaped=\"Testing escaping #012 \\\" \\] \\\"\" ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete", + PROCESSID); + private static final String collectionLine1 = "[Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" " + + "ToAccount=\"123456\"]"; + private static final String collectionLine2 = "[Extra@18060 Item1=\"Hello\" Item2=\"World\"]"; + private static final String collectionLine3 = "[RequestContext@3692 ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"]"; + private static final String collectionEndOfLine = "Transfer Complete"; + + + @BeforeAll + public static void setupClass() { + StatusLogger.getLogger().setLevel(Level.OFF); + final LoggerContext ctx = LoggerContext.getContext(); + ctx.getInjector().registerBinding(ConfigurationFactory.KEY, Lazy.lazy(BasicConfigurationFactory::new)); + ctx.reconfigure(); + } + + @AfterAll + public static void cleanupClass() { + LoggerContext.getContext().getInjector().removeBinding(ConfigurationFactory.KEY); + } + + /** + * Test case for MDC conversion pattern. + */ + @Test + public void testLayout() throws Exception { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + // set up appender + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + + appender.start(); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + ThreadContext.put("loginId", "JohnDoe"); + + // output starting message + root.debug("starting mdc pattern test"); + + root.debug("empty mdc"); + + ThreadContext.put("key1", "value1"); + ThreadContext.put("key2", "value2"); + + root.debug("filled mdc"); + + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + try { + final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + root.info(MarkerManager.getMarker("EVENT"), msg); + + List list = appender.getMessages(); + + assertTrue(list.get(0).endsWith(line1), "Expected line 1 to end with: " + line1 + " Actual " + list.get(0)); + assertTrue(list.get(1).endsWith(line2), "Expected line 2 to end with: " + line2 + " Actual " + list.get(1)); + assertTrue(list.get(2).endsWith(line3), "Expected line 3 to end with: " + line3 + " Actual " + list.get(2)); + assertTrue(list.get(3).endsWith(line4), "Expected line 4 to end with: " + line4 + " Actual " + list.get(3)); + + for (final String frame : list) { + int length = -1; + final int frameLength = frame.length(); + final int firstSpacePosition = frame.indexOf(' '); + final String messageLength = frame.substring(0, firstSpacePosition); + try { + length = Integers.parseInt(messageLength); + // the ListAppender removes the ending newline, so we expect one less size + assertEquals(frameLength, messageLength.length() + length); + } + catch (final NumberFormatException e) { + fail("Not a valid RFC 5425 frame"); + } + } + + appender.clear(); + + ThreadContext.remove("loginId"); + + root.debug("This is a test"); + + list = appender.getMessages(); + assertTrue(list.isEmpty(), "No messages expected, found " + list.size()); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + /** + * Test case for MDC conversion pattern. + */ + @Test + public void testCollection() throws Exception { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + // set up appender + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + + appender.start(); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + ThreadContext.put("loginId", "JohnDoe"); + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + try { + final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + final StructuredDataMessage msg2 = new StructuredDataMessage("Extra@18060", null, "Audit"); + msg2.put("Item1", "Hello"); + msg2.put("Item2", "World"); + final List messages = new ArrayList<>(); + messages.add(msg); + messages.add(msg2); + final StructuredDataCollectionMessage collectionMessage = new StructuredDataCollectionMessage(messages); + + root.info(MarkerManager.getMarker("EVENT"), collectionMessage); + + List list = appender.getMessages(); + String result = list.get(0); + assertTrue( + result.contains(collectionLine1), "Expected line to contain " + collectionLine1 + ", Actual " + result); + assertTrue( + result.contains(collectionLine2), "Expected line to contain " + collectionLine2 + ", Actual " + result); + assertTrue( + result.contains(collectionLine3), "Expected line to contain " + collectionLine3 + ", Actual " + result); + assertTrue( + result.endsWith(collectionEndOfLine), + "Expected line to end with: " + collectionEndOfLine + " Actual " + result); + + for (final String frame : list) { + int length = -1; + final int frameLength = frame.length(); + final int firstSpacePosition = frame.indexOf(' '); + final String messageLength = frame.substring(0, firstSpacePosition); + try { + length = Integers.parseInt(messageLength); + // the ListAppender removes the ending newline, so we expect one less size + assertEquals(frameLength, messageLength.length() + length); + } + catch (final NumberFormatException e) { + fail("Not a valid RFC 5425 frame"); + } + } + + appender.clear(); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + /** + * Test case for escaping newlines and other SD PARAM-NAME special characters. + */ + @Test + public void testEscape() throws Exception { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + // set up layout/appender + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, "#012", "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + + appender.start(); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + ThreadContext.put("loginId", "JohnDoe"); + + // output starting message + root.debug("starting mdc pattern test"); + + root.debug("empty mdc"); + + ThreadContext.put("escaped", "Testing escaping \n \" ] \""); + + root.debug("filled mdc"); + + ThreadContext.put("ipAddress", "192.168.0.120"); + ThreadContext.put("locale", Locale.US.getDisplayName()); + try { + final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit"); + msg.put("ToAccount", "123456"); + msg.put("FromAccount", "123457"); + msg.put("Amount", "200.00"); + root.info(MarkerManager.getMarker("EVENT"), msg); + + List list = appender.getMessages(); + + assertTrue(list.get(0).endsWith(line1), "Expected line 1 to end with: " + line1 + " Actual " + list.get(0)); + assertTrue(list.get(1).endsWith(line2), "Expected line 2 to end with: " + line2 + " Actual " + list.get(1)); + assertTrue(list.get(2).endsWith(lineEscaped3), + "Expected line 3 to end with: " + lineEscaped3 + " Actual " + list.get(2)); + assertTrue(list.get(3).endsWith(lineEscaped4), + "Expected line 4 to end with: " + lineEscaped4 + " Actual " + list.get(3)); + + appender.clear(); + + ThreadContext.remove("loginId"); + + root.debug("This is a test"); + + list = appender.getMessages(); + assertTrue(list.isEmpty(), "No messages expected, found " + list.size()); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + /** + * Test case for MDC exception conversion pattern. + */ + @Test + public void testException() throws Exception { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + // set up layout/appender + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", "%xEx", true, null, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + ThreadContext.put("loginId", "JohnDoe"); + + // output starting message + root.debug("starting mdc pattern test", new IllegalArgumentException("Test")); + + try { + + final List list = appender.getMessages(); + + assertTrue(list.size() > 1, "Not enough list entries"); + final String string = list.get(1); + assertTrue(string.contains("IllegalArgumentException"), "No Exception in " + string); + + appender.clear(); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + /** + * Test case for MDC logger field inclusion. + */ + @Test + public void testMDCLoggerFields() throws Exception { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + + final LoggerFields[] loggerFields = new LoggerFields[] { + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source", "%C.%M")}, null, null, false), + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source2", "%C.%M")}, null, null, false) + }; + + // set up layout/appender + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, loggerFields, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + // output starting message + root.info("starting logger fields test"); + + try { + + final List list = appender.getMessages(); + assertTrue(list.size() > 0, "Not enough list entries"); + assertTrue(list.get(0).contains("Rfc5424LayoutTest.testMDCLoggerFields"), "No class/method"); + + appender.clear(); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + @Test + public void testLoggerFields() { + final String[] fields = new String[] { + "[BAZ@32473 baz=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]", + "[RequestContext@3692 bar=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]", + "[SD-ID@32473 source=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]" + }; + final List expectedToContain = Arrays.asList(fields); + + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + + final LoggerFields[] loggerFields = new LoggerFields[] { + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source", "%C.%M")}, "SD-ID", + "32473", false), + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("baz", "%C.%M"), + new KeyValuePair("baz", "%C.%M") }, "BAZ", "32473", false), + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("bar", "%C.%M")}, null, null, false) + }; + + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + root.info("starting logger fields test"); + + try { + + final List list = appender.getMessages(); + assertTrue(list.size() > 0, "Not enough list entries"); + final String message = list.get(0); + assertTrue(message.contains("Rfc5424LayoutTest.testLoggerFields"), "No class/method"); + for (final String value : expectedToContain) { + assertTrue(message.contains(value), "Message expected to contain " + value + " but did not"); + } + appender.clear(); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + @Test + public void testDiscardEmptyLoggerFields() { + final String mdcId = "RequestContext"; + + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + + final LoggerFields[] loggerFields = new LoggerFields[] { + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("dummy", Strings.EMPTY), + new KeyValuePair("empty", Strings.EMPTY)}, "SD-ID", "32473", true), + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("baz", "%C.%M"), + new KeyValuePair("baz", "%C.%M") }, "BAZ", "32473", false), + LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("bar", "%C.%M")}, null, null, false) + }; + + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, mdcId, + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + root.info("starting logger fields test"); + + try { + + final List list = appender.getMessages(); + assertTrue(list.size() > 0, "Not enough list entries"); + final String message = list.get(0); + assertFalse(message.contains("SD-ID"), "SD-ID should have been discarded"); + assertTrue(message.contains("BAZ"), "BAZ should have been included"); + assertTrue(message.contains(mdcId), mdcId + "should have been included"); + appender.clear(); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + @Test + public void testSubstituteStructuredData() { + final String mdcId = "RequestContext"; + + final String expectedToContain = String.format("ATM %s MSG-ID - Message", PROCESSID); + + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, false, mdcId, + null, null, true, null, "ATM", "MSG-ID", "key1, key2, locale", null, null, null, false, null, null); + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + root.addAppender(appender); + root.setLevel(Level.DEBUG); + + root.info("Message"); + + try { + final List list = appender.getMessages(); + assertTrue(list.size() > 0, "Not enough list entries"); + final String message = list.get(0); + assertTrue(message.contains(expectedToContain), "Not the expected message received"); + appender.clear(); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + @Test + public void testParameterizedMessage() { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + // set up appender + final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", + null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, null, null); + + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + // set appender on root and set level to debug + root.addAppender(appender); + root.setLevel(Level.DEBUG); + root.info("Hello {}", "World"); + try { + final List list = appender.getMessages(); + assertTrue(list.size() > 0, "Not enough list entries"); + final String message = list.get(0); + assertTrue(message.contains("Hello World"), + "Incorrect message. Expected - Hello World, Actual - " + message); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + @Test + void testLayoutBuilder() { + for (final Appender appender : root.getAppenders().values()) { + root.removeAppender(appender); + } + + final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder() + .setFacility(Facility.LOCAL0) + .setId("Event") + .setEin("1234.56.7") + .setIncludeMDC(true) + .setMdcId("RequestContext") + .setIncludeNL(true) + .setAppName("ATM") + .setExcludes("key1, key2, locale") + .setUseTLSMessageFormat(true) + .build(); + + final ListAppender appender = new ListAppender("List", null, layout, true, false); + appender.start(); + + root.addAppender(appender); + root.setLevel(Level.DEBUG); + root.info("Hello {}", "World"); + try { + final List list = appender.getMessages(); + assertTrue(list.size() > 0, "Not enough list entries"); + final String message = list.get(0); + assertTrue(message.contains("Hello World"), + "Incorrect message. Expected - Hello World, Actual - " + message); + } finally { + root.removeAppender(appender); + appender.stop(); + } + } + + @Test + public void testLayoutBuilderDefaultValues() { + final Rfc5424Layout layout = new Rfc5424Layout.Rfc5424LayoutBuilder().build(); + checkDefaultValues(layout); + + final PluginNamespace corePlugins = ctx.getInjector().getInstance(Core.PLUGIN_NAMESPACE_KEY); + final PluginType pluginType = corePlugins.get("Rfc5424Layout"); + assertNotNull(pluginType); + final Node node = new Node(null, "Rfc5424Layout", pluginType); + node.getAttributes().put("name", "Rfc5242Layout"); + + final Injector injector = DI.createInjector() + .registerBinding(Keys.SUBSTITUTOR_KEY, Function::identity) + .registerBinding(Configuration.KEY, () -> ctx.getConfiguration()); + final Object obj = injector.configure(node); + assertThat(obj).isInstanceOf(Rfc5424Layout.class); + checkDefaultValues((Rfc5424Layout) obj); + } + + private void checkDefaultValues(final Rfc5424Layout layout) { + assertNotNull(layout); + assertEquals(Facility.LOCAL0, layout.getFacility()); + assertEquals(String.valueOf(Rfc5424Layout.DEFAULT_ENTERPRISE_NUMBER), layout.getEnterpriseNumber()); + assertEquals(true, layout.isIncludeMdc()); + assertEquals(Rfc5424Layout.DEFAULT_MDCID, layout.getMdcId()); + assertEquals(Rfc5424Layout.DEFAULT_ID, layout.getDefaultId()); + } + + @ParameterizedTest + @ValueSource(strings = { "123456789", "0", "2147483647", "123.45.6.78.9", "0.0.0.0.0.0.0.0.0.0.0.0.0.0" }) + void testLayoutBuilderValidEids(String eid) { + final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder() + .setEin(eid) + .build(); + + assertNotNull(layout); + } + + @ParameterizedTest + @ValueSource(strings = { "abc", "someEid", "-1" }) + void testLayoutBuilderInvalidEids(String eid) { + final AbstractStringLayout layout = new Rfc5424Layout.Rfc5424LayoutBuilder() + .setEin(eid) + .build(); + + assertNull(layout); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SpyByteBufferDestination.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java new file mode 100644 index 00000000000..96e6a944516 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java @@ -0,0 +1,310 @@ +package org.apache.logging.log4j.core.layout;/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the {@code TextEncoderHelper} class. + */ +public class StringBuilderEncoderTest { + + @Test + public void testEncodeText_TextFitCharBuff_BytesFitByteBuff() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024); + final StringBuilder text = createText(15); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17); + helper.encode(text, destination); + + assertEquals(0, destination.drainPoints.size(), "drained"); + assertEquals(text.length(), destination.buffer.position(), "destination.buf.pos"); + + for (int i = 0; i < text.length(); i++) { + assertEquals((byte) text.charAt(i), destination.buffer.get(i), "char at " + i); + } + } + + @Test + public void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024); + final StringBuilder text = createText(15); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(14, 15); + helper.encode(text, destination); + + assertEquals(1, destination.drainPoints.size(), "drained"); + assertEquals(0, destination.drainPoints.get(0).position, "drained[0].from"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).limit, "drained[0].to"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).length(), "drained[0].length"); + assertEquals(text.length() - destination.buffer.capacity(), + destination.buffer.position(), "destination.buf.pos"); + + for (int i = 0; i < destination.buffer.capacity(); i++) { + assertEquals((byte) text.charAt(i), destination.drained.get(i), "char at " + i); + } + for (int i = destination.buffer.capacity(); i < text.length(); i++) { + final int bufIx = i - destination.buffer.capacity(); + assertEquals((byte) text.charAt(i), destination.buffer.get(bufIx), "char at " + i); + } + } + + @Test + public void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff_MultiplePasses() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024); + final StringBuilder text = createText(15); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(4, 20); + helper.encode(text, destination); + + assertEquals(3, destination.drainPoints.size(), "drained"); + assertEquals(0, destination.drainPoints.get(0).position, "drained[0].from"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).limit, "drained[0].to"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(0).length(), "drained[0].length"); + assertEquals(0, destination.drainPoints.get(1).position, "drained[1].from"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(1).limit, "drained[1].to"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(1).length(), "drained[1].length"); + assertEquals(0, destination.drainPoints.get(2).position, "drained[2].from"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(2).limit, "drained[2].to"); + assertEquals(destination.buffer.capacity(), destination.drainPoints.get(2).length(), "drained[2].length"); + assertEquals(text.length() - 3 * destination.buffer.capacity(), + destination.buffer.position(), "destination.buf.pos"); + + for (int i = 0; i < 3 * destination.buffer.capacity(); i++) { + assertEquals((byte) text.charAt(i), destination.drained.get(i), "char at " + i); + } + for (int i = 3 * destination.buffer.capacity(); i < text.length(); i++) { + final int bufIx = i - 3 * destination.buffer.capacity(); + assertEquals((byte) text.charAt(i), destination.buffer.get(bufIx), "char at " + i); + } + } + + @Test + public void testEncodeText_TextDoesntFitCharBuff_BytesFitByteBuff() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); + final StringBuilder text = createText(15); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17); + helper.encode(text, destination); + + assertEquals(0, destination.drainPoints.size(), "drained"); + assertEquals(text.length(), destination.buffer.position(), "destination.buf.pos"); + + for (int i = 0; i < text.length(); i++) { + assertEquals((byte) text.charAt(i), destination.buffer.get(i), "char at " + i); + } + } + + @Test + public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesFitByteBuff() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); + final StringBuilder text = new StringBuilder( // 日本語テスト文章 + "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50); + helper.encode(text, destination); + + assertEquals(0, destination.drainPoints.size(), "drained"); + destination.drain(destination.getByteBuffer()); + + final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8); + for (int i = 0; i < utf8.length; i++) { + assertEquals(utf8[i], destination.drained.get(i), "byte at " + i); + } + } + + @Test + public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesFitByteBuff() throws Exception { + final Charset SHIFT_JIS = Charset.forName("Shift_JIS"); + final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 8 * 1024); + final StringBuilder text = new StringBuilder( // 日本語テスト文章 + "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50); + helper.encode(text, destination); + + assertEquals(0, destination.drainPoints.size(), "drained"); + destination.drain(destination.getByteBuffer()); + + final byte[] bytes = text.toString().getBytes(SHIFT_JIS); + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], destination.drained.get(i), "byte at " + i); + } + } + + @Test + public void testEncodeText_TextDoesntFitCharBuff_BytesDontFitByteBuff() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); + final StringBuilder text = createText(15); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 17); + helper.encode(text, destination); + + assertEquals(4, destination.drainPoints.size(), "drained"); + assertEquals(3, destination.buffer.position(), "destination.buf.pos"); + + for (int i = 0; i < text.length() - 3; i++) { + assertEquals((byte) text.charAt(i), destination.drained.get(i), "char at " + i); + } + for (int i = 0; i < 3; i++) { + assertEquals((byte) text.charAt(12 + i), destination.buffer.get(i), "char at " + (12 + i)); + } + } + + @Test + public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesDontFitByteBuff() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); + final StringBuilder text = new StringBuilder( // 日本語テスト文章 + "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); + helper.encode(text, destination); + + assertEquals(7, destination.drainPoints.size(), "drained"); + destination.drain(destination.getByteBuffer()); + + final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8); + for (int i = 0; i < utf8.length; i++) { + assertEquals(utf8[i], destination.drained.get(i), "byte at " + i); + } + } + + @Test + public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 5); + final StringBuilder text = new StringBuilder( // 日本語テスト文章日本語テスト文章 + "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); + helper.encode(text, destination); + + assertEquals(15, destination.drainPoints.size(), "drained"); + destination.drain(destination.getByteBuffer()); + + final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8); + for (int i = 0; i < utf8.length; i++) { + assertEquals(utf8[i], destination.drained.get(i), "byte at " + i); + } + } + + @Test + public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesDontFitByteBuff() throws Exception { + final Charset SHIFT_JIS = Charset.forName("Shift_JIS"); + final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 8 * 1024); + final StringBuilder text = new StringBuilder( // 日本語テスト文章 + "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); + helper.encode(text, destination); + + destination.drain(destination.getByteBuffer()); + + final byte[] bytes = text.toString().getBytes(SHIFT_JIS); + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], destination.drained.get(i), "byte at " + i); + } + } + + @Test + public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() throws Exception { + final Charset SHIFT_JIS = Charset.forName("Shift_JIS"); + final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 5); + final StringBuilder text = new StringBuilder( // 日本語テスト文章日本語テスト文章 + "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); + final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); + helper.encode(text, destination); + + destination.drain(destination.getByteBuffer()); + + final byte[] bytes = text.toString().getBytes(SHIFT_JIS); + for (int i = 0; i < bytes.length; i++) { + assertEquals(bytes[i], destination.drained.get(i), "byte at " + i); + } + } + + @Test + public void testCopyCopiesAllDataIfSuffientRemainingSpace() throws Exception { + final CharBuffer buff = CharBuffer.wrap(new char[16]); + final StringBuilder text = createText(15); + final int length = TextEncoderHelper.copy(text, 0, buff); + assertEquals(text.length(), length, "everything fits"); + for (int i = 0; i < length; i++) { + assertEquals(text.charAt(i), buff.get(i), "char at " + i); + } + assertEquals(text.length(), buff.position(), "position moved by length"); + } + + @Test + public void testCopyUpToRemainingSpace() throws Exception { + final CharBuffer buff = CharBuffer.wrap(new char[3]); + final StringBuilder text = createText(15); + final int length = TextEncoderHelper.copy(text, 0, buff); + assertEquals(buff.capacity(), length, "partial copy"); + for (int i = 0; i < length; i++) { + assertEquals(text.charAt(i), buff.get(i), "char at " + i); + } + assertEquals(0, buff.remaining(), "no space remaining"); + assertEquals(buff.capacity(), buff.position(), "position at end"); + } + + @Test + public void testCopyDoesNotWriteBeyondStringText() throws Exception { + final CharBuffer buff = CharBuffer.wrap(new char[5]); + assertEquals(0, buff.position(), "initial buffer position"); + final StringBuilder text = createText(2); + final int length = TextEncoderHelper.copy(text, 0, buff); + assertEquals(text.length(), length, "full copy"); + for (int i = 0; i < length; i++) { + assertEquals(text.charAt(i), buff.get(i), "char at " + i); + } + assertEquals(text.length(), buff.position(), "resulting buffer position"); + for (int i = length; i < buff.capacity(); i++) { + assertEquals(0, buff.get(i), "unset char at " + i); + } + } + + @Test + public void testCopyStartsAtBufferPosition() throws Exception { + final CharBuffer buff = CharBuffer.wrap(new char[10]); + final int START_POSITION = 5; + buff.position(START_POSITION); // set start position + final StringBuilder text = createText(15); + final int length = TextEncoderHelper.copy(text, 0, buff); + assertEquals(buff.capacity() - START_POSITION, length, "partial copy"); + for (int i = 0; i < length; i++) { + assertEquals(text.charAt(i), buff.get(START_POSITION + i), "char at " + i); + } + assertEquals(buff.capacity(), buff.position(), "buffer position at end"); + } + + @Test + public void testEncode_ALotWithoutErrors() throws Exception { + final StringBuilderEncoder helper = new StringBuilderEncoder(Charset.defaultCharset()); + final StringBuilder text = new StringBuilder("2016-04-13 21:07:47,487 DEBUG [org.apache.logging.log4j.perf.jmh.FileAppenderBenchmark.log4j2ParameterizedString-jmh-worker-1] FileAppenderBenchmark - This is a debug [2383178] message\r\n"); + final int DESTINATION_SIZE = 1024 * 1024; + final SpyByteBufferDestination destination = new SpyByteBufferDestination(256 * 1024, DESTINATION_SIZE); + + final int max = DESTINATION_SIZE / text.length(); + for (int i = 0; i < max; i++) { + helper.encode(text, destination); + } + // no error + } + + private StringBuilder createText(final int length) { + final StringBuilder result = new StringBuilder(length); + for (int i = 0; i < length; i++) { + result.append((char) (' ' + i)); // space=0x20 + } + return result; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java similarity index 76% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java index b5bea0391cb..a75753ebea4 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/layout/SyslogLayoutTest.java @@ -23,24 +23,22 @@ import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.BasicConfigurationFactory; import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.net.Facility; -import org.apache.logging.log4j.junit.ThreadContextRule; +import org.apache.logging.log4j.core.test.BasicConfigurationFactory; +import org.apache.logging.log4j.core.test.appender.ListAppender; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; +import org.apache.logging.log4j.test.junit.UsingAnyThreadContext; +import org.apache.logging.log4j.util.Lazy; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; -/** - * - */ +@UsingAnyThreadContext public class SyslogLayoutTest { LoggerContext ctx = LoggerContext.getContext(); Logger root = ctx.getRootLogger(); @@ -54,19 +52,16 @@ public class SyslogLayoutTest { static ConfigurationFactory cf = new BasicConfigurationFactory(); - @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); - - @BeforeClass + @BeforeAll public static void setupClass() { - ConfigurationFactory.setConfigurationFactory(cf); final LoggerContext ctx = LoggerContext.getContext(); + ctx.getInjector().registerBinding(ConfigurationFactory.KEY, Lazy.lazy(BasicConfigurationFactory::new)); ctx.reconfigure(); } - @AfterClass + @AfterAll public static void cleanupClass() { - ConfigurationFactory.removeConfigurationFactory(cf); + LoggerContext.getContext().getInjector().removeBinding(ConfigurationFactory.KEY); } /** @@ -115,9 +110,9 @@ public void testLayout() throws Exception { final List list = appender.getMessages(); - assertTrue("Expected line 1 to end with: " + line1 + " Actual " + list.get(0), list.get(0).endsWith(line1)); - assertTrue("Expected line 2 to end with: " + line2 + " Actual " + list.get(1), list.get(1).endsWith(line2)); - assertTrue("Expected line 3 to end with: " + line3 + " Actual " + list.get(2), list.get(2).endsWith(line3)); - assertTrue("Expected line 4 to end with: " + line4 + " Actual " + list.get(3), list.get(3).endsWith(line4)); + assertTrue(list.get(0).endsWith(line1), "Expected line 1 to end with: " + line1 + " Actual " + list.get(0)); + assertTrue(list.get(1).endsWith(line2), "Expected line 2 to end with: " + line2 + " Actual " + list.get(1)); + assertTrue(list.get(2).endsWith(line3), "Expected line 3 to end with: " + line3 + " Actual " + list.get(2)); + assertTrue(list.get(3).endsWith(line4), "Expected line 4 to end with: " + line4 + " Actual " + list.get(3)); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/log4j-customLevels.xml b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/log4j-customLevels.xml similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/log4j-customLevels.xml rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/log4j-customLevels.xml diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java new file mode 100644 index 00000000000..16d5f378922 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class CaseLookupTest { + + + @Test + public void testLookup() { + final String testStr = "JabberWocky"; + final String lower = "jabberwocky"; + final String upper = "JABBERWOCKY"; + StrLookup lookup = new LowerLookup(); + String value = lookup.lookup(null, testStr); + assertNotNull(value); + assertEquals(lower, value); + lookup = new UpperLookup(); + value = lookup.lookup(null, testStr); + assertNotNull(value); + assertEquals(upper, value); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/ContextMapLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/ContextMapLookupTest.java new file mode 100644 index 00000000000..57421a35286 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/ContextMapLookupTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import java.io.File; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runners.model.Statement; + +import static org.junit.Assert.*; + +public class ContextMapLookupTest { + + private static final String TESTKEY = "TestKey"; + private static final String TESTVAL = "TestValue"; + + private final LoggerContextRule context = new LoggerContextRule("ContextMapLookupTest.xml"); + + @Rule + public RuleChain chain = RuleChain.outerRule((base, description) -> new Statement() { + @Override + public void evaluate() throws Throwable { + final File logFile = new File("target", + description.getClassName() + '.' + description.getMethodName() + ".log"); + ThreadContext.put("testClassName", description.getClassName()); + ThreadContext.put("testMethodName", description.getMethodName()); + try { + base.evaluate(); + } finally { + ThreadContext.remove("testClassName"); + ThreadContext.remove("testMethodName"); + if (logFile.exists()) { + logFile.deleteOnExit(); + } + } + } + }).around(context); + + @Test + public void testLookup() { + ThreadContext.put(TESTKEY, TESTVAL); + final StrLookup lookup = new ContextMapLookup(); + String value = lookup.lookup(TESTKEY); + assertEquals(TESTVAL, value); + value = lookup.lookup("BadKey"); + assertNull(value); + } + + /** + * Demonstrates the use of ThreadContext in determining what log file name to use in a unit test. + * Inspired by LOG4J2-786. Note that this only works because the ThreadContext is prepared + * before the Logger instance is obtained. This use of ThreadContext and the associated + * ContextMapLookup can be used in many other ways in a config file. + */ + @Test + public void testFileLog() throws Exception { + final Logger logger = LogManager.getLogger(); + logger.info("Hello from testFileLog!"); + final File logFile = new File("target", this.getClass().getName() + ".testFileLog.log"); + assertTrue(logFile.exists()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java new file mode 100644 index 00000000000..5a36d982cfd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@StatusLoggerLevel("OFF") +public class DateLookupTest { + + @Test + public void testCorrectEvent() { + final LogEvent mockedEvent = mock(LogEvent.class); + final Calendar cal = Calendar.getInstance(); + cal.set(2011, Calendar.DECEMBER, 30, 10, 56, 35); + when(mockedEvent.getTimeMillis()).thenReturn(cal.getTimeInMillis()); + + final String lookupDate = new DateLookup().lookup(mockedEvent, "MM/dd/yyyy"); + assertEquals("12/30/2011", lookupDate); + } + + @Test + public void testValidKeyWithoutEvent() { + final String dateFormat = "MM/dd/yyyy"; + + final Calendar cal = Calendar.getInstance(); + final DateFormat formatter = new SimpleDateFormat(dateFormat); + cal.setTimeInMillis(System.currentTimeMillis()); + final String today = formatter.format(cal.getTime()); + cal.add(Calendar.DATE, 1); + final String tomorrow = formatter.format(cal.getTime()); + + final String lookupTime = new DateLookup().lookup(null, dateFormat); + // lookup gives current time, which by now could be tomorrow at midnight sharp + assertTrue(lookupTime.equals(today) || lookupTime.equals(tomorrow)); + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = { "bananas" }) + public void testInvalidKey(String key) { + // For invalid keys without event, the current time in default format should be returned. + // Checking this may depend on locale and exact time, and could become flaky. + // Therefore we just check that the result isn't null and that (formatting) exceptions are caught. + assertNotNull(new DateLookup().lookup(null, key)); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/EnvironmentLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EnvironmentLookupTest.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/EnvironmentLookupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EnvironmentLookupTest.java index 5cae1b1efa7..7636a1f2a64 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/EnvironmentLookupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EnvironmentLookupTest.java @@ -16,16 +16,12 @@ */ package org.apache.logging.log4j.core.lookup; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class EnvironmentLookupTest { - @Test public void testLookup() { final StrLookup lookup = new EnvironmentLookup(); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EventLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EventLookupTest.java new file mode 100644 index 00000000000..25006d2a207 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/EventLookupTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests {@link MarkerLookup}. + * + * @since 2.4 + */ +public class EventLookupTest { + + private static final String ABSENT_MARKER_NAME = "NONE"; + private final String markerName = "EventLookupTest"; + private final StrLookup strLookup = new EventLookup(); + + @Test + public void testLookupEventMarker() { + final Marker marker = MarkerManager.getMarker(markerName); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setMarker(marker) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage("Hello, world!")).build(); + final String value = strLookup.lookup(event, "Marker"); + assertEquals(markerName, value); + } + + @Test + public void testLookupEventMessage() { + String msg = "Hello, world!"; + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage(msg)).build(); + final String value = strLookup.lookup(event, "Message"); + assertEquals(msg, value); + } + + @Test + public void testLookupEventLevel() { + String msg = "Hello, world!"; + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage(msg)).build(); + final String value = strLookup.lookup(event, "Level"); + assertEquals(Level.INFO.toString(), value); + } + + @Test + public void testLookupEventTimestamp() { + String msg = "Hello, world!"; + long now = System.currentTimeMillis(); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setTimeMillis(now) + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage(msg)).build(); + final String value = strLookup.lookup(event, "Timestamp"); + assertEquals(Long.toString(now), value); + } + + @Test + public void testLookupEventLogger() { + String msg = "Hello, world!"; + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage(msg)).build(); + final String value = strLookup.lookup(event, "Logger"); + assertEquals(this.getClass().getName(), value); + } + + @Test + public void testLookupEventThreadName() { + String msg = "Hello, world!"; + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName(this.getClass().getName()) // + .setThreadName("Main") + .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // + .setLevel(Level.INFO) // + .setMessage(new SimpleMessage(msg)).build(); + final String value = strLookup.lookup(event, "ThreadName"); + assertEquals("Main", value); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java new file mode 100644 index 00000000000..10002f73720 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + +import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * + */ +public class InterpolatorTest { + + private static final String TESTKEY = "TestKey"; + private static final String TESTKEY2 = "TestKey2"; + private static final String TESTVAL = "TestValue"; + + private static final String TEST_CONTEXT_RESOURCE_NAME = "logging/context-name"; + private static final String TEST_CONTEXT_NAME = "app-1"; + + @BeforeAll + public static void beforeClass() { + System.setProperty(TESTKEY, TESTVAL); + System.setProperty(TESTKEY2, TESTVAL); + } + + @AfterAll + public static void afterClass() { + System.clearProperty(TESTKEY); + System.clearProperty(TESTKEY2); + } + + @Test + public void testGetDefaultLookup() { + final Map map = new HashMap<>(); + map.put(TESTKEY, TESTVAL); + final MapLookup defaultLookup = new MapLookup(map); + final Interpolator interpolator = new Interpolator(defaultLookup); + assertEquals(defaultLookup.getMap(), ((MapLookup) interpolator.getDefaultLookup()).getMap()); + assertSame(defaultLookup, interpolator.getDefaultLookup()); + } + + @Test + public void testLookup() { + final Map map = new HashMap<>(); + map.put(TESTKEY, TESTVAL); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + ThreadContext.put(TESTKEY, TESTVAL); + String value = lookup.lookup(TESTKEY); + assertEquals(TESTVAL, value); + value = lookup.lookup("ctx:" + TESTKEY); + assertEquals(TESTVAL, value); + value = lookup.lookup("sys:" + TESTKEY); + assertEquals(TESTVAL, value); + value = lookup.lookup("SYS:" + TESTKEY2); + assertEquals(TESTVAL, value); + value = lookup.lookup("BadKey"); + assertNull(value); + ThreadContext.clearMap(); + value = lookup.lookup("ctx:" + TESTKEY); + assertEquals(TESTVAL, value); + } + + private void assertLookupNotEmpty(final StrLookup lookup, final String key) { + final String value = lookup.lookup(key); + assertNotNull(value); + assertFalse(value.isEmpty()); + System.out.println(key + " = " + value); + } + + @Test + public void testLookupWithDefaultInterpolator() { + final StrLookup lookup = new Interpolator(); + String value = lookup.lookup("sys:" + TESTKEY); + assertEquals(TESTVAL, value); + value = lookup.lookup("env:PATH"); + assertNotNull(value); + value = lookup.lookup("date:yyyy-MM-dd"); + assertNotNull("No Date", value); + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + final String today = format.format(new Date()); + assertEquals(value, today); + assertLookupNotEmpty(lookup, "java:version"); + assertLookupNotEmpty(lookup, "java:runtime"); + assertLookupNotEmpty(lookup, "java:vm"); + assertLookupNotEmpty(lookup, "java:os"); + assertLookupNotEmpty(lookup, "java:locale"); + assertLookupNotEmpty(lookup, "java:hw"); + } + + + @Test + public void testInterpolatorMapMessageWithNoPrefix() { + final HashMap configProperties = new HashMap<>(); + configProperties.put("key", "configProperties"); + Interpolator interpolator = new Interpolator(configProperties); + final HashMap map = new HashMap<>(); + map.put("key", "mapMessage"); + LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(getClass().getName()) + .setLoggerFqcn(Logger.class.getName()) + .setLevel(Level.INFO) + .setMessage(new StringMapMessage(map)) + .build(); + assertEquals("configProperties", interpolator.lookup(event, "key")); + } + + @Test + public void testInterpolatorMapMessageWithNoPrefixConfigDoesntMatch() { + Interpolator interpolator = new Interpolator(Collections.emptyMap()); + final HashMap map = new HashMap<>(); + map.put("key", "mapMessage"); + LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(getClass().getName()) + .setLoggerFqcn(Logger.class.getName()) + .setLevel(Level.INFO) + .setMessage(new StringMapMessage(map)) + .build(); + assertNull( + interpolator.lookup(event, "key"), + "Values without a map prefix should not match MapMessages"); + } + + @Test + public void testInterpolatorMapMessageWithMapPrefix() { + final HashMap configProperties = new HashMap<>(); + configProperties.put("key", "configProperties"); + Interpolator interpolator = new Interpolator(configProperties); + final HashMap map = new HashMap<>(); + map.put("key", "mapMessage"); + LogEvent event = Log4jLogEvent.newBuilder() + .setLoggerName(getClass().getName()) + .setLoggerFqcn(Logger.class.getName()) + .setLevel(Level.INFO) + .setMessage(new StringMapMessage(map)) + .build(); + assertEquals("mapMessage", interpolator.lookup(event, "map:key")); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.java index 0db5db0233d..0ee103d6e58 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupTest.java @@ -15,30 +15,26 @@ */ package org.apache.logging.log4j.core.lookup; -import java.io.File; -import java.net.URISyntaxException; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationAware; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.impl.ContextAnchor; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.File; import static org.apache.logging.log4j.core.lookup.Log4jLookup.KEY_CONFIG_LOCATION; import static org.apache.logging.log4j.core.lookup.Log4jLookup.KEY_CONFIG_PARENT_LOCATION; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; -/** - * - */ -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class Log4jLookupTest { private final static File EXPECT = new File(System.getProperty("user.home"), "/a/b/c/d/e/log4j2.xml"); @@ -50,14 +46,14 @@ public class Log4jLookupTest { @Mock private ConfigurationSource configSrc; - @Before - public void setup() throws URISyntaxException { + @BeforeEach + public void setup() { ContextAnchor.THREAD_CONTEXT.set(mockCtx); given(config.getConfigurationSource()).willReturn(configSrc); given(configSrc.getFile()).willReturn(EXPECT); } - @After + @AfterEach public void cleanup() { ContextAnchor.THREAD_CONTEXT.set(null); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupWithSpacesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupWithSpacesTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupWithSpacesTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupWithSpacesTest.java index e5ecb490dd5..94ff966299f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupWithSpacesTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/Log4jLookupWithSpacesTest.java @@ -15,31 +15,26 @@ */ package org.apache.logging.log4j.core.lookup; -import java.io.File; -import java.net.MalformedURLException; -import java.net.URISyntaxException; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationAware; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.impl.ContextAnchor; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.File; import static org.apache.logging.log4j.core.lookup.Log4jLookup.KEY_CONFIG_LOCATION; import static org.apache.logging.log4j.core.lookup.Log4jLookup.KEY_CONFIG_PARENT_LOCATION; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; -/** - * - */ -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class Log4jLookupWithSpacesTest { private final static File EXPECT = new File(System.getProperty("user.home"), "/a a/b b/c c/d d/e e/log4j2 file.xml"); @@ -50,14 +45,14 @@ public class Log4jLookupWithSpacesTest { @Mock private ConfigurationSource configSrc; - @Before - public void setup() throws URISyntaxException, MalformedURLException { + @BeforeEach + public void setup() { ContextAnchor.THREAD_CONTEXT.set(mockCtx); given(config.getConfigurationSource()).willReturn(configSrc); given(configSrc.getFile()).willReturn(EXPECT); } - @After + @AfterEach public void cleanup() { ContextAnchor.THREAD_CONTEXT.set(null); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsJmxLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsJmxLookupTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsJmxLookupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsJmxLookupTest.java index be1f5873e34..16b1a31deb9 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsJmxLookupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsJmxLookupTest.java @@ -16,15 +16,15 @@ */ package org.apache.logging.log4j.core.lookup; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Tests {@link JmxRuntimeInputArgumentsLookup} from the command line, not a JUnit test. - * + * * From an IDE or CLI: --file foo.txt - * + * * @since 2.1 */ public class MainInputArgumentsJmxLookupTest { @@ -35,16 +35,16 @@ public static void main(final String[] args) { @Test public void testMap() { - final JmxRuntimeInputArgumentsLookup lookup = JmxRuntimeInputArgumentsLookup.JMX_SINGLETON; - assertEquals(null, lookup.lookup(null)); - assertEquals(null, lookup.lookup("X")); - assertEquals(null, lookup.lookup("foo.txt")); + final JmxRuntimeInputArgumentsLookup lookup = JmxRuntimeInputArgumentsLookup.getInstance(); + assertNull(lookup.lookup(null)); + assertNull(lookup.lookup("X")); + assertNull(lookup.lookup("foo.txt")); } public void callFromMain() { - final JmxRuntimeInputArgumentsLookup lookup = JmxRuntimeInputArgumentsLookup.JMX_SINGLETON; - assertEquals(null, lookup.lookup(null)); - assertEquals(null, lookup.lookup("X")); + final JmxRuntimeInputArgumentsLookup lookup = JmxRuntimeInputArgumentsLookup.getInstance(); + assertNull(lookup.lookup(null)); + assertNull(lookup.lookup("X")); // Eclipse adds -Dfile.encoding=Cp1252 // assertEquals("--file", lookup.lookup("0")); // assertEquals("foo.txt", lookup.lookup("1")); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupApp.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupApp.java new file mode 100644 index 00000000000..44149a81791 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupApp.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; + +/** + * Tests {@link org.apache.logging.log4j.core.lookup.MainMapLookup#MAIN_SINGLETON} from the command line, not a real + * JUnit test. + * + * From an IDE or CLI: --file foo.txt + * + * @since 2.4 + */ +public class MainInputArgumentsLookupApp { + + public static void main(final String[] args) { + MainMapLookup.setMainArguments(args); + try (final LoggerContext ctx = Configurator.initialize(MainInputArgumentsLookupApp.class.getName(), + "target/test-classes/log4j-lookup-main.xml")) { + LogManager.getLogger().error("this is an error message"); + } + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java index f6bad2251fe..9245621229d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java @@ -16,11 +16,13 @@ */ package org.apache.logging.log4j.core.lookup; +import org.apache.logging.log4j.core.LogEvent; + import java.util.Map; /** * Work in progress, saved for future experimentation. - * + * * TODO The goal is to use the Sun debugger API to find the main arg values on the stack. */ public class MainInputArgumentsMapLookup extends MapLookup { @@ -34,7 +36,7 @@ public class MainInputArgumentsMapLookup extends MapLookup { final StackTraceElement[] stackTraceElements = entry.getValue(); entry.getKey(); // Can't use the thread name to look for "main" since anyone can set it. - // Can't use thread ID since it can be any positive value, and is likely vender dependent. Oracle seems to + // Can't use thread ID since it can be any positive value, and is likely vendor dependent. Oracle seems to // use 1. // We are left to look for "main" at the top of the stack if (stackTraceElements != null) { @@ -54,4 +56,18 @@ public class MainInputArgumentsMapLookup extends MapLookup { public MainInputArgumentsMapLookup(final Map map) { super(map); } + + @Override + public String lookup(final LogEvent event, final String key) { + return lookup(key); + } + + @Override + public String lookup(final String key) { + if (key == null) { + return null; + } + Map map = getMap(); + return map == null ? null : map.get(key); + } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainLookupTest.java new file mode 100644 index 00000000000..9c45774d765 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MainLookupTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests MainLookup. + */ +public class MainLookupTest { + + @Test + public void testMainArgs(){ + MainMapLookup.setMainArguments("--file", "foo.txt", "--verbose", "-x", "bar"); + String str ="${key} ${main:-1} ${main:0} ${main:1} ${main:2} ${main:3} ${main:4} ${main:\\--file} ${main:foo.txt} ${main:\\--verbose} ${main:\\-x} ${main:bar} ${main:\\--quiet:-true}"; + Map properties = new HashMap(); + properties.put("key", "value"); + properties.put("bar", "default_bar_value"); + Interpolator lookup = new Interpolator(properties); + StrSubstitutor substitutor = new StrSubstitutor(lookup); + String replacedValue = substitutor.replace(null, str); + String[] values = replacedValue.split(" "); + assertEquals("value", values[0], "Item 0 is incorrect "); + assertEquals("1", values[1], "Item 1 is incorrect "); + assertEquals("--file", values[2], "Item 2 is incorrect"); + assertEquals("foo.txt", values[3], "Item 3 is incorrect"); + assertEquals("--verbose", values[4], "Item 4 is incorrect"); + assertEquals("-x", values[5], "Item 5 is incorrect"); + assertEquals("bar", values[6], "Iten 6 is incorrect"); + assertEquals("foo.txt", values[7], "Item 7 is incorrect"); + assertEquals("--verbose", values[8], "Item 8 is incorrect"); + assertEquals("-x", values[9], "Item 9 is incorrect"); + assertEquals("bar", values[10], "Item 10 is incorrect"); + assertEquals("default_bar_value", values[11], "Item 11 is incorrect"); + assertEquals("true", values[12], "Item 12 is incorrect"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java new file mode 100644 index 00000000000..426c717f318 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import java.util.HashMap; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests {@link MapLookup}. + */ +public class MapLookupTest { + + @Test + public void testEmptyMap() { + final MapLookup lookup = new MapLookup(new HashMap()); + assertNull(lookup.lookup(null)); + assertNull(lookup.lookup("X")); + } + + @Test + public void testMap() { + final HashMap map = new HashMap<>(); + map.put("A", "B"); + final MapLookup lookup = new MapLookup(map); + assertNull(lookup.lookup(null)); + assertEquals("B", lookup.lookup("A")); + } + + @Test + public void testNullMap() { + final MapLookup lookup = new MapLookup(); + assertNull(lookup.lookup(null)); + assertNull(lookup.lookup("X")); + } + + @Test + public void testMainMap() { + MainMapLookup.setMainArguments("--file", "foo.txt"); + final MapLookup lookup = MainMapLookup.MAIN_SINGLETON; + assertNull(lookup.lookup(null)); + assertNull(lookup.lookup("X")); + assertEquals("--file", lookup.lookup("0")); + assertEquals("foo.txt", lookup.lookup("1")); + assertEquals("foo.txt", lookup.lookup("--file")); + assertNull(lookup.lookup("foo.txt")); + } + + @Test + public void testEventStringMapMessage() { + final HashMap map = new HashMap<>(); + map.put("A", "B"); + final HashMap eventMap = new HashMap<>(); + eventMap.put("A1", "B1"); + final StringMapMessage message = new StringMapMessage(eventMap); + final LogEvent event = Log4jLogEvent.newBuilder() + .setMessage(message) + .build(); + final MapLookup lookup = new MapLookup(map); + assertEquals("B", lookup.lookup(event, "A")); + assertEquals("B1", lookup.lookup(event, "A1")); + } + + @Test + public void testEventMapMessage() { + final HashMap map = new HashMap<>(); + map.put("A", "B"); + final HashMap eventMap = new HashMap<>(); + eventMap.put("A1", 11); + final MapMessage message = new MapMessage<>(eventMap); + final LogEvent event = Log4jLogEvent.newBuilder() + .setMessage(message) + .build(); + final MapLookup lookup = new MapLookup(map); + assertEquals("B", lookup.lookup(event, "A")); + assertEquals("11", lookup.lookup(event, "A1")); + } + + @Test + public void testLookupMapMessageIsCheckedBeforeDefaultMap() { + final HashMap map = new HashMap<>(); + map.put("A", "ADefault"); + map.put("B", "BDefault"); + final HashMap eventMap = new HashMap<>(); + eventMap.put("A", "AEvent"); + final MapMessage message = new MapMessage<>(eventMap); + final LogEvent event = Log4jLogEvent.newBuilder() + .setMessage(message) + .build(); + final MapLookup lookup = new MapLookup(map); + assertEquals("AEvent", lookup.lookup(event, "A")); + assertEquals("BDefault", lookup.lookup(event, "B")); + } + + @Test + public void testNullEvent() { + final HashMap map = new HashMap<>(); + map.put("A", "B"); + final MapLookup lookup = new MapLookup(map); + assertEquals("B", lookup.lookup(null, "A")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupConfigTest.java new file mode 100644 index 00000000000..eac6099bc83 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupConfigTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests {@link MarkerLookup} with a configuration file. + * + * @since 2.4 + */ +@LoggerContextSource("log4j-marker-lookup.yaml") +@Tag("yaml") +public class MarkerLookupConfigTest { + + public static final Marker PAYLOAD = MarkerManager.getMarker("PAYLOAD"); + private static final String PAYLOAD_LOG = "Message in payload.log"; + + public static final Marker PERFORMANCE = MarkerManager.getMarker("PERFORMANCE"); + + private static final String PERFORMANCE_LOG = "Message in performance.log"; + public static final Marker SQL = MarkerManager.getMarker("SQL"); + private static final String SQL_LOG = "Message in sql.log"; + + @Test + public void test() throws IOException { + final Logger logger = LogManager.getLogger(); + logger.info(SQL, SQL_LOG); + logger.info(PAYLOAD, PAYLOAD_LOG); + logger.info(PERFORMANCE, PERFORMANCE_LOG); + { + final String log = FileUtils.readFileToString(new File("target/logs/sql.log"), StandardCharsets.UTF_8); + assertTrue(log.contains(SQL_LOG)); + assertFalse(log.contains(PAYLOAD_LOG)); + assertFalse(log.contains(PERFORMANCE_LOG)); + } + { + final String log = FileUtils.readFileToString(new File("target/logs/payload.log"), StandardCharsets.UTF_8); + assertFalse(log.contains(SQL_LOG)); + assertTrue(log.contains(PAYLOAD_LOG)); + assertFalse(log.contains(PERFORMANCE_LOG)); + } + { + final String log = FileUtils.readFileToString(new File("target/logs/performance.log"), StandardCharsets.UTF_8); + assertFalse(log.contains(SQL_LOG)); + assertFalse(log.contains(PAYLOAD_LOG)); + assertTrue(log.contains(PERFORMANCE_LOG)); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java index d26f8959700..f55121e28d0 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java @@ -16,20 +16,19 @@ */ package org.apache.logging.log4j.core.lookup; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests {@link MarkerLookup}. - * + * * @since 2.4 */ public class MarkerLookupTest { @@ -93,4 +92,4 @@ public void testLookupNonExistant() { assertNull(value); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/PropertiesLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/PropertiesLookupTest.java new file mode 100644 index 00000000000..2c708f08db3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/PropertiesLookupTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.lookup; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link PropertiesLookup}. + */ +public class PropertiesLookupTest { + + @Test + public void testGetProperties() { + final HashMap properties = new HashMap<>(); + properties.put("A", "1"); + assertEquals(properties, new PropertiesLookup(properties).getProperties()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookupTest.java new file mode 100644 index 00000000000..b2fd05dfd01 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookupTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class ResourceBundleLookupTest { + + @Test + public void testLookup() { + final StrLookup lookup = new ResourceBundleLookup(); + lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle_en:KeyA"); + assertEquals("ValueA", lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyA")); + } + + @Test + public void testLookupWithLocale() { + final StrLookup lookup = new ResourceBundleLookup(); + lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyA"); + assertEquals("ValueA", lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyA")); + } + + @Test + public void testMissingKey() { + final StrLookup lookup = new ResourceBundleLookup(); + assertNull(lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyUnkown")); + } + + @Test + public void testBadFormatBundleOnly() { + final StrLookup lookup = new ResourceBundleLookup(); + assertNull(lookup.lookup("X")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java new file mode 100644 index 00000000000..976bbb32a74 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java @@ -0,0 +1,336 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@StatusLoggerLevel("OFF") +public class StrSubstitutorTest { + + private static final String TESTKEY = "TestKey"; + private static final String TESTVAL = "TestValue"; + + + @BeforeAll + public static void before() { + System.setProperty(TESTKEY, TESTVAL); + } + + @AfterAll + public static void after() { + System.clearProperty(TESTKEY); + } + + @Test + public void testLookup() { + final Map map = new HashMap<>(); + map.put(TESTKEY, TESTVAL); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + ThreadContext.put(TESTKEY, TESTVAL); + String value = subst.replace("${TestKey}-${ctx:TestKey}-${sys:TestKey}"); + assertEquals("TestValue-TestValue-TestValue", value); + value = subst.replace("${BadKey}"); + assertEquals("${BadKey}", value); + + value = subst.replace("${BadKey:-Unknown}-${ctx:BadKey:-Unknown}-${sys:BadKey:-Unknown}"); + assertEquals("Unknown-Unknown-Unknown", value); + value = subst.replace("${BadKey:-Unknown}-${ctx:BadKey}-${sys:BadKey:-Unknown}"); + assertEquals("Unknown-${ctx:BadKey}-Unknown", value); + value = subst.replace("${BadKey:-Unknown}-${ctx:BadKey:-}-${sys:BadKey:-Unknown}"); + assertEquals("Unknown--Unknown", value); + } + + @Test + public void testJavaDocExample() { + final Map valuesMap = new HashMap<>(); + valuesMap.put("animal", "quick brown fox"); + valuesMap.put("target", "lazy dog"); + final String templateString = "The ${animal} jumped over the ${target}."; + final StrSubstitutor sub = new StrSubstitutor(valuesMap); + final String resolvedString = sub.replace(templateString); + assertEquals("The quick brown fox jumped over the lazy dog.", resolvedString); + } + + @Test + public void testDelimiterExampleFromJavaDoc() { + final Map valuesMap = new HashMap<>(); + valuesMap.put("animal", "quick brown fox"); + valuesMap.put("target", "lazy dog"); + final String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}"; + final StrSubstitutor sub = new StrSubstitutor(valuesMap); + final String resolvedString = sub.replace(templateString); + assertEquals("The quick brown fox jumped over the lazy dog. 1234567890", resolvedString); + } + + @Test + public void testEscapedRecursionExampleFromJavaDoc() { + final Map valuesMap = new HashMap<>(); + valuesMap.put("name", "x"); + final String templateString = "The variable $${${name}} must be used."; + final StrSubstitutor sub = new StrSubstitutor(valuesMap); + + sub.setEnableSubstitutionInVariables(false); + final String resolvedString = sub.replace(templateString); + assertEquals("The variable ${x} must be used.", resolvedString); + + // Due to escaping enabling recursion now should make no difference. + sub.setEnableSubstitutionInVariables(true); + final String resolvedStringWithRecursion = sub.replace(templateString); + assertEquals(resolvedString, resolvedStringWithRecursion); + } + + @Test + public void testPrePostfixRecursionExampleFromJavaDoc() { + final Map valuesMap = new HashMap<>(); + valuesMap.put("name", "x"); + final String templateString = "The variable ${$[name]} must be used."; + final StrSubstitutor sub = new StrSubstitutor(valuesMap, "$[", "]"); + final String resolvedString = sub.replace(templateString); + assertEquals("The variable ${x} must be used.", resolvedString); + } + + @Test + public void testRecursionExampleFromJavaDoc() { + final Map valuesMap = new HashMap<>(); + valuesMap.put("name", "x"); + valuesMap.put("x", "3"); + final String templateString = "The value ${${name}} must be used."; + final StrSubstitutor sub = new StrSubstitutor(valuesMap); + + sub.setEnableSubstitutionInVariables(false); + assertEquals("The value ${${name}} must be used.", sub.replace(templateString)); + + sub.setEnableSubstitutionInVariables(true); + assertEquals("The value 3 must be used.", sub.replace(templateString)); + } + + @Test + public void testValueEscapeDelimiter() { + final Map valuesMap = new HashMap<>(); + // Example from MainMapLookup. Key contains ":-" + valuesMap.put("main:--file", "path/file.txt"); + // Create substitutor, initially without support for escaping :- + final StrSubstitutor sub = new StrSubstitutor( + new PropertiesLookup(valuesMap), + StrSubstitutor.DEFAULT_PREFIX, + StrSubstitutor.DEFAULT_SUFFIX, + StrSubstitutor.DEFAULT_ESCAPE, + StrSubstitutor.DEFAULT_VALUE_DELIMITER, + null // Ensure valueEscapeMatcher == null + ); + // regular default values work without a valueEscapeMatcher. + assertEquals("3", sub.replace("${y:-3}")); + // variables with ':-' are treated as if they have a default value + assertEquals("-file", sub.replace("${main:--file}")); + // yet escaping doesn't work anymore (results in the original string). + assertEquals("${main:\\--file}", sub.replace("${main:\\--file}")); + + // ensure there is valueEscapeMatcher (by resetting the value delimiter) + sub.setValueDelimiter(StrSubstitutor.DEFAULT_VALUE_DELIMITER_STRING); + // now the escaped variable with ":-" in it will be resolved. + assertEquals("path/file.txt", sub.replace("${main:\\--file}")); + // default values continue to work: + assertEquals("no help", sub.replace("${main:\\--help:-no help}")); + // even in minimalistic corner case: + assertEquals("", sub.replace("${:\\-:-}")); + } + + @Test + public void testDefault() { + final Map map = new HashMap<>(); + map.put(TESTKEY, TESTVAL); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + ThreadContext.put(TESTKEY, TESTVAL); + //String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}"); + final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}"); + assertEquals("TestValue", value); + } + + @Test + public void testDefaultReferencesLookupValue() { + final Map map = new HashMap<>(); + map.put(TESTKEY, "${java:version}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(false); + final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}"); + assertEquals("${java:version}", value); + } + + @Test + public void testInfiniteSubstitutionOnString() { + final StrLookup lookup = new Interpolator(new MapLookup(new HashMap<>())); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(true); + String infiniteSubstitution = "${${::-${::-$${::-j}}}}"; + assertEquals(infiniteSubstitution, subst.replace(infiniteSubstitution)); + } + + @Test + public void testInfiniteSubstitutionOnStringBuilder() { + final StrLookup lookup = new Interpolator(new MapLookup(new HashMap<>())); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(true); + String infiniteSubstitution = "${${::-${::-$${::-j}}}}"; + assertEquals(infiniteSubstitution, subst.replace(null, new StringBuilder(infiniteSubstitution))); + } + + @Test + public void testRecursiveSubstitution() { + final Map map = new HashMap<>(); + map.put("first", "${ctx:first}"); + map.put("second", "secondValue"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(true); + assertEquals("${ctx:first} and secondValue", subst.replace("${ctx:first} and ${ctx:second}")); + } + + @Test + public void testRecursiveWithDefault() { + final Map map = new HashMap<>(); + map.put("first", "${ctx:first:-default}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(true); + assertEquals("default", subst.replace("${ctx:first}")); + } + + @Test + public void testRecursiveWithRecursiveDefault() { + final Map map = new HashMap<>(); + map.put("first", "${ctx:first:-${ctx:first}}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(true); + assertEquals("${ctx:first}", subst.replace("${ctx:first}")); + } + + @Test + public void testNestedSelfReferenceWithRecursiveEvaluation() { + final Map map = new HashMap<>(); + map.put("first", "${${ctx:first}}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(true); + assertEquals("${${ctx:first}}", subst.replace("${ctx:first}")); + } + + @Test + public void testRandomWithRecursiveDefault() { + final Map map = new HashMap<>(); + map.put("first", "${env:RANDOM:-${ctx:first}}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(true); + assertEquals("${ctx:first}", subst.replace("${ctx:first}")); + } + + @Test + public void testNoRecursiveEvaluationWithDefault() { + final Map map = new HashMap<>(); + map.put("first", "${java:version}"); + map.put("second", "${java:runtime}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(false); + assertEquals("${java:version}", subst.replace("${ctx:first:-${ctx:second}}")); + } + + @Test + public void testNoRecursiveEvaluationWithDepthOne() { + final Map map = new HashMap<>(); + map.put("first", "${java:version}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(false); + assertEquals("${java:version}", subst.replace("${ctx:first}")); + } + + @Test + public void testLookupsNestedWithoutRecursiveEvaluation() { + final Map map = new HashMap<>(); + map.put("first", "${java:version}"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(false); + assertEquals("${java:version}", subst.replace("${${lower:C}t${lower:X}:first}")); + } + + @Test + public void testLookupThrows() { + final StrSubstitutor subst = new StrSubstitutor(new Interpolator(new StrLookup() { + + @Override + public String lookup(String key) { + if ("throw".equals(key)) { + throw new RuntimeException(); + } + return "success"; + } + + @Override + public String lookup(LogEvent event, String key) { + return lookup(key); + } + })); + subst.setRecursiveEvaluationAllowed(false); + assertEquals("success ${foo:throw} success", subst.replace("${foo:a} ${foo:throw} ${foo:c}")); + } + + @Test + public void testTopLevelLookupsWithoutRecursiveEvaluation() { + final Map map = new HashMap<>(); + map.put("key", "VaLuE"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(false); + assertEquals("value", subst.replace("${lower:${ctx:key}}")); + } + + @Test + public void testTopLevelLookupsWithoutRecursiveEvaluation_doubleLower() { + final Map map = new HashMap<>(); + map.put("key", "VaLuE"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(false); + assertEquals("value", subst.replace("${lower:${lower:${ctx:key}}}")); + } + + @Test + public void testTopLevelLookupsWithoutRecursiveEvaluationAndDefaultValueLookup() { + final Map map = new HashMap<>(); + map.put("key2", "TWO"); + final StrLookup lookup = new Interpolator(new MapLookup(map)); + final StrSubstitutor subst = new StrSubstitutor(lookup); + subst.setRecursiveEvaluationAllowed(false); + assertEquals("two", subst.replace("${lower:${ctx:key1:-${ctx:key2}}}")); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StructuredDataLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StructuredDataLookupTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StructuredDataLookupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StructuredDataLookupTest.java index 09081dd13a5..737ff418e79 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StructuredDataLookupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/StructuredDataLookupTest.java @@ -21,13 +21,10 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class StructuredDataLookupTest { private static final String TESTKEY = "type"; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookupTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookupTest.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookupTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookupTest.java index 5749548c0da..e4db4cbcc8e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookupTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookupTest.java @@ -16,26 +16,23 @@ */ package org.apache.logging.log4j.core.lookup; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class SystemPropertiesLookupTest { private static final String TESTKEY = "TestKey"; private static final String TESTVAL = "TestValue"; - @BeforeClass + @BeforeAll public static void before() { System.setProperty(TESTKEY, TESTVAL); } - @AfterClass + @AfterAll public static void after() { System.clearProperty(TESTKEY); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java index 8333f19459d..1900548a67c 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java @@ -17,9 +17,9 @@ package org.apache.logging.log4j.core.message; import org.apache.logging.log4j.message.ThreadDumpMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests that ThreadDumpMessage uses ExtendedThreadInformation when available. @@ -31,6 +31,6 @@ public void testMessage() { final String message = msg.getFormattedMessage(); //System.out.print(message); - assertTrue("No header", message.contains(" Id=")); + assertTrue(message.contains(" Id="), "No header"); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/MutableLogEventWithReusableParamMsgTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/MutableLogEventWithReusableParamMsgTest.java new file mode 100644 index 00000000000..a9fa4b7b9a0 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/message/MutableLogEventWithReusableParamMsgTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.message; + +import org.apache.logging.log4j.core.impl.MutableLogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ReusableParameterizedMessage; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsSame.sameInstance; + +/** + * LOG4J2-1409 + */ +// test must be in log4j-core but in org.apache.logging.log4j.message package because it calls package-private methods +public class MutableLogEventWithReusableParamMsgTest { + @Test + public void testInteractionWithReusableParameterizedMessage() { + final MutableLogEvent evt = new MutableLogEvent(); + final MutableMessage msg = new MutableMessage(); + msg.set("Hello {} {} {}", 1, 2, 3); + evt.setMessage(msg); + evt.clear(); + + msg.set("Hello {}", new Object[]{1}); + evt.setMessage(msg); + evt.clear(); + + msg.set("Hello {}", 1); + evt.setMessage(msg); + evt.clear(); + + // Uncomment out this log event and the params gets reset correctly (No exception occurs) + // msg.set("Hello {}", 1); + // evt.setMessage(msg); + // evt.clear(); + + // Exception at this log event - as the params is set to 1! + msg.set("Hello {} {} {}", 1, 2, 3); + evt.setMessage(msg); + evt.clear(); + + Message mementoMessage = evt.memento(); + Message mementoMessageSecondInvocation = evt.memento(); + // MutableLogEvent.memento should be cached + assertThat(mementoMessage, sameInstance(mementoMessageSecondInvocation)); + } + + private static class MutableMessage extends ReusableParameterizedMessage { + private static final long serialVersionUID = 1153384568858161030L; + + public ReusableParameterizedMessage set(final String messagePattern, final Object p0) { + return super.set(messagePattern, p0); + } + public ReusableParameterizedMessage set(final String messagePattern, final Object p0, final Object p1, + final Object p2) { + return super.set(messagePattern, p0, p1, p2); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/PriorityTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/PriorityTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/PriorityTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/PriorityTest.java index 234bf6b7e0e..83eb42fefb3 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/PriorityTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/PriorityTest.java @@ -17,18 +17,15 @@ package org.apache.logging.log4j.core.net; import org.apache.logging.log4j.Level; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class PriorityTest { @Test public void testP1() { final int p = Priority.getPriority(Facility.AUTH, Level.INFO); - assertTrue("Expected priority value is 38, got "+ p, p == 38); + assertEquals(38, p, "Expected priority value is 38, got " + p); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SocketAppenderReconnectTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SocketAppenderReconnectTest.java new file mode 100644 index 00000000000..7fe111aea5d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/SocketAppenderReconnectTest.java @@ -0,0 +1,426 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.net.TcpSocketManager.HostResolver; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests reconnection support of {@link org.apache.logging.log4j.core.appender.SocketAppender}. + */ +@StatusLoggerLevel("OFF") +@Tag("sleepy") +class SocketAppenderReconnectTest { + + private static final long DEFAULT_POLL_MILLIS = 1_000L; + private static final int EPHEMERAL_PORT = 0; + private static final Logger LOGGER = StatusLogger.getLogger(); + + /** + * Tests if failures are propagated when reconnection fails. + * + * @see LOG4J2-2829 + */ + @Test + void repeating_reconnect_failures_should_be_propagated() throws Exception { + try (final LineReadingTcpServer server = new LineReadingTcpServer()) { + + // Start the server. + server.start("Main", EPHEMERAL_PORT); + final int port = server.serverSocket.getLocalPort(); + + // Initialize the logger context. + final LoggerContext loggerContext = initContext(port); + try { + + // Verify the initial working state. + verifyLoggingSuccess(server); + + // Stop the server, and verify the logging failure. + server.close(); + verifyLoggingFailure(); + + // Start the server again, and verify the logging success. + server.start("Main", port); + verifyLoggingSuccess(server); + + } + + // Shutdown the logger context. + finally { + Configurator.shutdown(loggerContext); + } + + } + } + + /** + * Tests if all the {@link InetSocketAddress}es returned by an {@link HostResolver} is used for fallback on reconnect attempts. + */ + @Test + void reconnect_should_fallback_when_there_are_multiple_resolved_hosts() throws Exception { + try (final LineReadingTcpServer primaryServer = new LineReadingTcpServer(); + final LineReadingTcpServer secondaryServer = new LineReadingTcpServer()) { + + // Start servers. + primaryServer.start("Primary", EPHEMERAL_PORT); + secondaryServer.start("Secondary", EPHEMERAL_PORT); + + // Mock the host resolver. + final FixedHostResolver hostResolver = FixedHostResolver.ofServers(primaryServer, secondaryServer); + TcpSocketManager.setHostResolver(hostResolver); + try { + + // Initialize the logger context. + final LoggerContext loggerContext = initContext( + // Passing an invalid port, since the resolution is supposed to be performed by the mocked host resolver anyway. + // Here, 0 does NOT mean an ephemeral port. + 0); + try { + + // Verify the initial working state on the primary server. + verifyLoggingSuccess(primaryServer); + + // Stop the primary server, and verify the logging success due to fallback on to the secondary server. + primaryServer.close(); + verifyLoggingSuccess(secondaryServer); + + } + + // Shutdown the logger context. + finally { + Configurator.shutdown(loggerContext); + } + + } finally { + // Reset the host resolver. + TcpSocketManager.setHostResolver(new HostResolver()); + } + + } + } + + private static LoggerContext initContext(final int port) { + + // Create the configuration builder. + final ConfigurationBuilder configBuilder = ConfigurationBuilderFactory + .newConfigurationBuilder() + .setStatusLevel(Level.ERROR) + .setConfigurationName(SocketAppenderReconnectTest.class.getSimpleName()); + + // Create the configuration. + final String appenderName = "Socket"; + final Configuration config = configBuilder + .add(configBuilder + .newAppender(appenderName, "SOCKET") + .addAttribute("host", "localhost") + .addAttribute("port", String.valueOf(port)) + .addAttribute("protocol", Protocol.TCP) + .addAttribute("ignoreExceptions", false) + .addAttribute("reconnectionDelayMillis", 10) + .addAttribute("immediateFlush", true) + .add(configBuilder + .newLayout("PatternLayout") + .addAttribute("pattern", "%m%n"))) + .add(configBuilder.newLogger("org.apache.logging.log4j", Level.DEBUG)) + .add(configBuilder + .newRootLogger(Level.ERROR) + .add(configBuilder.newAppenderRef(appenderName))) + .build(false); + + // Initialize the configuration. + return Configurator.initialize(config); + + } + + private static void verifyLoggingSuccess(final LineReadingTcpServer server) throws Exception { + final int messageCount = 100; + // noinspection ConstantConditions + assertTrue(messageCount > 1, "was expecting messageCount to be bigger than 1 due to LOG4J2-2829, found: " + messageCount); + final List expectedMessages = IntStream + .range(0, messageCount) + .mapToObj(messageIndex -> String.format("m%02d", messageIndex)) + .collect(Collectors.toList()); + final Logger logger = LogManager.getLogger(); + for (int messageIndex = 0; messageIndex < expectedMessages.size(); messageIndex++) { + final String message = expectedMessages.get(messageIndex); + // Due to socket initialization, the first write() might need some extra effort. + if (messageIndex == 0) { + awaitUntilSucceeds(() -> logger.info(message)); + } else { + logger.info(message); + } + } + expectedMessages.forEach(logger::info); + final List actualMessages = server.pollLines(messageCount); + assertEquals(expectedMessages, actualMessages); + } + + private static void awaitUntilSucceeds(final Runnable runnable) { + // These figures are collected via trial-and-error; nothing scientific to look for here. + final long pollIntervalMillis = DEFAULT_POLL_MILLIS; + final long timeoutSeconds = 120L; + await() + .pollInterval(pollIntervalMillis, TimeUnit.MILLISECONDS) + .atMost(timeoutSeconds, TimeUnit.SECONDS) + .until(() -> { + runnable.run(); + return true; + }); + } + + private static void verifyLoggingFailure() { + final Logger logger = LogManager.getLogger(); + int retryCount = 3; + // noinspection ConstantConditions + assertTrue(retryCount > 1, "was expecting retryCount to be bigger than 1 due to LOG4J2-2829, found: " + retryCount); + for (int i = 0; i < retryCount; i++) { + try { + logger.info("should fail #" + i); + fail("should have failed #" + i); + } catch (final AppenderLoggingException ignored) {} + } + } + + /** + * A simple TCP server implementation reading the accepted connection's input stream into a blocking queue of lines. + *

+ * The implementation is thread-safe, yet connections are handled sequentially, i.e., no parallelization. + * The input stream of the connection is decoded in UTF-8. + *

+ */ + private static final class LineReadingTcpServer implements AutoCloseable { + + private static final int UNBOUND_PORT = -1; + + private volatile boolean running; + + private ServerSocket serverSocket; + + private Socket clientSocket; + + private Thread readerThread; + + private final BlockingQueue lines = new LinkedBlockingQueue<>(); + + private LineReadingTcpServer() {} + + private synchronized void start(final String name, final int port) throws IOException { + if (!running) { + running = true; + serverSocket = createServerSocket(port); + readerThread = createReaderThread(name); + // Make sure the server socket is ready. + if (serverSocket.getLocalPort() == UNBOUND_PORT || !serverSocket.isBound()) { + try { + Thread.sleep(DEFAULT_POLL_MILLIS); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + assertNotEquals(UNBOUND_PORT, serverSocket.getLocalPort(), + () -> String.format("Server socket is not bound to port %s (0 = ephemeral). This can only happen if a machine runs out of ports.", port)); + } + } + + private ServerSocket createServerSocket(final int port) throws IOException { + final ServerSocket serverSocket = new ServerSocket(port); + serverSocket.setReuseAddress(true); + serverSocket.setSoTimeout(0); // Zero indicates accept() will block indefinitely. + return serverSocket; + } + + private Thread createReaderThread(final String name) { + final String threadName = "LineReadingTcpSocketServerReader-" + name; + final Thread thread = new Thread(this::acceptClients, threadName); + thread.setDaemon(true); // Avoid blocking JVM exit. + thread.setUncaughtExceptionHandler((ignored, error) -> LOGGER.error("uncaught reader thread exception", error)); + thread.start(); + return thread; + } + + private void acceptClients() { + try { + while (running) { + acceptClient(); + } + } catch (final Exception error) { + LOGGER.error("failed accepting client connections", error); + } + } + + private void acceptClient() throws Exception { + + // Accept the client connection. + final Socket clientSocket; + try { + clientSocket = serverSocket.accept(); + } catch (SocketException ignored) { + return; + } + clientSocket.setSoLinger(true, 0); // Enable immediate forceful close. + synchronized (this) { + if (running) { + this.clientSocket = clientSocket; + } + } + + // Read from the client. + try (final InputStream clientInputStream = clientSocket.getInputStream(); + final InputStreamReader clientReader = new InputStreamReader(clientInputStream, StandardCharsets.UTF_8); + final BufferedReader clientBufferedReader = new BufferedReader(clientReader)) { + while (running) { + final String line = clientBufferedReader.readLine(); + if (line == null) { + break; + } + lines.put(line); + } + } + + // Ignore connection failures. + catch (final SocketException ignored) {} + + // Clean up the client connection. + finally { + try { + synchronized (this) { + if (!clientSocket.isClosed()) { + clientSocket.shutdownOutput(); + clientSocket.close(); + } + this.clientSocket = null; + } + } catch (final Exception error) { + LOGGER.error("failed closing client socket", error); + } + } + + } + + @Override + public void close() throws Exception { + + // Stop the reader, if running. + Thread stoppedReaderThread = null; + synchronized (this) { + if (running) { + running = false; + // acceptClient() might have closed the client socket due to a connection failure and haven't created a new one yet. + // Hence, here we double-check if the client connection is in place. + if (clientSocket != null && !clientSocket.isClosed()) { + // Interrupting a thread is not sufficient to unblock operations waiting on socket I/O: https://stackoverflow.com/a/4426050/1278899 + // Hence, here we close the client socket to unblock the read from the client socket. + clientSocket.close(); + } + serverSocket.close(); + stoppedReaderThread = readerThread; + clientSocket = null; + serverSocket = null; + readerThread = null; + } + } + + // We wait for the termination of the reader thread outside the synchronized block. + // Otherwise, there is a chance of deadlock with this join() and the synchronized block inside the acceptClient(). + if (stoppedReaderThread != null) { + stoppedReaderThread.join(); + } + + } + + private List pollLines(@SuppressWarnings("SameParameterValue") final int count) throws InterruptedException, TimeoutException { + final List polledLines = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + final String polledLine = pollLine(); + polledLines.add(polledLine); + } + return polledLines; + } + + private String pollLine() throws InterruptedException, TimeoutException { + final String line = lines.poll(2, TimeUnit.SECONDS); + if (line == null) { + throw new TimeoutException(); + } + return line; + } + + } + + /** + * {@link HostResolver} implementation always resolving to the given list of {@link #addresses}. + */ + private static final class FixedHostResolver extends HostResolver { + + private final List addresses; + + private FixedHostResolver(List addresses) { + this.addresses = addresses; + } + + private static FixedHostResolver ofServers(LineReadingTcpServer... servers) { + List addresses = Arrays + .stream(servers) + .map(server -> (InetSocketAddress) server.serverSocket.getLocalSocketAddress()) + .collect(Collectors.toList()); + return new FixedHostResolver(addresses); + } + + @Override + public List resolveHost(String ignoredHost, int ignoredPort) { + return addresses; + } + + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java new file mode 100644 index 00000000000..e84e156bf09 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net; + +import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED; +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.util.Base64; +import java.util.Enumeration; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.sun.management.UnixOperatingSystemMXBean; + +/** + * Tests the UrlConnectionFactory + */ +public class UrlConnectionFactoryTest { + + private static final Logger LOGGER = LogManager.getLogger(UrlConnectionFactoryTest.class); + private static final String BASIC = "Basic "; + private static final String expectedCreds = "testuser:password"; + private static Server server; + private static final Base64.Decoder decoder = Base64.getDecoder(); + private static int port; + private static final int BUF_SIZE = 1024; + + @BeforeAll + public static void startServer() throws Exception { + try { + server = new Server(0); + final ServletContextHandler context = new ServletContextHandler(); + final ServletHolder defaultServ = new ServletHolder("default", TestServlet.class); + defaultServ.setInitParameter("resourceBase", System.getProperty("user.dir")); + defaultServ.setInitParameter("dirAllowed", "true"); + context.addServlet(defaultServ, "/"); + server.setHandler(context); + + // Start Server + server.start(); + port = ((ServerConnector) server.getConnectors()[0]).getLocalPort(); + } catch (Throwable ex) { + ex.printStackTrace(); + throw ex; + } + } + + @AfterAll + public static void stopServer() throws Exception { + server.stop(); + } + + @Test + public void testBadCrdentials() throws Exception { + System.setProperty("log4j2.Configuration.username", "foo"); + System.setProperty("log4j2.Configuration.password", "bar"); + System.setProperty("log4j2.Configuration.allowedProtocols", "http"); + final URI uri = new URI("http://localhost:" + port + "/log4j2-config.xml"); + final ConfigurationSource source = ConfigurationSource.fromUri(uri); + assertNull(source, "A ConfigurationSource should not have been returned"); + } + + @Test + public void withAuthentication() throws Exception { + System.setProperty("log4j2.Configuration.username", "testuser"); + System.setProperty("log4j2.Configuration.password", "password"); + System.setProperty("log4j2.Configuration.allowedProtocols", "http"); + final URI uri = new URI("http://localhost:" + port + "/log4j2-config.xml"); + final ConfigurationSource source = ConfigurationSource.fromUri(uri); + assertNotNull(source, "No ConfigurationSource returned"); + InputStream is = source.getInputStream(); + assertNotNull(is, "No data returned"); + is.close(); + final long lastModified = source.getLastModified(); + int result = verifyNotModified(uri, lastModified); + assertEquals(SC_NOT_MODIFIED, result,"File was modified"); + final File file = new File("target/test-classes/log4j2-config.xml"); + if (!file.setLastModified(System.currentTimeMillis())) { + fail("Unable to set last modified time"); + } + result = verifyNotModified(uri, lastModified); + assertEquals(SC_OK, result,"File was not modified"); + } + + private int verifyNotModified(final URI uri, final long lastModifiedMillis) throws Exception { + final HttpURLConnection urlConnection = UrlConnectionFactory.createConnection(uri.toURL(), + lastModifiedMillis, null, null); + urlConnection.connect(); + + try { + return urlConnection.getResponseCode(); + } catch (final IOException ioe) { + LOGGER.error("Error accessing configuration at {}: {}", uri, ioe.getMessage()); + return SC_INTERNAL_SERVER_ERROR; + } + } + + @Test + public void testNoJarFileLeak() throws Exception { + final URL url = new File("target/test-classes/jarfile.jar").toURI().toURL(); + // Retrieve using 'file:' + URL jarUrl = new URL("jar:" + url.toString() + "!/config/console.xml"); + long expected = getOpenFileDescriptorCount(); + UrlConnectionFactory.createConnection(jarUrl).getInputStream().close(); + assertEquals(expected, getOpenFileDescriptorCount()); + // Retrieve using 'http:' + jarUrl = new URL("jar:http://localhost:" + port + "/jarfile.jar!/config/console.xml"); + final File tmpDir = new File(System.getProperty("java.io.tmpdir")); + expected = tmpDir.list().length; + UrlConnectionFactory.createConnection(jarUrl).getInputStream().close(); + assertEquals(expected, tmpDir.list().length, "File descriptor leak"); + } + + private long getOpenFileDescriptorCount() { + final OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean(); + if (os instanceof UnixOperatingSystemMXBean) { + return ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount(); + } + return 0L; + } + + public static class TestServlet extends DefaultServlet { + + private static final long serialVersionUID = -2885158530511450659L; + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + Enumeration headers = request.getHeaders(HttpHeader.AUTHORIZATION.toString()); + if (headers == null) { + response.sendError(SC_UNAUTHORIZED, "No Auth header"); + return; + } + while (headers.hasMoreElements()) { + String authData = headers.nextElement(); + assertTrue(authData.startsWith(BASIC), "Not a Basic auth header"); + String credentials = new String(decoder.decode(authData.substring(BASIC.length()))); + if (!expectedCreds.equals(credentials)) { + response.sendError(SC_UNAUTHORIZED, "Invalid credentials"); + return; + } + } + final String servletPath = request.getServletPath(); + if (servletPath != null) { + final File file = new File("target/test-classes" + servletPath); + if (!file.exists()) { + response.sendError(SC_NOT_FOUND); + return; + } + long modifiedSince = request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString()); + long lastModified = (file.lastModified() / 1000) * 1000; + LOGGER.debug("LastModified: {}, modifiedSince: {}", lastModified, modifiedSince); + if (modifiedSince > 0 && lastModified <= modifiedSince) { + response.setStatus(SC_NOT_MODIFIED); + return; + } + response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(), lastModified); + response.setContentLengthLong(file.length()); + Files.copy(file.toPath(), response.getOutputStream()); + response.getOutputStream().flush(); + response.setStatus(SC_OK); + } else { + response.sendError(SC_BAD_REQUEST, "Unsupported request"); + } + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java similarity index 87% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java index a9b266fa487..73080243715 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java @@ -15,15 +15,15 @@ * limitations under the license. */ -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class EnvironmentPasswordProviderTest { - @Test(expected = NullPointerException.class) + @Test public void testConstructorDisallowsNull() { - new EnvironmentPasswordProvider(null); + assertThrows(NullPointerException.class, () -> new EnvironmentPasswordProvider(null)); } @Test @@ -35,4 +35,4 @@ public void testGetPasswordReturnsEnvironmentVariableValue() { final char[] actual = new EnvironmentPasswordProvider("PATH").getPassword(); assertArrayEquals(value.toCharArray(), actual); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProviderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProviderTest.java similarity index 84% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProviderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProviderTest.java index 26cda80a959..e7a6c939b28 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProviderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProviderTest.java @@ -19,9 +19,10 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; public class FilePasswordProviderTest { @@ -36,13 +37,13 @@ public void testGetPassword() throws Exception { assertArrayEquals(PASSWORD.toCharArray(), actual); } - @Test(expected = NullPointerException.class) + @Test public void testConstructorDisallowsNull() throws Exception { - new FilePasswordProvider(null); + assertThrows(NullPointerException.class, () -> new FilePasswordProvider(null)); } - @Test(expected = NoSuchFileException.class) + @Test public void testConstructorFailsIfFileDoesNotExist() throws Exception { - new FilePasswordProvider("nosuchfile"); + assertThrows(NoSuchFileException.class, () -> new FilePasswordProvider("nosuchfile")); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java new file mode 100644 index 00000000000..0ed2262ec7d --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net.ssl; + +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.Test; + +import java.security.KeyStore; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class KeyStoreConfigurationTest { + @SuppressWarnings("deprecation") + @Test + public void loadEmptyConfigurationDeprecated() { + assertThrows(StoreConfigurationException.class, + () -> new KeyStoreConfiguration(null, TestConstants.NULL_PWD, null, null)); + } + + @Test + public void loadEmptyConfiguration() { + assertThrows(StoreConfigurationException.class, + () -> new KeyStoreConfiguration(null, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null)); + } + + @Test + public void loadNotEmptyConfigurationDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") final KeyStoreConfiguration ksc = + new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), + TestConstants.KEYSTORE_TYPE, null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + } + + @Test + public void loadNotEmptyConfiguration() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), + TestConstants.KEYSTORE_TYPE, null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + } + + @Test + public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") final KeyStoreConfiguration ksc = + new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), + TestConstants.KEYSTORE_TYPE, null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @Test + public void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), + TestConstants.KEYSTORE_TYPE, null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @SuppressWarnings("deprecation") + @Test + public void wrongPasswordDeprecated() { + assertThrows(StoreConfigurationException.class, + () -> new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, "wrongPassword!", null, null)); + } + + @Test + public void wrongPassword() { + assertThrows(StoreConfigurationException.class, () -> new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, + new MemoryPasswordProvider("wrongPassword!".toCharArray()), null, null)); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/MemoryPasswordProviderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/MemoryPasswordProviderTest.java similarity index 91% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/MemoryPasswordProviderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/MemoryPasswordProviderTest.java index df4b5f25266..d4d67f1fad0 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/MemoryPasswordProviderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/MemoryPasswordProviderTest.java @@ -17,14 +17,14 @@ import java.util.Arrays; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class MemoryPasswordProviderTest { @Test public void testConstructorAllowsNull() { - assertEquals(null, new MemoryPasswordProvider(null).getPassword()); + assertNull(new MemoryPasswordProvider(null).getPassword()); } @Test diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.java new file mode 100644 index 00000000000..27370e28be5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactoryTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net.ssl; + +import java.util.Properties; + +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class SslConfigurationFactoryTest { + + private static void addKeystoreConfiguration(Properties props) { + props.setProperty("log4j2.keyStoreLocation", TestConstants.KEYSTORE_FILE_RESOURCE); + props.setProperty("log4j2.keyStoreKeyStoreType", TestConstants.KEYSTORE_TYPE); + } + + private static void addTruststoreConfiguration(Properties props) { + props.setProperty("log4j2.trustStoreLocation", TestConstants.TRUSTSTORE_FILE_RESOURCE); + props.setProperty("log4j2.trustStoreKeyStoreType", TestConstants.TRUSTSTORE_TYPE); + } + + @Test + public void testStaticConfiguration() { + final Properties props = new Properties(); + final PropertiesUtil util = new PropertiesUtil(props); + // No keystore and truststore -> no SslConfiguration + SslConfiguration sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNull(sslConfiguration); + // Only keystore + props.clear(); + addKeystoreConfiguration(props); + util.reload(); + sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNotNull(sslConfiguration); + assertNotNull(sslConfiguration.getKeyStoreConfig()); + assertNull(sslConfiguration.getTrustStoreConfig()); + // Only truststore + props.clear(); + addTruststoreConfiguration(props); + util.reload(); + sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNotNull(sslConfiguration); + assertNull(sslConfiguration.getKeyStoreConfig()); + assertNotNull(sslConfiguration.getTrustStoreConfig()); + // Both + props.clear(); + addKeystoreConfiguration(props); + addTruststoreConfiguration(props); + util.reload(); + sslConfiguration = SslConfigurationFactory.createSslConfiguration(util); + assertNotNull(sslConfiguration); + assertNotNull(sslConfiguration.getKeyStoreConfig()); + assertNotNull(sslConfiguration.getTrustStoreConfig()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java new file mode 100644 index 00000000000..f9f25fb9434 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net.ssl; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class SslConfigurationTest { + + private static final String TLS_TEST_HOST = "login.yahoo.com"; + private static final int TLS_TEST_PORT = 443; + + @SuppressWarnings("deprecation") + public static SslConfiguration createTestSslConfigurationResourcesDeprecated() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE_RESOURCE, + TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE_RESOURCE, + TestConstants.TRUSTSTORE_PWD(), null, null); + return SslConfiguration.createSSLConfiguration(null, ksc, tsc); + } + + public static SslConfiguration createTestSslConfigurationResources() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE_RESOURCE, + new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), TestConstants.KEYSTORE_TYPE, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE_RESOURCE, + new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); + return SslConfiguration.createSSLConfiguration(null, ksc, tsc); + } + + @SuppressWarnings("deprecation") + public static SslConfiguration createTestSslConfigurationFilesDeprecated() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, + TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, + TestConstants.TRUSTSTORE_PWD(), null, null); + return SslConfiguration.createSSLConfiguration(null, ksc, tsc); + } + + public static SslConfiguration createTestSslConfigurationFiles() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, + new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), TestConstants.KEYSTORE_TYPE, null); + final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, + new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); + return SslConfiguration.createSSLConfiguration(null, ksc, tsc); + } + + @Test + public void testGettersFromScratchFiles() throws StoreConfigurationException { + assertNotNull(createTestSslConfigurationFiles().getProtocol()); + assertNotNull(createTestSslConfigurationFiles().getKeyStoreConfig()); + assertNotNull(createTestSslConfigurationFiles().getSslContext()); + assertNotNull(createTestSslConfigurationFiles().getSslSocketFactory()); + assertNotNull(createTestSslConfigurationFiles().getTrustStoreConfig()); + } + + @Test + public void testGettersFromScratchResources() throws StoreConfigurationException { + assertNotNull(createTestSslConfigurationResources().getProtocol()); + assertNotNull(createTestSslConfigurationResources().getKeyStoreConfig()); + assertNotNull(createTestSslConfigurationResources().getSslContext()); + assertNotNull(createTestSslConfigurationResources().getSslSocketFactory()); + assertNotNull(createTestSslConfigurationResources().getTrustStoreConfig()); + } + + @Test + public void equals() { + assertEquals(SslConfiguration.createSSLConfiguration(null, null, null), + SslConfiguration.createSSLConfiguration(null, null, null)); + } + + @Test + public void emptyConfigurationDoesntCauseNullSSLSocketFactory() { + final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); + final SSLSocketFactory factory = sc.getSslSocketFactory(); + assertNotNull(factory); + } + + @Test + public void emptyConfigurationHasDefaultTrustStore() throws IOException { + final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); + final SSLSocketFactory factory = sc.getSslSocketFactory(); + try { + try (final SSLSocket clientSocket = (SSLSocket) factory.createSocket(TLS_TEST_HOST, TLS_TEST_PORT)) { + assertNotNull(clientSocket); + } + } catch (final UnknownHostException | ConnectException connectionTimeout) { + // this exception is thrown on Windows when host is behind a proxy that does not allow connection to external network + } + } + + @Test + public void connectionFailsWithoutValidServerCertificate() throws IOException, StoreConfigurationException { + final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, + new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); + final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, tsc); + final SSLSocketFactory factory = sc.getSslSocketFactory(); + try { + try (final SSLSocket clientSocket = (SSLSocket) factory.createSocket(TLS_TEST_HOST, TLS_TEST_PORT)) { + try (final OutputStream os = clientSocket.getOutputStream()) { + assertThrows(IOException.class, () -> os.write("GET config/login_verify2?".getBytes())); + } + } + } catch (final UnknownHostException | ConnectException connectionTimeout) { + // this exception is thrown on Windows when host is behind a proxy that does not allow connection to external network + } + } + + @Test + public void loadKeyStoreWithoutPassword() throws StoreConfigurationException { + final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, + new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); + final SslConfiguration sslConf = SslConfiguration.createSSLConfiguration(null, ksc, null); + final SSLSocketFactory factory = sslConf.getSslSocketFactory(); + assertNotNull(factory); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationTest.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationTest.java index 2224ab6a7c7..cf73ea4b10f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationTest.java @@ -16,12 +16,12 @@ */ package org.apache.logging.log4j.core.net.ssl; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -@Ignore +@Disabled public class StoreConfigurationTest> { @Test @@ -31,8 +31,8 @@ public void equalsWithNotNullValues() { final StoreConfiguration a = new StoreConfiguration<>(location, password); final StoreConfiguration b = new StoreConfiguration<>(location, password); - assertTrue(a.equals(b)); - assertTrue(b.equals(a)); + assertEquals(b, a); + assertEquals(a, b); } @Test @@ -51,7 +51,7 @@ public void equalsWithNullValues() { final StoreConfiguration a = new StoreConfiguration<>(null, new MemoryPasswordProvider(null)); final StoreConfiguration b = new StoreConfiguration<>(null, new MemoryPasswordProvider(null)); - assertTrue(a.equals(b)); - assertTrue(b.equals(a)); + assertEquals(b, a); + assertEquals(a, b); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java index b8dc2ea1d83..05f3286455f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogTestUtil.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.Random; +import org.apache.logging.log4j.core.test.net.ssl.TlsSyslogMessageFormat; + public class TlsSyslogTestUtil { public static final String ABC = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static final String NUMBERS = "0123456789"; diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java new file mode 100644 index 00000000000..a62f91ad64f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net.ssl; + +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; +import org.apache.logging.log4j.test.junit.StatusLoggerLevel; +import org.junit.jupiter.api.Test; + +import java.security.KeyStore; + +import static org.junit.jupiter.api.Assertions.*; + +@StatusLoggerLevel("OFF") +public class TrustStoreConfigurationTest { + @SuppressWarnings("deprecation") + @Test + public void loadEmptyConfigurationDeprecated() { + assertThrows(StoreConfigurationException.class, () -> new TrustStoreConfiguration(null, TestConstants.NULL_PWD, null, null)); + } + + @Test + public void loadEmptyConfiguration() { + assertThrows(StoreConfigurationException.class, () -> new TrustStoreConfiguration(null, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null)); + } + + @Test + public void loadConfigurationDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") final TrustStoreConfiguration ksc = + new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null, null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + } + + @Test + public void loadConfiguration() throws StoreConfigurationException { + final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); + final KeyStore ks = ksc.getKeyStore(); + assertNotNull(ks); + } + + @Test + public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { + @SuppressWarnings("deprecation") final TrustStoreConfiguration ksc = + new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null, null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @Test + public void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { + final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); + final KeyStore ks = ksc.getKeyStore(); + final KeyStore ks2 = ksc.getKeyStore(); + assertSame(ks, ks2); + } + + @SuppressWarnings("deprecation") + @Test + public void wrongPasswordDeprecated() { + assertThrows(StoreConfigurationException.class, () -> new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, "wrongPassword!".toCharArray(), null, null)); + } + + @Test + public void wrongPassword() { + assertThrows(StoreConfigurationException.class, () -> new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider("wrongPassword!".toCharArray()), null, null)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/CallerInformationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/CallerInformationTest.java new file mode 100644 index 00000000000..0d676eb5507 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/CallerInformationTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j2-calling-class.xml") +public class CallerInformationTest { + + @Test + public void testClassLogger(final LoggerContext context, @Named("Class") final ListAppender app) { + app.clear(); + final Logger logger = context.getLogger("ClassLogger"); + logger.info("Ignored message contents."); + logger.warn("Verifying the caller class is still correct."); + logger.error("Hopefully nobody breaks me!"); + final List messages = app.getMessages(); + assertEquals(3, messages.size(), "Incorrect number of messages."); + for (final String message : messages) { + assertEquals(this.getClass().getName(), message, "Incorrect caller class name."); + } + } + + @Test + public void testMethodLogger(final LoggerContext context, @Named("Method") final ListAppender app) { + app.clear(); + final Logger logger = context.getLogger("MethodLogger"); + logger.info("More messages."); + logger.warn("CATASTROPHE INCOMING!"); + logger.error("ZOMBIES!!!"); + logger.fatal("brains~~~"); + logger.info("Itchy. Tasty."); + final List messages = app.getMessages(); + assertEquals(5, messages.size(), "Incorrect number of messages."); + for (final String message : messages) { + assertEquals("testMethodLogger", message, "Incorrect caller method name."); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java new file mode 100644 index 00000000000..fdf6ca365e8 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java @@ -0,0 +1,463 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.TimeZone; + +import org.apache.logging.log4j.core.AbstractLogEvent; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedTimeZoneFormat; +import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.util.Strings; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(Parameterized.class) +public class DatePatternConverterTest { + + private static class MyLogEvent extends AbstractLogEvent { + private static final long serialVersionUID = 0; + + @Override + public Instant getInstant() { + MutableInstant result = new MutableInstant(); + result.initFromEpochMilli(getTimeMillis(), 123456); + return result; + } + + @Override + public long getTimeMillis() { + final Calendar cal = Calendar.getInstance(); + cal.set(2011, Calendar.DECEMBER, 30, 10, 56, 35); + cal.set(Calendar.MILLISECOND, 987); + return cal.getTimeInMillis(); + } + } + + /** + * SimpleTimePattern for DEFAULT. + */ + private static final String DEFAULT_PATTERN = FixedDateFormat.FixedFormat.DEFAULT.getPattern(); + + /** + * ISO8601 string literal. + */ + private static final String ISO8601 = FixedDateFormat.FixedFormat.ISO8601.name(); + + /** + * ISO8601_OFFSE_DATE_TIME_XX string literal. + */ + private static final String ISO8601_OFFSE_DATE_TIME_HHMM = FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHMM + .name(); + + /** + * ISO8601_OFFSET_DATE_TIME_XXX string literal. + */ + private static final String ISO8601_OFFSET_DATE_TIME_HHCMM = FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHCMM + .name(); + + private static final String[] ISO8601_FORMAT_OPTIONS = { ISO8601 }; + + @Parameterized.Parameters(name = "threadLocalEnabled={0}") + public static Collection data() { + return Arrays.asList(new Object[][]{{Boolean.TRUE}, {Boolean.FALSE}}); + } + + public DatePatternConverterTest(final Boolean threadLocalEnabled) { + Constants.setThreadLocalsEnabled(threadLocalEnabled); + } + + private static Date date(final int year, final int month, final int date) { + final Calendar cal = Calendar.getInstance(); + cal.set(year, month, date, 14, 15, 16); + cal.set(Calendar.MILLISECOND, 123); + return cal.getTime(); + } + + private String precisePattern(final String pattern, int precision) { + String search = "SSS"; + int foundIndex = pattern.indexOf(search); + final String seconds = pattern.substring(0, foundIndex); + final String remainder = pattern.substring(foundIndex + search.length()); + return seconds + "nnnnnnnnn".substring(0, precision) + remainder; + } + + @Test + public void testFormatDateStringBuilderDefaultPattern() { + assertDatePattern(null, date(2001, 1, 1), "2001-02-01 14:15:16,123"); + } + + @Test + public void testFormatDateStringBuilderIso8601() { + final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS); + final StringBuilder sb = new StringBuilder(); + converter.format(date(2001, 1, 1), sb); + + final String expected = "2001-02-01T14:15:16,123"; + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatDateStringBuilderIso8601BasicWithPeriod() { + assertDatePattern(FixedDateFormat.FixedFormat.ISO8601_BASIC_PERIOD.name(), date(2001, 1, 1), "20010201T141516.123"); + } + + @Test + public void testFormatDateStringBuilderIso8601WithPeriod() { + assertDatePattern(FixedDateFormat.FixedFormat.ISO8601_PERIOD.name(), date(2001, 1, 1), "2001-02-01T14:15:16.123"); + } + + @Test + public void testFormatDateStringBuilderIso8601WithPeriodMicroseconds() { + final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD_MICROS.name(), "Z"}; + final DatePatternConverter converter = DatePatternConverter.newInstance(pattern); + final StringBuilder sb = new StringBuilder(); + MutableInstant instant = new MutableInstant(); + instant.initFromEpochMilli( + 1577225134559L, + // One microsecond + 1000); + converter.format(instant, sb); + + final String expected = "2019-12-24T22:05:34.559001"; + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatDateStringBuilderOriginalPattern() { + assertDatePattern("yyyy/MM/dd HH-mm-ss.SSS", date(2001, 1, 1), "2001/02/01 14-15-16.123"); + } + + @Test + public void testFormatLogEventStringBuilderDefaultPattern() { + final LogEvent event = new MyLogEvent(); + final DatePatternConverter converter = DatePatternConverter.newInstance(null); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + final String expected = "2011-12-30 10:56:35,987"; + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatLogEventStringBuilderIso8601() { + final LogEvent event = new MyLogEvent(); + final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + final String expected = "2011-12-30T10:56:35,987"; + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatAmericanPatterns() { + Date date = date(2011, 2, 11); + assertDatePattern("US_MONTH_DAY_YEAR4_TIME", date, "11/03/2011 14:15:16.123"); + assertDatePattern("US_MONTH_DAY_YEAR2_TIME", date, "11/03/11 14:15:16.123"); + assertDatePattern("dd/MM/yyyy HH:mm:ss.SSS", date, "11/03/2011 14:15:16.123"); + assertDatePattern("dd/MM/yyyy HH:mm:ss.nnnnnn", date, "11/03/2011 14:15:16.123000"); + assertDatePattern("dd/MM/yy HH:mm:ss.SSS", date, "11/03/11 14:15:16.123"); + assertDatePattern("dd/MM/yy HH:mm:ss.nnnnnn", date, "11/03/11 14:15:16.123000"); + } + + private static void assertDatePattern(final String format, final Date date, final String expected) { + DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {format}); + StringBuilder sb = new StringBuilder(); + converter.format(date, sb); + + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatLogEventStringBuilderIso8601TimezoneJST() { + final LogEvent event = new MyLogEvent(); + final String[] optionsWithTimezone = {ISO8601, "JST"}; + final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + // JST=Japan Standard Time: UTC+9:00 + final TimeZone tz = TimeZone.getTimeZone("JST"); + final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern()); + sdf.setTimeZone(tz); + final long adjusted = event.getTimeMillis() + tz.getDSTSavings(); + final String expected = sdf.format(new Date(adjusted)); + // final String expected = "2011-12-30T18:56:35,987"; // in CET (Central Eastern Time: Amsterdam) + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHCMM() { + final LogEvent event = new MyLogEvent(); + final String[] optionsWithTimezone = { ISO8601_OFFSET_DATE_TIME_HHCMM }; + final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern()); + final String format = sdf.format(new Date(event.getTimeMillis())); + final String expected = format.endsWith("Z") ? format.substring(0, format.length() - 1) + "+00:00" : format; + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHMM() { + final LogEvent event = new MyLogEvent(); + final String[] optionsWithTimezone = { ISO8601_OFFSE_DATE_TIME_HHMM }; + final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern()); + final String format = sdf.format(new Date(event.getTimeMillis())); + final String expected = format.endsWith("Z") ? format.substring(0, format.length() - 1) + "+0000" : format; + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatLogEventStringBuilderIso8601TimezoneUTC() { + final LogEvent event = new MyLogEvent(); + final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {"ISO8601", "UTC"}); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + final TimeZone tz = TimeZone.getTimeZone("UTC"); + final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern()); + sdf.setTimeZone(tz); + final long adjusted = event.getTimeMillis() + tz.getDSTSavings(); + final String expected = sdf.format(new Date(adjusted)); + // final String expected = "2011-12-30T09:56:35,987"; + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatLogEventStringBuilderIso8601TimezoneZ() { + final LogEvent event = new MyLogEvent(); + final String[] optionsWithTimezone = { ISO8601, "Z" }; + final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + + final TimeZone tz = TimeZone.getTimeZone("UTC"); + final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern()); + sdf.setTimeZone(tz); + final long adjusted = event.getTimeMillis() + tz.getDSTSavings(); + final String expected = sdf.format(new Date(adjusted)); + // final String expected = "2011-12-30T17:56:35,987"; // in UTC + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatObjectStringBuilderDefaultPattern() { + final DatePatternConverter converter = DatePatternConverter.newInstance(null); + final StringBuilder sb = new StringBuilder(); + converter.format("nondate", sb); + + final String expected = ""; // only process dates + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatStringBuilderObjectArrayDefaultPattern() { + final DatePatternConverter converter = DatePatternConverter.newInstance(null); + final StringBuilder sb = new StringBuilder(); + converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3)); + + final String expected = "2001-02-01 14:15:16,123"; // only process first date + assertEquals(expected, sb.toString()); + } + + @Test + public void testFormatStringBuilderObjectArrayIso8601() { + final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS); + final StringBuilder sb = new StringBuilder(); + converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3)); + + final String expected = "2001-02-01T14:15:16,123"; // only process first date + assertEquals(expected, sb.toString()); + } + + @Test + public void testGetPatternReturnsDefaultForEmptyOptionsArray() { + assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[0]).getPattern()); + } + + @Test + public void testGetPatternReturnsDefaultForInvalidPattern() { + final String[] invalid = {"ABC I am not a valid date pattern"}; + assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern()); + } + + @Test + public void testGetPatternReturnsDefaultForNullOptions() { + assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(null).getPattern()); + } + + @Test + public void testGetPatternReturnsDefaultForSingleNullElementOptionsArray() { + assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[1]).getPattern()); + } + + @Test + public void testGetPatternReturnsDefaultForTwoNullElementsOptionsArray() { + assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[2]).getPattern()); + } + + @Test + public void testGetPatternReturnsNullForUnix() { + final String[] options = {"UNIX"}; + assertNull(DatePatternConverter.newInstance(options).getPattern()); + } + + @Test + public void testGetPatternReturnsNullForUnixMillis() { + final String[] options = {"UNIX_MILLIS"}; + assertNull(DatePatternConverter.newInstance(options).getPattern()); + } + + @Test + public void testInvalidLongPatternIgnoresExcessiveDigits() { + final StringBuilder preciseBuilder = new StringBuilder(); + final StringBuilder milliBuilder = new StringBuilder(); + final LogEvent event = new MyLogEvent(); + + for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { + String pattern = format.getPattern(); + final String search = "SSS"; + final int foundIndex = pattern.indexOf(search); + if (pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*")) { + // ignore patterns that already have precise time formats + // ignore patterns that do not use seconds. + continue; + } + preciseBuilder.setLength(0); + milliBuilder.setLength(0); + + final DatePatternConverter preciseConverter; + final String precisePattern; + if (foundIndex < 0) { + precisePattern = pattern; + preciseConverter = DatePatternConverter.newInstance(new String[] { precisePattern }); + } else { + final String subPattern = pattern.substring(0, foundIndex); + final String remainder = pattern.substring(foundIndex + search.length()); + precisePattern = subPattern + "nnnnnnnnn" + "n" + remainder; // nanos too long + preciseConverter = DatePatternConverter.newInstance(new String[] { precisePattern }); + } + preciseConverter.format(event, preciseBuilder); + + final String[] milliOptions = { pattern }; + DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder); + FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat(); + final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0); + final String tz = timeZoneFormat != null + ? milliBuilder.substring(milliBuilder.length() - timeZoneFormat.getLength(), milliBuilder.length()) + : Strings.EMPTY; + milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis + if (foundIndex >= 0) { + milliBuilder.append("987123456"); + } + final String expected = milliBuilder.append(tz).toString(); + + assertEquals("format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern, + expected, preciseBuilder.toString()); + // System.out.println(preciseOptions[0] + ": " + precise); + } + } + + @Test + public void testNewInstanceAllowsNullParameter() { + DatePatternConverter.newInstance(null); // no errors + } + + // test with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision) + @Test + public void testPredefinedFormatWithAnyValidNanoPrecision() { + final StringBuilder preciseBuilder = new StringBuilder(); + final StringBuilder milliBuilder = new StringBuilder(); + final LogEvent event = new MyLogEvent(); + + for (final String timeZone : new String[] { "PST", null }) { // Pacific Standard Time=UTC-8:00 + for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { + for (int i = 1; i <= 9; i++) { + final String pattern = format.getPattern(); + if (pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*") + || pattern.indexOf("SSS") < 0) { + // ignore patterns that already have precise time formats + // ignore patterns that do not use seconds. + continue; + } + preciseBuilder.setLength(0); + milliBuilder.setLength(0); + + final String precisePattern = precisePattern(pattern, i); + final String[] preciseOptions = { precisePattern, timeZone }; + final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions); + preciseConverter.format(event, preciseBuilder); + + final String[] milliOptions = { pattern, timeZone }; + DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder); + FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat(); + final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0); + final String tz = timeZoneFormat != null + ? milliBuilder.substring(milliBuilder.length() - timeZoneFormat.getLength(), + milliBuilder.length()) + : Strings.EMPTY; + milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis + final String expected = milliBuilder.append("987123456".substring(0, i)).append(tz).toString(); + + assertEquals( + "format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern, + expected, preciseBuilder.toString()); + // System.out.println(preciseOptions[0] + ": " + precise); + } + } + } + } + + @Test + public void testPredefinedFormatWithoutTimezone() { + for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { + final String[] options = {format.name()}; + final DatePatternConverter converter = DatePatternConverter.newInstance(options); + assertEquals(format.getPattern(), converter.getPattern()); + } + } + + @Test + public void testPredefinedFormatWithTimezone() { + for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { + final String[] options = {format.name(), "PST"}; // Pacific Standard Time=UTC-8:00 + final DatePatternConverter converter = DatePatternConverter.newInstance(options); + assertEquals(format.getPattern(), converter.getPattern()); + } + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DisableAnsiTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DisableAnsiTest.java new file mode 100644 index 00000000000..596842f6f8a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DisableAnsiTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j2-console-disableAnsi.xml") +public class DisableAnsiTest { + + private static final String EXPECTED = + "ERROR LoggerTest o.a.l.l.c.p.DisableAnsiTest org.apache.logging.log4j.core.pattern.DisableAnsiTest" + + Strings.LINE_SEPARATOR; + + private Logger logger; + private ListAppender app; + + @BeforeEach + public void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + public void testReplacement() { + logger.error(this.getClass().getName()); + + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue(msgs.get(0).endsWith(EXPECTED), + "Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0)); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviatorTest.java new file mode 100644 index 00000000000..e52d20088fd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviatorTest.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Unit tests for the {@link DynamicWordAbbreviator} class. + */ +class DynamicWordAbbreviatorTest extends Assertions { + + @Test + void testNullAndEmptyInputs() { + DynamicWordAbbreviator abbreviator = DynamicWordAbbreviator.create("1.1*"); + + assertDoesNotThrow(() -> abbreviator.abbreviate("orig", null)); + assertDoesNotThrow(() -> abbreviator.abbreviate(null, new StringBuilder())); + + StringBuilder dest = new StringBuilder(); + abbreviator.abbreviate(null, dest); + assertEquals("", dest.toString()); + + abbreviator.abbreviate("", dest); + assertEquals("", dest.toString()); + } + + @ParameterizedTest(name = "[{index}] \"{0}\"") + @ValueSource(strings = { + "", + " ", + "0.0*", + "0,0*", + "1.2", + "1.2**", + "1.0*" + }) + void testInvalidPatterns(String pattern) { + assertNull(DynamicWordAbbreviator.create(pattern)); + } + + @ParameterizedTest(name = "[{index}] \"{0}\" \"{1}\" \"{2}\"") + @CsvSource(delimiter = '|', value = { + "1.1*|.|.", + "1.1*|\\ |\\ ", + "1.1*|org.novice.o|o.n.o", + "1.1*|org.novice.|o.novice", + "1.1*|org......novice|o.novice", + "1.1*|org. . .novice|o. . .novice", + }) + void testStrangeWords(String pattern, String input, String expected) { + DynamicWordAbbreviator abbreviator = DynamicWordAbbreviator.create(pattern); + StringBuilder actual = new StringBuilder(); + abbreviator.abbreviate(input, actual); + assertEquals(expected, actual.toString()); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverterTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverterTest.java index a6733c17428..e996241f209 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverterTest.java @@ -19,15 +19,14 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class EncodingPatternConverterTest { @Test @@ -42,7 +41,7 @@ public void testReplacement() { final String[] options = new String[]{"%msg"}; final EncodingPatternConverter converter = EncodingPatternConverter .newInstance(ctx.getConfiguration(), options); - assertNotNull("Error creating converter", converter); + assertNotNull(converter, "Error creating converter"); converter.format(event, sb); assertEquals( "Test \\r\\n<div class="test">this</div> & <div class='test'>that</div>", @@ -50,7 +49,7 @@ public void testReplacement() { } @Test - public void testJsonEscaping() throws Exception { + public void testJsonEscaping() { final LogEvent event = Log4jLogEvent.newBuilder() .setLoggerName(getClass().getName()) .setLevel(Level.DEBUG) @@ -62,7 +61,7 @@ public void testJsonEscaping() throws Exception { final String[] options = new String[]{"%msg", "JSON"}; final EncodingPatternConverter converter = EncodingPatternConverter.newInstance(ctx.getConfiguration(), options); - assertNotNull("Error creating converter", converter); + assertNotNull(converter, "Error creating converter"); converter.format(event, sb); assertEquals(expected, sb.toString()); @@ -80,7 +79,7 @@ public void testCrlfEscaping() { final String[] options = new String[]{"%msg", "CRLF"}; final EncodingPatternConverter converter = EncodingPatternConverter .newInstance(ctx.getConfiguration(), options); - assertNotNull("Error creating converter", converter); + assertNotNull(converter, "Error creating converter"); converter.format(event, sb); assertEquals( "Test \\r\\n
this\\r
& \\n
that
", @@ -99,11 +98,21 @@ public void testXmlEscaping() { final String[] options = new String[]{"%msg", "XML"}; final EncodingPatternConverter converter = EncodingPatternConverter .newInstance(ctx.getConfiguration(), options); - assertNotNull("Error creating converter", converter); + assertNotNull(converter, "Error creating converter"); converter.format(event, sb); assertEquals( "Test \r\n<div class="test">this</div> & <div class='test'>that</div>", sb.toString()); } + @Test + public void testHandlesThrowable() { + final Configuration configuration = new DefaultConfiguration(); + assertFalse(EncodingPatternConverter.newInstance(configuration, new String[]{"%msg", "XML"}) + .handlesThrowable()); + assertTrue(EncodingPatternConverter.newInstance(configuration, new String[]{"%xThrowable{full}", "JSON"}) + .handlesThrowable()); + assertTrue(EncodingPatternConverter.newInstance(configuration, new String[]{"%ex", "XML"}) + .handlesThrowable()); + } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverterTest.java index 210b689c40a..eda57d257d7 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverterTest.java @@ -18,9 +18,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class EndOfBatchPatternConverterTest { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverterTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverterTest.java index ff03b726b95..fa4ae878cbe 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverterTest.java @@ -16,19 +16,16 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.Strings; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class EqualsIgnoreCaseReplacementConverterTest { @Test @@ -57,6 +54,7 @@ private void testReplacement(final String tag, final String expectedValue) { final String[] options = new String[] { "aaa[" + tag + "]zzz", "AAA[]ZZZ", expectedValue }; final EqualsIgnoreCaseReplacementConverter converter = EqualsIgnoreCaseReplacementConverter .newInstance(ctx.getConfiguration(), options); + assertNotNull(converter); converter.format(event, sb); assertEquals(expectedValue, sb.toString()); } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverterTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverterTest.java index 2b7984a19d1..35bee32d102 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverterTest.java @@ -22,13 +22,10 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.Strings; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class EqualsReplacementConverterTest { private static final String TEST_MESSAGE = "This is a test"; @@ -68,6 +65,7 @@ private void testReplacement(final String expectedValue, final String[] options) final LoggerContext ctx = LoggerContext.getContext(); final EqualsReplacementConverter converter = EqualsReplacementConverter.newInstance(ctx.getConfiguration(), options); + assertNotNull(converter); converter.format(event, sb); assertEquals(expectedValue, sb.toString()); } @@ -104,6 +102,7 @@ private void testParseSubstitution(final String substitution, final String expec new String[]{"[%marker]", "[]", substitution}); final StringBuilder sb = new StringBuilder(); + assertNotNull(converter); converter.parseSubstitution(event, sb); final String actual = sb.toString(); assertEquals(expected, actual); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverterTest.java new file mode 100644 index 00000000000..391b1fdca8f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverterTest.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.ThrowableFormatOptions; +import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ExtendedThrowablePatternConverterTest { + /** + * TODO: Needs better a better exception? NumberFormatException is NOT helpful. + */ + @Test + public void testBadShortOption() { + final String[] options = { "short.UNKNOWN" }; + assertThrows(NumberFormatException.class, () -> ThrowablePatternConverter.newInstance(null, options)); + } + + @Test + public void testShortStacktrace() { + final String[] options = { "short" }; + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); + final Throwable parent = new IllegalArgumentException("IllegalArgument", new NullPointerException()); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("test exception")) // + .setThrown(parent).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String result = sb.toString(); + String[] lines = result.split(Strings.LINE_SEPARATOR); + assertEquals(2, lines.length, "Invalid number of lines in stacktrace"); + assertFalse(result.contains("NullPointerException"), "Exception should not be visible"); + } + + @Test + public void testLenghLimitedStacktrace() { + final String[] options = { "10" }; + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); + final Throwable parent = new IllegalArgumentException("IllegalArgument", new NullPointerException()); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("test exception")) // + .setThrown(parent).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String result = sb.toString(); + String[] lines = result.split(Strings.LINE_SEPARATOR); + assertEquals(10, lines.length, "Invalid number of lines in stacktrace"); + assertFalse(result.contains("NullPointerException"), "Exception should not be visible"); + } + + @Test + public void testSuffixFromNormalPattern() { + final String suffix = "suffix(%mdc{key})"; + ThreadContext.put("key", "test suffix "); + final String[] options = {suffix}; + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("test exception")) // + .setThrown(parent).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String result = sb.toString(); + assertTrue(result.contains("test suffix"), "No suffix"); + } + + @Test + public void testSuffix() { + final String suffix = "suffix(test suffix)"; + final String[] options = {suffix}; + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("test exception")) // + .setThrown(parent).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String result = sb.toString(); + assertTrue(result.contains("test suffix"), "No suffix"); + } + + @Test + public void testSuffixWillIgnoreThrowablePattern() { + final String suffix = "suffix(%xEx{suffix(inner suffix)})"; + final String[] options = {suffix}; + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("test exception")) // + .setThrown(parent).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String result = sb.toString(); + assertFalse(result.contains("inner suffix"), "Has unexpected suffix"); + } + + @Test + public void testDeserializedLogEventWithThrowableProxyButNoThrowable() { + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, null); + final Throwable originalThrowable = new Exception("something bad happened"); + final ThrowableProxy throwableProxy = new ThrowableProxy(originalThrowable); + final Throwable deserializedThrowable = null; + final Log4jLogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("")) // + .setThrown(deserializedThrowable) // + .setThrownProxy(throwableProxy) // + .setTimeMillis(0).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String result = sb.toString(); + assertTrue(result.contains(originalThrowable.getMessage()), result); + assertTrue(result.contains(originalThrowable.getStackTrace()[0].getMethodName()), result); + } + + @Test + public void testFiltering() { + final String packages = "filters(org.junit, org.apache.maven, sun.reflect, java.lang.reflect)"; + final String[] options = {packages}; + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("test exception")) // + .setThrown(parent).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String result = sb.toString(); + assertTrue(result.contains(" suppressed "), "No suppressed lines"); + } + + @Test + public void testFull() { + final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, null); + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("testLogger") // + .setLoggerFqcn(this.getClass().getName()) // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("test exception")) // + .setThrown(parent).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + parent.printStackTrace(pw); + String result = sb.toString(); + result = result.replaceAll(" ~?\\[.*\\]", Strings.EMPTY); + final String expected = sw.toString(); //.replaceAll("\r", Strings.EMPTY); + assertEquals(expected, result); + } + + @Test + public void testFiltersAndSeparator() { + final ExtendedThrowablePatternConverter exConverter = ExtendedThrowablePatternConverter.newInstance(null, + new String[] { "full", "filters(org.junit,org.eclipse)", "separator(|)" }); + final ThrowableFormatOptions options = exConverter.getOptions(); + final List ignorePackages = options.getIgnorePackages(); + assertNotNull(ignorePackages); + final String ignorePackagesString = ignorePackages.toString(); + assertTrue(ignorePackages.contains("org.junit"), ignorePackagesString); + assertTrue(ignorePackages.contains("org.eclipse"), ignorePackagesString); + assertEquals("|", options.getSeparator()); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowableTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowableTest.java new file mode 100644 index 00000000000..e27ce2d9dbc --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowableTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-throwablefilter.xml") +public class ExtendedThrowableTest { + private ListAppender app; + + @BeforeEach + public void setUp(@Named("List") final ListAppender app) throws Exception { + this.app = app.clear(); + } + + @Test + public void testException(final LoggerContext context) { + final Logger logger = context.getLogger("LoggerTest"); + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + logger.error("Exception", parent); + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue(msgs.get(0).contains("suppressed"), "No suppressed lines"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/FormattingInfoTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/FormattingInfoTest.java new file mode 100644 index 00000000000..3080240eb02 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/FormattingInfoTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Testing FormattingInfo. + */ +public class FormattingInfoTest { + + @Test + public void testFormatTruncateFromBeginning() { + final StringBuilder message = new StringBuilder("Hello, world"); + + final FormattingInfo formattingInfo = new FormattingInfo(false, 0, 5, true); + formattingInfo.format(0, message); + + assertEquals("world", message.toString()); + } + + @Test + public void testFormatTruncateFromEnd() { + final StringBuilder message = new StringBuilder("Hello, world"); + + final FormattingInfo formattingInfo = new FormattingInfo(false, 0, 5, false); + formattingInfo.format(0, message); + + assertEquals("Hello", message.toString()); + } + + @Test + public void testFormatTruncateFromEndGivenFieldStart() { + final StringBuilder message = new StringBuilder("2015-03-09 11:49:28,295; INFO org.apache.logging.log4j.PatternParserTest"); + + final FormattingInfo formattingInfo = new FormattingInfo(false, 0, 5, false); + formattingInfo.format(31, message); + + assertEquals("2015-03-09 11:49:28,295; INFO org.a", message.toString()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java new file mode 100644 index 00000000000..c62889543aa --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java @@ -0,0 +1,145 @@ +package org.apache.logging.log4j.core.pattern;/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the HighlightConverter. + */ +public class HighlightConverterTest { + + @Test + public void testAnsiEmpty() { + final String[] options = {"", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false"}; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + + final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( + new SimpleMessage("message in a bottle")).build(); + final StringBuilder buffer = new StringBuilder(); + converter.format(event, buffer); + assertEquals("", buffer.toString()); + } + + @Test + public void testAnsiNonEmpty() { + final String[] options = {"%-5level: %msg", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false"}; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + + final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( + new SimpleMessage("message in a bottle")).build(); + final StringBuilder buffer = new StringBuilder(); + converter.format(event, buffer); + assertEquals("\u001B[32mINFO : message in a bottle\u001B[m", buffer.toString()); + } + + @Test + public void testLevelNamesBad() { + String colorName = "red"; + final String[] options = { "%-5level: %msg", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + + PatternParser.DISABLE_ANSI + "=false, " + "BAD_LEVEL_A=" + colorName + ", BAD_LEVEL_B=" + colorName }; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + assertNotNull(converter.getLevelStyle(Level.TRACE)); + assertNotNull(converter.getLevelStyle(Level.DEBUG)); + } + + @Test + public void testLevelNamesGood() { + String colorName = "red"; + final String[] options = { "%-5level: %msg", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + + PatternParser.DISABLE_ANSI + "=false, " + "DEBUG=" + colorName + ", TRACE=" + colorName }; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + assertEquals(AnsiEscape.createSequence(colorName), converter.getLevelStyle(Level.TRACE)); + assertEquals(AnsiEscape.createSequence(colorName), converter.getLevelStyle(Level.DEBUG)); + } + + @Test + public void testLevelNamesUnknown() { + String colorName = "blue"; + final String[] options = { "%level", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + + PatternParser.DISABLE_ANSI + "=false, " + "DEBUG=" + colorName + ", CUSTOM1=" + colorName }; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + assertNotNull(converter.getLevelStyle(Level.INFO)); + assertNotNull(converter.getLevelStyle(Level.DEBUG)); + assertNotNull(converter.getLevelStyle(Level.forName("CUSTOM1", 412))); + assertNull(converter.getLevelStyle(Level.forName("CUSTOM2", 512))); + + assertArrayEquals(new byte[]{27, '[', '3', '4', 'm', 'D', 'E', 'B', 'U', 'G', 27, '[', 'm'}, + toFormattedCharSeq(converter, Level.DEBUG).toString().getBytes()); + assertArrayEquals(new byte[]{27, '[', '3', '2', 'm', 'I', 'N', 'F', 'O', 27, '[', 'm'}, + toFormattedCharSeq(converter, Level.INFO).toString().getBytes()); + assertArrayEquals(new byte[]{27, '[', '3', '4', 'm', 'C', 'U', 'S', 'T', 'O', 'M', '1', 27, '[', 'm'}, + toFormattedCharSeq(converter, Level.forName("CUSTOM1", 412)).toString().getBytes()); + assertArrayEquals(new byte[]{'C', 'U', 'S', 'T', 'O', 'M', '2'}, + toFormattedCharSeq(converter, Level.forName("CUSTOM2", 512)).toString().getBytes()); + } + + @Test + public void testLevelNamesNone() { + final String[] options = { "%-5level: %msg", + PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false" }; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + assertNotNull(converter.getLevelStyle(Level.TRACE)); + assertNotNull(converter.getLevelStyle(Level.DEBUG)); + } + + @Test + public void testNoAnsiEmpty() { + final String[] options = {"", PatternParser.DISABLE_ANSI + "=true"}; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + + final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( + new SimpleMessage("message in a bottle")).build(); + final StringBuilder buffer = new StringBuilder(); + converter.format(event, buffer); + assertEquals("", buffer.toString()); + } + + @Test + public void testNoAnsiNonEmpty() { + final String[] options = {"%-5level: %msg", PatternParser.DISABLE_ANSI + "=true"}; + final HighlightConverter converter = HighlightConverter.newInstance(null, options); + assertNotNull(converter); + + final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( + new SimpleMessage("message in a bottle")).build(); + final StringBuilder buffer = new StringBuilder(); + converter.format(event, buffer); + assertEquals("INFO : message in a bottle", buffer.toString()); + } + + + private CharSequence toFormattedCharSeq(final HighlightConverter converter, final Level level) { + final StringBuilder sb= new StringBuilder(); + converter.format(Log4jLogEvent.newBuilder() + .setLevel(level) + .build(), sb); + return sb; + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LevelPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LevelPatternConverterTest.java similarity index 98% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LevelPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LevelPatternConverterTest.java index 9d1d763f6e5..bde85e12cb5 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LevelPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LevelPatternConverterTest.java @@ -21,13 +21,10 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class LevelPatternConverterTest { private void testLevelLength(final int length, final String debug, final String warn) { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverterTest.java index 3b8835b31fe..4634bb9ce62 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverterTest.java @@ -1,43 +1,43 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.pattern; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the LiteralPatternConverter. - */ -public class LiteralPatternConverterTest { - - @Test - public void testConvertBackslashes() { - final String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; - final LiteralPatternConverter converter = new LiteralPatternConverter(null, literal, true); - assertEquals("ABC\tDEF\nGHI\rJKL\'MNO\f \b \\DROPPED:x", converter.getLiteral()); - } - - @Test - public void testDontConvertBackslashes() { - final String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; - final LiteralPatternConverter converter = new LiteralPatternConverter(null, literal, false); - assertEquals(literal, converter.getLiteral()); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.pattern; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the LiteralPatternConverter. + */ +public class LiteralPatternConverterTest { + + @Test + public void testConvertBackslashes() { + final String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + final LiteralPatternConverter converter = new LiteralPatternConverter(null, literal, true); + assertEquals("ABC\tDEF\nGHI\rJKL\'MNO\f \b \\DROPPED:x", converter.getLiteral()); + } + + @Test + public void testDontConvertBackslashes() { + final String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + final LiteralPatternConverter converter = new LiteralPatternConverter(null, literal, false); + assertEquals(literal, converter.getLiteral()); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverterTest.java index 2cb1fafc64d..a87679d5e7e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverterTest.java @@ -18,9 +18,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; public class LoggerFqcnPatternConverterTest { diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java new file mode 100644 index 00000000000..735e0ce847f --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.message.StringMapMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class MapPatternConverterTest { + + @Test + public void testConverter() { + + final StringMapMessage msg = new StringMapMessage(); + msg.put("subject", "I"); + msg.put("verb", "love"); + msg.put("object", "Log4j"); + final MapPatternConverter converter = MapPatternConverter.newInstance(null); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) // + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String str = sb.toString(); + String expected = "subject=I"; + assertTrue(str.contains(expected), "Missing or incorrect subject. Expected " + expected + ", actual " + str); + expected = "verb=love"; + assertTrue(str.contains(expected), "Missing or incorrect verb"); + expected = "object=Log4j"; + assertTrue(str.contains(expected), "Missing or incorrect object"); + + assertEquals("{object=Log4j, subject=I, verb=love}", str); + } + + @Test + public void testConverterWithKey() { + + final StringMapMessage msg = new StringMapMessage(); + msg.put("subject", "I"); + msg.put("verb", "love"); + msg.put("object", "Log4j"); + final MapPatternConverter converter = MapPatternConverter.newInstance(new String[] {"object"}); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) // + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String str = sb.toString(); + final String expected = "Log4j"; + assertEquals(expected, str); + } + + @Test + public void testConverterWithJavaFormat() { + + final StringMapMessage msg = new StringMapMessage(); + msg.put("subject", "I"); + msg.put("verb", "love"); + msg.put("object", "Log4j"); + final MapPatternConverter converter = MapPatternConverter.newInstance(null, MapMessage.MapFormat.JAVA); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg) // + .build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + final String str = sb.toString(); + String expected = "subject=\"I\""; + assertTrue(str.contains(expected), "Missing or incorrect subject. Expected " + expected + ", actual " + str); + expected = "verb=\"love\""; + assertTrue(str.contains(expected), "Missing or incorrect verb"); + expected = "object=\"Log4j\""; + assertTrue(str.contains(expected), "Missing or incorrect object"); + + assertEquals("{object=\"Log4j\", subject=\"I\", verb=\"love\"}", str); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverterTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverterTest.java index 26d68cb6347..da06f55ba97 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverterTest.java @@ -23,9 +23,9 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests {@link MarkerPatternConverter}. diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverterTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverterTest.java index 1f90506909c..88bebcf4b47 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverterTest.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; @@ -25,7 +23,9 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * Tests {@link MarkerSimpleNamePatternConverter}. diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MaxLengthConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MaxLengthConverterTest.java similarity index 87% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MaxLengthConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MaxLengthConverterTest.java index e379915b1db..a41a5feab01 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MaxLengthConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MaxLengthConverterTest.java @@ -22,19 +22,16 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class MaxLengthConverterTest { - private static MaxLengthConverter converter = MaxLengthConverter.newInstance(null, new String[]{"%m", "10"}); + private static final MaxLengthConverter converter = MaxLengthConverter.newInstance(null, new String[]{"%m", "10"}); @Test - public void testUnderMaxLength() throws Exception { + public void testUnderMaxLength() { final Message message = new SimpleMessage("0123456789"); final LogEvent event = Log4jLogEvent.newBuilder() .setLoggerName("MyLogger") @@ -47,7 +44,7 @@ public void testUnderMaxLength() throws Exception { } @Test - public void testOverMaxLength() throws Exception { + public void testOverMaxLength() { final Message message = new SimpleMessage("01234567890123456789"); final LogEvent event = Log4jLogEvent.newBuilder() .setLoggerName("MyLogger") @@ -58,8 +55,9 @@ public void testOverMaxLength() throws Exception { converter.format(event, sb); assertEquals("0123456789", sb.toString()); } + @Test - public void testOverMaxLength21WithEllipsis() throws Exception { + public void testOverMaxLength21WithEllipsis() { final Message message = new SimpleMessage("012345678901234567890123456789"); final LogEvent event = Log4jLogEvent.newBuilder() .setLoggerName("MyLogger") diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java similarity index 90% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java index 00e97f7d4d9..fd87a7f9b03 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MdcPatternConverterTest.java @@ -16,29 +16,22 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.junit.ThreadContextMapRule; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -/** - * - */ -public class MdcPatternConverterTest { +import static org.junit.jupiter.api.Assertions.*; - @Rule - public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule(); +@UsingThreadContextMap +public class MdcPatternConverterTest { - @Before + @BeforeEach public void setup() { ThreadContext.put("subject", "I"); ThreadContext.put("verb", "love"); @@ -58,7 +51,7 @@ public void testConverter() { converter.format(event, sb); final String str = sb.toString(); final String expected = "{object=Log4j, subject=I, verb=love}"; - assertTrue("Incorrect result. Expected " + expected + ", actual " + str, str.equals(expected)); + assertEquals(expected, str, "Incorrect result. Expected " + expected + ", actual " + str); } @Test @@ -75,8 +68,9 @@ public void testConverterWithExistingData() { converter.format(event, sb); final String str = sb.toString(); final String expected = "prefix {object=Log4j, subject=I, verb=love}"; - assertTrue("Incorrect result. Expected " + expected + ", actual " + str, str.equals(expected)); + assertEquals(expected, str, "Incorrect result. Expected " + expected + ", actual " + str); } + @Test public void testConverterFullEmpty() { ThreadContext.clearMap(); @@ -91,7 +85,7 @@ public void testConverterFullEmpty() { converter.format(event, sb); final String str = sb.toString(); final String expected = "{}"; - assertTrue("Incorrect result. Expected " + expected + ", actual " + str, str.equals(expected)); + assertEquals(expected, str, "Incorrect result. Expected " + expected + ", actual " + str); } @Test @@ -109,7 +103,7 @@ public void testConverterFullSingle() { converter.format(event, sb); final String str = sb.toString(); final String expected = "{foo=bar}"; - assertTrue("Incorrect result. Expected " + expected + ", actual " + str, str.equals(expected)); + assertEquals(expected, str, "Incorrect result. Expected " + expected + ", actual " + str); } @Test diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java new file mode 100644 index 00000000000..f90fe1337a2 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-message-ansi.xml") +public class MessageJansiConverterTest { + + private static final String EXPECTED = "\u001B[31;1mWarning!\u001B[m Pants on \u001B[31mfire!\u001B[m" + + Strings.LINE_SEPARATOR; + + private Logger logger; + private ListAppender app; + + @BeforeEach + public void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + public void testReplacement() { + // See org.fusesource.jansi.AnsiRenderer + logger.error("@|red,bold Warning!|@ Pants on @|red fire!|@"); + + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue( + msgs.get(0).endsWith(EXPECTED), "Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0)); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.java index cd85071a5bc..30be131f7df 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessagePatternConverterTest.java @@ -16,9 +16,6 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; @@ -31,15 +28,14 @@ import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.StringMapMessage; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class MessagePatternConverterTest { @Test - public void testPattern() throws Exception { + public void testPattern() { final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, null); Message msg = new SimpleMessage("Hello!"); LogEvent event = Log4jLogEvent.newBuilder() // @@ -48,14 +44,14 @@ public void testPattern() throws Exception { .setMessage(msg).build(); StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "Hello!", sb.toString()); + assertEquals("Hello!", sb.toString(), "Unexpected result"); event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.DEBUG) // .setMessage(null).build(); sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Incorrect length: " + sb, 0, sb.length()); + assertEquals(0, sb.length(), "Incorrect length: " + sb); msg = new SimpleMessage(null); event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // @@ -63,11 +59,11 @@ public void testPattern() throws Exception { .setMessage(msg).build(); sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Incorrect length: " + sb, 4, sb.length()); + assertEquals(4, sb.length(), "Incorrect length: " + sb); } @Test - public void testPatternAndParameterizedMessageDateLookup() throws Exception { + public void testPatternAndParameterizedMessageDateLookup() { final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, null); final Message msg = new ParameterizedMessage("${date:now:buhu}"); final LogEvent event = Log4jLogEvent.newBuilder() // @@ -76,16 +72,11 @@ public void testPatternAndParameterizedMessageDateLookup() throws Exception { .setMessage(msg).build(); final StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "${date:now:buhu}", sb.toString()); + assertEquals("${date:now:buhu}", sb.toString(), "Unexpected result"); } @Test - public void testLookupEnabledByDefault() { - assertFalse("Expected lookups to be enabled", Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS); - } - - @Test - public void testLookup() { + public void testDefaultDisabledLookup() { final Configuration config = new DefaultConfigurationBuilder() .addProperty("foo", "bar") .build(true); @@ -97,7 +88,7 @@ public void testLookup() { .setMessage(msg).build(); final StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "bar", sb.toString()); + assertEquals("${foo}", sb.toString(), "Unexpected result"); } @Test @@ -114,11 +105,28 @@ public void testDisabledLookup() { .setMessage(msg).build(); final StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Expected the raw pattern string without lookup", "${foo}", sb.toString()); + assertEquals("${foo}", sb.toString(), "Expected the raw pattern string without lookup"); + } + + @Test + public void testLookup() { + final Configuration config = new DefaultConfigurationBuilder() + .addProperty("foo", "bar") + .build(true); + final MessagePatternConverter converter = + MessagePatternConverter.newInstance(config, new String[] {"lookups"}); + final Message msg = new ParameterizedMessage("${foo}"); + final LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(msg).build(); + final StringBuilder sb = new StringBuilder(); + converter.format(event, sb); + assertEquals("${foo}", sb.toString(), "Unexpected result"); } @Test - public void testPatternWithConfiguration() throws Exception { + public void testPatternWithConfiguration() { final Configuration config = new DefaultConfiguration(); final MessagePatternConverter converter = MessagePatternConverter.newInstance(config, null); Message msg = new SimpleMessage("Hello!"); @@ -128,14 +136,14 @@ public void testPatternWithConfiguration() throws Exception { .setMessage(msg).build(); StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "Hello!", sb.toString()); + assertEquals("Hello!", sb.toString(), "Unexpected result"); event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // .setLevel(Level.DEBUG) // .setMessage(null).build(); sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Incorrect length: " + sb, 0, sb.length()); + assertEquals(0, sb.length(), "Incorrect length: " + sb); msg = new SimpleMessage(null); event = Log4jLogEvent.newBuilder() // .setLoggerName("MyLogger") // @@ -143,11 +151,11 @@ public void testPatternWithConfiguration() throws Exception { .setMessage(msg).build(); sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Incorrect length: " + sb, 4, sb.length()); + assertEquals(4, sb.length(), "Incorrect length: " + sb); } @Test - public void testMapMessageFormatJson() throws Exception { + public void testMapMessageFormatJson() { final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, new String[]{"json"}); Message msg = new StringMapMessage() .with("key", "val"); @@ -157,11 +165,11 @@ public void testMapMessageFormatJson() throws Exception { .setMessage(msg).build(); StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "{\"key\":\"val\"}", sb.toString()); + assertEquals("{\"key\":\"val\"}", sb.toString(), "Unexpected result"); } @Test - public void testMapMessageFormatXml() throws Exception { + public void testMapMessageFormatXml() { final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, new String[]{"xml"}); Message msg = new StringMapMessage() .with("key", "val"); @@ -171,11 +179,11 @@ public void testMapMessageFormatXml() throws Exception { .setMessage(msg).build(); StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "\n val\n", sb.toString()); + assertEquals("\n val\n", sb.toString(), "Unexpected result"); } @Test - public void testMapMessageFormatDefault() throws Exception { + public void testMapMessageFormatDefault() { final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, null); Message msg = new StringMapMessage() .with("key", "val"); @@ -185,11 +193,11 @@ public void testMapMessageFormatDefault() throws Exception { .setMessage(msg).build(); StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "key=\"val\"", sb.toString()); + assertEquals("key=\"val\"", sb.toString(), "Unexpected result"); } @Test - public void testStructuredDataFormatFull() throws Exception { + public void testStructuredDataFormatFull() { final MessagePatternConverter converter = MessagePatternConverter.newInstance(null, new String[]{"FULL"}); Message msg = new StructuredDataMessage("id", "message", "type") .with("key", "val"); @@ -199,6 +207,6 @@ public void testStructuredDataFormatFull() throws Exception { .setMessage(msg).build(); StringBuilder sb = new StringBuilder(); converter.format(event, sb); - assertEquals("Unexpected result", "type [id key=\"val\"] message", sb.toString()); + assertEquals("type [id key=\"val\"] message", sb.toString(), "Unexpected result"); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java new file mode 100644 index 00000000000..9f3ba88b223 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-message-styled.xml") +public class MessageStyledConverterTest { + + private static final String EXPECTED = "\u001B[31;1mWarning!\u001B[m Pants on \u001B[31;1mfire!\u001B[m" + + Strings.LINE_SEPARATOR; + + private Logger logger; + private ListAppender app; + + @BeforeEach + public void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + public void testReplacement() { + // See org.fusesource.jansi.AnsiRenderer + logger.error("@|WarningStyle Warning!|@ Pants on @|WarningStyle fire!|@"); + + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue( + msgs.get(0).endsWith(EXPECTED), "Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java new file mode 100644 index 00000000000..8e728a6b98e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.junit.Assert.*; + +/** + * + */ +@RunWith(Parameterized.class) +public class NameAbbreviatorTest { + + private final String pattern; + private final String expected; + + public NameAbbreviatorTest(final String pattern, final String expected) { + this.pattern = pattern; + this.expected = expected; + } + + @Parameterized.Parameters(name = "pattern=\"{0}\", expected={1}") + public static Collection data() { + return Arrays.asList( + new Object[][]{ + // { pattern, expected } + { "0", "NameAbbreviatorTest" }, + { "1", "NameAbbreviatorTest" }, + { "2", "pattern.NameAbbreviatorTest" }, + { "3", "core.pattern.NameAbbreviatorTest" }, + { "1.", "o.a.l.l.c.p.NameAbbreviatorTest" }, + { "1.1.~", "o.a.~.~.~.~.NameAbbreviatorTest" }, + { "1.1.1.*", "o.a.l.log4j.core.pattern.NameAbbreviatorTest" }, + { ".", "......NameAbbreviatorTest" }, + { "1.2*", "o.a.l.l.c.pattern.NameAbbreviatorTest" }, + { "1.3*", "o.a.l.l.core.pattern.NameAbbreviatorTest" }, + { "1.8*", "org.apache.logging.log4j.core.pattern.NameAbbreviatorTest" } + } + ); + } + + @Test + public void testAbbreviatorPatterns() throws Exception { + final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(this.pattern); + final StringBuilder destination = new StringBuilder(); + abbreviator.abbreviate(this.getClass().getName(), destination); + final String actual = destination.toString(); + assertEquals(expected, actual); + } + + @Test + public void testAbbreviatorPatternsAppendLongPrefix() throws Exception { + final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(this.pattern); + final String PREFIX = "some random text big enough to be larger than abbreviated string "; + final StringBuilder destination = new StringBuilder(PREFIX); + abbreviator.abbreviate(this.getClass().getName(), destination); + final String actual = destination.toString(); + assertEquals(PREFIX + expected, actual); + } + + @Test + public void testAbbreviatorPatternsAppend() throws Exception { + final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(this.pattern); + final String PREFIX = "some random text"; + final StringBuilder destination = new StringBuilder(PREFIX); + abbreviator.abbreviate(this.getClass().getName(), destination); + final String actual = destination.toString(); + assertEquals(PREFIX + expected, actual); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverterTest.java index f9598d20139..c4a0238b564 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverterTest.java @@ -18,13 +18,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class NanoTimePatternConverterTest { @Test diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java similarity index 90% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java index 92557ec647f..1218bb0ff53 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NdcPatternConverterTest.java @@ -16,22 +16,19 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.junit.ThreadContextStackRule; +import org.apache.logging.log4j.test.junit.UsingThreadContextStack; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class NdcPatternConverterTest { +import static org.junit.jupiter.api.Assertions.*; - @Rule - public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule(); +@UsingThreadContextStack +public class NdcPatternConverterTest { @Test public void testEmpty() { diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NoConsoleNoAnsiTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NoConsoleNoAnsiTest.java new file mode 100644 index 00000000000..0bf979cb590 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/NoConsoleNoAnsiTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j2-console-noConsoleNoAnsi.xml") +public class NoConsoleNoAnsiTest { + + private static final String EXPECTED = + "ERROR LoggerTest o.a.l.l.c.p.NoConsoleNoAnsiTest org.apache.logging.log4j.core.pattern.NoConsoleNoAnsiTest" + + Strings.LINE_SEPARATOR; + + private Logger logger; + private ListAppender app; + + @BeforeEach + public void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + public void testReplacement() { + logger.error(this.getClass().getName()); + + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue(msgs.get(0).endsWith(EXPECTED), + "Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0)); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java index 1c150919a8b..c5d0f78b163 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java @@ -16,10 +16,6 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.util.Calendar; import java.util.List; @@ -33,17 +29,16 @@ import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.impl.ThrowableFormatOptions; -import org.apache.logging.log4j.core.util.DummyNanoClock; -import org.apache.logging.log4j.core.util.SystemNanoClock; +import org.apache.logging.log4j.core.time.SystemNanoClock; +import org.apache.logging.log4j.core.time.internal.DummyNanoClock; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.StringMap; import org.apache.logging.log4j.util.Strings; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class PatternParserTest { static String OUTPUT_FILE = "output/PatternParser"; @@ -69,15 +64,15 @@ public class PatternParserTest { private static final String KEY = "Converter"; private PatternParser parser; - @Before + @BeforeEach public void setup() { parser = new PatternParser(KEY); } private void validateConverter(final List formatter, final int index, final String name) { final PatternConverter pc = formatter.get(index).getConverter(); - assertEquals("Incorrect converter " + pc.getName() + " at index " + index + " expected " + name, - pc.getName(), name); + assertEquals( + pc.getName(), name, "Incorrect converter " + pc.getName() + " at index " + index + " expected " + name); } /** @@ -87,7 +82,7 @@ private void validateConverter(final List formatter, final int public void defaultPattern() { final List formatters = parser.parse(msgPattern); assertNotNull(formatters); - assertTrue(formatters.size() == 2); + assertEquals(formatters.size(), 2); validateConverter(formatters, 0, "Message"); validateConverter(formatters, 1, "Line Sep"); } @@ -118,8 +113,8 @@ public void testCustomPattern() { formatter.format(event, buf); } final String str = buf.toString(); - final String expected = "INFO [PatternParserTest :104 ] - Hello, world" + Strings.LINE_SEPARATOR; - assertTrue("Expected to end with: " + expected + ". Actual: " + str, str.endsWith(expected)); + final String expected = "INFO [PatternParserTest :99 ] - Hello, world" + Strings.LINE_SEPARATOR; + assertTrue(str.endsWith(expected), "Expected to end with: " + expected + ". Actual: " + str); } @Test @@ -140,7 +135,7 @@ public void testPatternTruncateFromBeginning() { } final String str = buf.toString(); final String expected = "INFO rTest Hello, world" + Strings.LINE_SEPARATOR; - assertTrue("Expected to end with: " + expected + ". Actual: " + str, str.endsWith(expected)); + assertTrue(str.endsWith(expected), "Expected to end with: " + expected + ". Actual: " + str); } @Test @@ -161,7 +156,7 @@ public void testPatternTruncateFromEnd() { } final String str = buf.toString(); final String expected = "INFO org.a Hello, world" + Strings.LINE_SEPARATOR; - assertTrue("Expected to end with: " + expected + ". Actual: " + str, str.endsWith(expected)); + assertTrue(str.endsWith(expected), "Expected to end with: " + expected + ". Actual: " + str); } @Test @@ -192,7 +187,7 @@ public void testBadPattern() { // eats all characters until the closing '}' character final String expected = "[2001-02-03 04:05:06,789] - Hello, world"; - assertTrue("Expected to start with: " + expected + ". Actual: " + str, str.startsWith(expected)); + assertTrue(str.startsWith(expected), "Expected to start with: " + expected + ". Actual: " + str); } @Test @@ -226,8 +221,8 @@ private void testNestedPatternHighlight(final Level level, final String expected } final String str = buf.toString(); final String expectedEnd = String.format("] %-5s: Hello, world%s\u001B[m", level, Strings.LINE_SEPARATOR); - assertTrue("Expected to start with: " + expectedStart + ". Actual: " + str, str.startsWith(expectedStart)); - assertTrue("Expected to end with: \"" + expectedEnd + "\". Actual: \"" + str, str.endsWith(expectedEnd)); + assertTrue(str.startsWith(expectedStart), "Expected to start with: " + expectedStart + ". Actual: " + str); + assertTrue(str.endsWith(expectedEnd), "Expected to end with: \"" + expectedEnd + "\". Actual: \"" + str); } @Test @@ -301,8 +296,8 @@ private void testFirstConverter(final String pattern, final Class checkClass) final List formatters = parser.parse(pattern); assertNotNull(formatters); final String msg = formatters.toString(); - assertEquals(msg, 1, formatters.size()); - assertTrue(msg, checkClass.isInstance(formatters.get(0).getConverter())); + assertEquals(1, formatters.size(), msg); + assertTrue(checkClass.isInstance(formatters.get(0).getConverter()), msg); } @Test @@ -367,7 +362,7 @@ public void testClosingBracketButWrongPlace() { assertNotNull(formatters); assertEquals(2, formatters.size()); - validateConverter(formatters, 0, "Literal"); + validateConverter(formatters, 0, "SimpleLiteral"); validateConverter(formatters, 1, "Date"); } @@ -401,9 +396,18 @@ public void testExceptionWithFiltersAndSeparator() { final List ignorePackages = options.getIgnorePackages(); assertNotNull(ignorePackages); final String ignorePackagesString = ignorePackages.toString(); - assertTrue(ignorePackagesString, ignorePackages.contains("org.junit")); - assertTrue(ignorePackagesString, ignorePackages.contains("org.eclipse")); + assertTrue(ignorePackages.contains("org.junit"), ignorePackagesString); + assertTrue(ignorePackages.contains("org.eclipse"), ignorePackagesString); assertEquals("|", options.getSeparator()); } + // LOG4J2-2564: Multiple newInstance methods. + @Test + public void testMapPatternConverter() { + final List formatters = parser.parse("%K"); + assertNotNull(formatters); + assertEquals(formatters.size(), 1); + PatternFormatter formatter = formatters.get(0); + assertTrue(formatter.getConverter() instanceof MapPatternConverter, "Expected a MapPatternConverter"); + } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest2.java similarity index 97% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest2.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest2.java index 45c0a8bde01..cc2f3ea3515 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest2.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest2.java @@ -21,13 +21,10 @@ import java.util.Date; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class PatternParserTest2 { @Test @@ -74,7 +71,7 @@ private void parse(final String pattern, final boolean convert, final StringBuil /** * Format file name. - * + * * @param buf string buffer to which formatted file name is appended, may not be null. * @param objects objects to be evaluated in formatting, may not be null. */ diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java index d15221d97db..f629fddeb68 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java @@ -1,4 +1,4 @@ -package org.apache.logging.log4j.core.pattern;/* +/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -14,19 +14,20 @@ * See the license for the specific language governing permissions and * limitations under the license. */ +package org.apache.logging.log4j.core.pattern; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests that process ID succeeds. */ public class ProcessIdPatternConverterTest { @Test - public void getProcessId() throws Exception { + public void getProcessId() { final String[] defaultValue = {"???"}; final String actual = ProcessIdPatternConverter.newInstance(defaultValue).getProcessId(); assertNotEquals("???", actual); } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest.java index bf09eb47676..b9ffe3fec0f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest.java @@ -23,13 +23,10 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.Strings; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class RegexReplacementConverterTest { @Test @@ -47,6 +44,7 @@ public void testReplacement() { }; final RegexReplacementConverter converter = RegexReplacementConverter.newInstance(ctx.getConfiguration(), options); + assertNotNull(converter); converter.format(event, sb); assertEquals("org/apache/logging/log4j/core/pattern/RegexReplacementConverterTest This is a test" + Strings.LINE_SEPARATOR, sb.toString()); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java new file mode 100644 index 00000000000..f4d9c173eb3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.test.junit.UsingThreadContextMap; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-replace.xml") +@UsingThreadContextMap +public class RegexReplacementTest { + private final ListAppender app; + private final ListAppender app2; + private final org.apache.logging.log4j.Logger logger; + private final org.apache.logging.log4j.Logger logger2; + + private static final String EXPECTED = "/RegexReplacementTest" + Strings.LINE_SEPARATOR; + + public RegexReplacementTest( + final LoggerContext context, @Named("List") final ListAppender app, @Named("List2") final ListAppender app2) { + logger = context.getLogger("LoggerTest"); + logger2 = context.getLogger("ReplacementTest"); + this.app = app.clear(); + this.app2 = app2.clear(); + } + + @Test + public void testReplacement() { + logger.error(this.getClass().getName()); + List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue( + msgs.get(0).endsWith(EXPECTED), "Replacement failed - expected ending " + EXPECTED + " Actual " + msgs.get(0)); + } + + @Test + public void testMessageReplacement() { + ThreadContext.put("MyKey", "Apache"); + logger.error("This is a test for ${ctx:MyKey}"); + List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertEquals("LoggerTest This is a test for ${ctx:MyKey}" + Strings.LINE_SEPARATOR, msgs.get(0)); + } + + @Test + public void testConverter() { + logger2.error(this.getClass().getName()); + final List msgs = app2.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue( + msgs.get(0).endsWith(EXPECTED), "Replacement failed - expected ending " + EXPECTED + " Actual " + msgs.get(0)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverterTest.java new file mode 100644 index 00000000000..ee1ed34ac21 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverterTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Tests that process ID succeeds. + */ +public class RepeatPatternConverterTest { + @Test + public void repeat() { + final String[] args = {"*", "10"}; + final String expected = "**********"; + PatternConverter converter = RepeatPatternConverter.newInstance(null, args); + assertNotNull(converter, "No RepeatPatternConverter returned"); + StringBuilder sb = new StringBuilder(); + converter.format(null, sb); + assertEquals(expected, sb.toString()); + sb.setLength(0); + LogEvent event = Log4jLogEvent.newBuilder() // + .setLoggerName("MyLogger") // + .setLevel(Level.DEBUG) // + .setMessage(new SimpleMessage("Hello")).build(); + converter.format(event, sb); + assertEquals(expected, sb.toString()); + } + + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java index 90d42a6dff7..846a460429e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverterTest.java @@ -16,19 +16,15 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class RootThrowablePatternConverterTest { @Test @@ -47,7 +43,7 @@ public void testSuffix() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertTrue("No suffix", result.contains("test suffix")); + assertTrue(result.contains("test suffix"), "No suffix"); } @Test @@ -67,7 +63,7 @@ public void testSuffixFromNormalPattern() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertTrue("No suffix", result.contains("test suffix")); + assertTrue(result.contains("test suffix"), "No suffix"); } @Test @@ -86,7 +82,7 @@ public void testSuffixWillIgnoreThrowablePattern() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertFalse("Has unexpected suffix", result.contains("inner suffix")); + assertFalse(result.contains("inner suffix"), "Has unexpected suffix"); } @Test @@ -104,9 +100,9 @@ public void testFull1() { converter.format(event, sb); final String result = sb.toString(); // System.out.print(result); - assertTrue("Missing Exception", - result.contains("Wrapped by: java.lang.IllegalArgumentException: IllegalArgument")); - assertTrue("Incorrect start of msg", result.startsWith("java.lang.NullPointerException: null pointer")); + assertTrue( + result.contains("Wrapped by: java.lang.IllegalArgumentException: IllegalArgument"), "Missing Exception"); + assertTrue(result.startsWith("java.lang.NullPointerException: null pointer"), "Incorrect start of msg"); } /** @@ -136,8 +132,8 @@ public void testFull2() { converter.format(event, sb); final String result = sb.toString(); // System.out.print(result); - assertTrue("Missing Exception", - result.contains("Wrapped by: java.lang.IllegalArgumentException: IllegalArgument")); - assertTrue("Incorrect start of msg", result.startsWith("java.lang.NullPointerException: null pointer")); + assertTrue( + result.contains("Wrapped by: java.lang.IllegalArgumentException: IllegalArgument"), "Missing Exception"); + assertTrue(result.startsWith("java.lang.NullPointerException: null pointer"), "Incorrect start of msg"); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowableTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowableTest.java new file mode 100644 index 00000000000..3637796b9e5 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowableTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@LoggerContextSource("log4j-rootthrowablefilter.xml") +public class RootThrowableTest { + @Test + public void testException(final LoggerContext context, @Named("List") final ListAppender app) { + app.clear(); + final Logger logger = context.getLogger("LoggerTest"); + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + logger.error("Exception", parent); + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue(msgs.get(0).contains("suppressed"), "No suppressed lines"); + app.clear(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java new file mode 100644 index 00000000000..a78a444837a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +@LoggerContextSource("SequenceNumberPatternConverterTest.yml") +public class SequenceNumberPatternConverterTest { + + @Test + public void testSequenceIncreases(final LoggerContext context, @Named("List") final ListAppender app) { + app.clear(); + final Logger logger = context.getLogger(getClass().getName()); + logger.info("Message 1"); + logger.info("Message 2"); + logger.info("Message 3"); + logger.info("Message 4"); + logger.info("Message 5"); + + final List messages = app.getMessages(); + assertThat(messages, contains("1", "2", "3", "4", "5")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterZeroPaddedTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterZeroPaddedTest.java new file mode 100644 index 00000000000..9ad778c5c47 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterZeroPaddedTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +@LoggerContextSource("SequenceNumberPatternConverterZeroPaddedTest.yml") +public class SequenceNumberPatternConverterZeroPaddedTest { + + @Test + public void testPaddedSequence(final LoggerContext context, @Named("Padded") final ListAppender app) { + app.clear(); + final Logger logger = context.getLogger(getClass().getName()); + logger.info("Message 1"); + logger.info("Message 2"); + logger.info("Message 3"); + logger.info("Message 4"); + logger.info("Message 5"); + + final List messages = app.getMessages(); + // System.out.println("Written messages "+messages); + assertThat(messages, contains("001", "002", "003", "004", "005")); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.java new file mode 100644 index 00000000000..ba6b4024471 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverterTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.pattern; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SimpleLiteralPatternConverterTest { + + @Test + public void testConvertBackslashes() { + String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, true); + String actual = literal(converter); + assertEquals("ABC\tDEF\nGHI\rJKL\'MNO\f \b \\DROPPED:x", actual); + } + + @Test + public void testDontConvertBackslashes() { + String literal = "ABC\\tDEF\\nGHI\\rJKL\\'MNO\\f \\b \\\\DROPPED:\\x"; + LogEventPatternConverter converter = SimpleLiteralPatternConverter.of(literal, false); + String actual = literal(converter); + assertEquals(literal, actual); + } + + private static String literal(LogEventPatternConverter converter) { + StringBuilder buffer = new StringBuilder(); + converter.format(null, buffer); + return buffer.toString(); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java new file mode 100644 index 00000000000..fefd76f6692 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.util.Strings; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.*; + +@SetSystemProperty(key = Log4jProperties.JANSI_DISABLED, value = "false") +public class StyleConverterTest { + + private static final String EXPECTED = + "\u001B[1;31mERROR\u001B[m \u001B[1;36mLoggerTest\u001B[m o.a.l.l.c.p.StyleConverterTest org.apache.logging.log4j.core.pattern.StyleConverterTest" + + Strings.LINE_SEPARATOR; + + @Test + @LoggerContextSource("log4j-style.xml") + public void testReplacement(final LoggerContext context, @Named("List") final ListAppender app) { + final Logger logger = context.getLogger("LoggerTest"); + logger.error(this.getClass().getName()); + + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertTrue(msgs.get(0).endsWith(EXPECTED), + "Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0)); + } + + @Test + public void testNull() { + assertNull(StyleConverter.newInstance(null, null)); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverterTest.java index b608df81994..883cf71637f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverterTest.java @@ -18,13 +18,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class ThreadIdPatternConverterTest { @Test diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverterTest.java index 1ae94d29b22..2458b6befd4 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverterTest.java @@ -18,13 +18,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class ThreadNamePatternConverterTest { @Test diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverterTest.java similarity index 94% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverterTest.java index 37db5554fdc..82b6cd7836a 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverterTest.java @@ -18,13 +18,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class ThreadPriorityPatternConverterTest { @Test diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java similarity index 89% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java index 4931cf0e769..cc52994ca28 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverterTest.java @@ -16,15 +16,14 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.Strings; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; public class ThrowablePatternConverterTest { @@ -51,10 +50,10 @@ private boolean everyLineEndsWith(final String text, final String suffix) { /** * TODO: Needs better a better exception? NumberFormatException is NOT helpful. */ - @Test(expected = Exception.class) + @Test public void testBadShortOption() { final String[] options = { "short.UNKNOWN" }; - ThrowablePatternConverter.newInstance(null, options); + assertThrows(NumberFormatException.class, () -> ThrowablePatternConverter.newInstance(null, options)); } @Test @@ -81,8 +80,8 @@ public void testFull() { converter.format(event, sb); final String result = sb.toString(); // System.out.print(result); - assertTrue("Incorrect start of msg", result.startsWith("java.lang.IllegalArgumentException: IllegalArgument")); - assertTrue("Missing nested exception", result.contains("java.lang.NullPointerException: null pointer")); + assertTrue(result.startsWith("java.lang.IllegalArgumentException: IllegalArgument"), "Incorrect start of msg"); + assertTrue(result.contains("java.lang.NullPointerException: null pointer"), "Missing nested exception"); } @Test @@ -101,7 +100,7 @@ public void testShortClassName() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertEquals("The class names should be same", packageName + "ThrowablePatternConverterTest", result); + assertEquals(packageName + "ThrowablePatternConverterTest", result, "The class names should be same"); } @Test @@ -119,7 +118,7 @@ public void testShortFileName() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertEquals("The file names should be same", "ThrowablePatternConverterTest.java", result); + assertEquals("ThrowablePatternConverterTest.java", result, "The file names should be same"); } @Test @@ -140,7 +139,7 @@ public void testShortLineNumber() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertTrue("The line numbers should be same", expectedLineNumber == Integer.parseInt(result)); + assertEquals(Integer.parseInt(result), expectedLineNumber, "The line numbers should be same"); } @Test @@ -157,7 +156,7 @@ public void testShortLocalizedMessage() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertEquals("The messages should be same", "I am localized.", result); + assertEquals("I am localized.", result, "The messages should be same"); } @Test @@ -175,7 +174,7 @@ public void testShortMessage() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertEquals("The messages should be same", "IllegalArgument", result); + assertEquals("IllegalArgument", result, "The messages should be same"); } @Test @@ -193,7 +192,7 @@ public void testShortMethodName() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertEquals("The method names should be same", "testShortMethodName", result); + assertEquals("testShortMethodName", result, "The method names should be same"); } @Test @@ -219,7 +218,8 @@ public void testFullWithSuffix() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertTrue("Each line of full stack trace should end with the specified suffix", everyLineEndsWith(result, "test suffix")); + assertTrue(everyLineEndsWith(result, "test suffix"), + "Each line of full stack trace should end with the specified suffix"); } @Test @@ -238,7 +238,7 @@ public void testShortOptionWithSuffix() { final StringBuilder sb = new StringBuilder(); converter.format(event, sb); final String result = sb.toString(); - assertTrue("Each line should end with suffix", everyLineEndsWith(result, "test suffix")); + assertTrue(everyLineEndsWith(result, "test suffix"), "Each line should end with suffix"); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowableTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowableTest.java new file mode 100644 index 00000000000..5d0c1648a5c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/ThrowableTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.core.test.junit.Named; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@code throwable} pattern. + */ +@LoggerContextSource("log4j-throwable.xml") +public class ThrowableTest { + private ListAppender app; + private Logger logger; + + @BeforeEach + public void setUp(final LoggerContext context, @Named("List") final ListAppender app) { + this.logger = context.getLogger("LoggerTest"); + this.app = app.clear(); + } + + @Test + public void testException() { + final Throwable cause = new NullPointerException("null pointer"); + final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); + logger.error("Exception", parent); + final List msgs = app.getMessages(); + assertNotNull(msgs); + assertEquals(1, msgs.size(), "Incorrect number of messages. Should be 1 is " + msgs.size()); + assertFalse(msgs.get(0).contains("suppressed"), "No suppressed lines"); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverterTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverterTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverterTest.java index dd6f1b6cbcd..34a8a137f26 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverterTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverterTest.java @@ -16,19 +16,16 @@ */ package org.apache.logging.log4j.core.pattern; -import static org.junit.Assert.assertEquals; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.util.Strings; -import org.junit.Test; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; -/** - * - */ public class VariablesNotEmptyReplacementConverterTest { @Test @@ -72,6 +69,7 @@ private void testReplacement(final String tag, final String expectedValue) { final String[] options = new String[] { "[" + tag + "]" }; final VariablesNotEmptyReplacementConverter converter = VariablesNotEmptyReplacementConverter .newInstance(ctx.getConfiguration(), options); + assertNotNull(converter); converter.format(event, sb); assertEquals(expectedValue, sb.toString()); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/plugins/convert/CoreTypeConvertersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/plugins/convert/CoreTypeConvertersTest.java new file mode 100644 index 00000000000..1f13210f8b3 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/plugins/convert/CoreTypeConvertersTest.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.plugins.convert; + +import org.apache.logging.log4j.plugins.convert.TypeConverter; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class CoreTypeConvertersTest { + + private final Injector injector = DI.createInjector(); + + @BeforeEach + void setUp() { + injector.init(); + } + + @Test + public void testFindNullConverter() { + assertThrows(NullPointerException.class, () -> injector.getTypeConverter(null)); + } + + @Test + public void testFindBooleanConverter() throws Exception { + final TypeConverter converter = injector.getTypeConverter(Boolean.class); + assertNotNull(converter); + assertTrue((Boolean) converter.convert("TRUE")); + } + + @Test + public void testFindPrimitiveBooleanConverter() throws Exception { + final TypeConverter converter = injector.getTypeConverter(Boolean.TYPE); + assertNotNull(converter); + assertTrue((Boolean) converter.convert("tRUe")); + } + + @SuppressWarnings("unchecked") + @Test + public void testFindCharSequenceConverterUsingStringConverter() throws Exception { + final TypeConverter converter = (TypeConverter) + injector.getTypeConverter(CharSequence.class); + assertNotNull(converter); + final CharSequence expected = "This is a test sequence of characters"; + final CharSequence actual = converter.convert(expected.toString()); + assertEquals(expected, actual); + } + + @SuppressWarnings("unchecked") + @Test + public void testFindNumberConverter() throws Exception { + final TypeConverter numberTypeConverter = (TypeConverter) + injector.getTypeConverter(Number.class); + assertNotNull(numberTypeConverter); + // TODO: is there a specific converter this should return? + } + + public enum Foo { + I, PITY, THE + } + + @SuppressWarnings("unchecked") + @Test + public void testFindEnumConverter() throws Exception { + final TypeConverter fooTypeConverter = (TypeConverter) injector.getTypeConverter(Foo.class); + assertNotNull(fooTypeConverter); + assertEquals(Foo.I, fooTypeConverter.convert("i")); + assertEquals(Foo.PITY, fooTypeConverter.convert("pity")); + assertEquals(Foo.THE, fooTypeConverter.convert("THE")); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.java new file mode 100644 index 00000000000..e854e773491 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/BasicContextSelectorTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.selector; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.test.junit.ContextSelectorType; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +@ContextSelectorType(BasicContextSelector.class) +public final class BasicContextSelectorTest { + + @Test + public void testLogManagerShutdown() { + LoggerContext context = (LoggerContext) LogManager.getContext(); + assertEquals(LifeCycle.State.STARTED, context.getState()); + LogManager.shutdown(); + assertEquals(LifeCycle.State.STOPPED, context.getState()); + } + + @Test + public void testNotDependentOnClassLoader() { + assertFalse(LogManager.getFactory().isClassLoaderDependent()); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java index 5b7e6ad4464..6cf7e5c84dc 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java @@ -19,11 +19,11 @@ import java.lang.reflect.Field; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.util.ReflectionUtil; -import org.junit.Before; -import org.junit.Test; +import org.apache.logging.log4j.util.ReflectionUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class ClassLoaderContextSelectorTest { @@ -31,7 +31,7 @@ public class ClassLoaderContextSelectorTest { private ClassLoader loader1, loader2, loader3; - @Before + @BeforeEach public void setUp() throws Exception { loader1 = new TestClassLoader(); loader2 = new TestClassLoader(); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/TestClassLoader.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/TestClassLoader.java similarity index 86% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/selector/TestClassLoader.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/TestClassLoader.java index 7a54182ef61..49e63c20405 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/TestClassLoader.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/TestClassLoader.java @@ -54,9 +54,11 @@ protected Class findClass(final String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } try { - final URLConnection uc = resource.openConnection(); - final int len = uc.getContentLength(); - final InputStream in = new BufferedInputStream(uc.getInputStream()); + final URLConnection urlConnection = resource.openConnection(); + // A "jar:" URL file remains open after the stream is closed, so do not cache it. + urlConnection.setUseCaches(false); + final int len = urlConnection.getContentLength(); + final InputStream in = new BufferedInputStream(urlConnection.getInputStream()); final byte[] bytecode = new byte[len]; try { IOUtils.readFully(in, bytecode); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/a/Logging1.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/a/Logging1.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/selector/a/Logging1.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/a/Logging1.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/b/Logging2.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/b/Logging2.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/selector/b/Logging2.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/b/Logging2.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/c/Logging3.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/c/Logging3.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/selector/c/Logging3.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/selector/c/Logging3.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/ClockFactoryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/ClockFactoryTest.java new file mode 100644 index 00000000000..525d2b358f6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/ClockFactoryTest.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time; + +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.time.internal.CachedClock; +import org.apache.logging.log4j.core.time.internal.CoarseCachedClock; +import org.apache.logging.log4j.core.time.internal.SystemClock; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisabledOnOs(value = OS.WINDOWS, disabledReason = "https://issues.apache.org/jira/browse/LOG4J2-3515") +public class ClockFactoryTest { + + private final Injector injector = DI.createInjector(); + + @Test + public void testDefaultIsSystemClock() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(SystemClock.class); + } + + @Test + @SetSystemProperty(key = Log4jProperties.CONFIG_CLOCK, value = "SystemClock") + public void testSpecifySystemClockShort() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(SystemClock.class); + } + + @Test + @SetSystemProperty(key = Log4jProperties.CONFIG_CLOCK, value = "org.apache.logging.log4j.core.time.internal.SystemClock") + public void testSpecifySystemClockLong() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(SystemClock.class); + } + + @Test + @SetSystemProperty(key = Log4jProperties.CONFIG_CLOCK, value = "CachedClock") + public void testSpecifyCachedClockShort() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(CachedClock.class); + } + + @Test + @SetSystemProperty(key = Log4jProperties.CONFIG_CLOCK, value = "org.apache.logging.log4j.core.time.internal.CachedClock") + public void testSpecifyCachedClockLong() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(CachedClock.class); + } + + @Test + @SetSystemProperty(key = Log4jProperties.CONFIG_CLOCK, value = "CoarseCachedClock") + public void testSpecifyCoarseCachedClockShort() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(CoarseCachedClock.class); + } + + @Test + @SetSystemProperty(key = Log4jProperties.CONFIG_CLOCK, value = "org.apache.logging.log4j.core.time.internal.CoarseCachedClock") + public void testSpecifyCoarseCachedClockLong() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(CoarseCachedClock.class); + } + + public static class MyClock implements Clock { + @Override + public long currentTimeMillis() { + return 42; + } + } + + @Test + @SetSystemProperty(key = Log4jProperties.CONFIG_CLOCK, value = "org.apache.logging.log4j.core.time.ClockFactoryTest$MyClock") + public void testCustomClock() { + injector.init(); + assertThat(injector.getInstance(Clock.class)).isInstanceOf(MyClock.class); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/InstantFormatterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/InstantFormatterTest.java new file mode 100644 index 00000000000..5c479068323 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/InstantFormatterTest.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time; + +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.logging.log4j.core.time.internal.format.FastDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class InstantFormatterTest { + + @ParameterizedTest + @CsvSource({ + "yyyy-MM-dd'T'HH:mm:ss.SSS" + ",FixedDateFormat", + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + ",FastDateFormat", + "yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'" + ",DateTimeFormatter" + }) + void all_internal_implementations_should_be_used( + final String pattern, + final String className) { + final InstantFormatter formatter = InstantFormatter + .newBuilder() + .setPattern(pattern) + .build(); + Assertions + .assertThat(formatter.getInternalImplementationClass()) + .asString() + .describedAs("pattern=%s", pattern) + .endsWith("." + className); + } + + @Test + void nanoseconds_should_be_formatted() { + final InstantFormatter formatter = InstantFormatter + .newBuilder() + .setPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'") + .setTimeZone(TimeZone.getTimeZone("UTC")) + .build(); + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(0, 123_456_789); + Assertions + .assertThat(formatter.format(instant)) + .isEqualTo("1970-01-01T00:00:00.123456789Z"); + } + + /** + * @see LOG4J2-3614 + */ + @Test + void FastDateFormat_failures_should_be_handled() { + + // Define a pattern causing `FastDateFormat` to fail. + final String pattern = "ss.nnnnnnnnn"; + final TimeZone timeZone = TimeZone.getTimeZone("UTC"); + final Locale locale = Locale.US; + + // Assert that the pattern is not supported by `FixedDateFormat`. + final FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported(pattern, timeZone.getID()); + Assertions.assertThat(fixedDateFormat).isNull(); + + // Assert that the pattern indeed causes a `FastDateFormat` failure. + Assertions + .assertThatThrownBy(() -> FastDateFormat.getInstance(pattern, timeZone, locale)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Illegal pattern component: nnnnnnnnn"); + + // Assert that `InstantFormatter` falls back to `DateTimeFormatter`. + final InstantFormatter formatter = InstantFormatter + .newBuilder() + .setPattern(pattern) + .setTimeZone(timeZone) + .build(); + Assertions + .assertThat(formatter.getInternalImplementationClass()) + .asString() + .endsWith(".DateTimeFormatter"); + + // Assert that formatting works. + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(0, 123_456_789); + Assertions + .assertThat(formatter.format(instant)) + .isEqualTo("00.123456789"); + + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java new file mode 100644 index 00000000000..d9fa068452c --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time; + +import org.apache.logging.log4j.core.time.internal.FixedPreciseClock; +import org.junit.jupiter.api.Test; + +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.*; + +class MutableInstantTest { + + @Test + void testGetEpochSecond() { + MutableInstant instant = new MutableInstant(); + assertEquals(0, instant.getEpochSecond(), "initial"); + + instant.initFromEpochSecond(123, 456); + assertEquals(123, instant.getEpochSecond(), "returns directly set value"); + + instant.initFromEpochMilli(123456, 789012); + assertEquals(123, instant.getEpochSecond(), "returns converted value when initialized from milllis"); + + MutableInstant other = new MutableInstant(); + other.initFromEpochSecond(788, 456); + instant.initFrom(other); + + assertEquals(788, instant.getEpochSecond(), "returns ref value when initialized from instant"); + } + + @Test + void testGetNanoOfSecond() { + MutableInstant instant = new MutableInstant(); + assertEquals(0, instant.getNanoOfSecond(), "initial"); + + instant.initFromEpochSecond(123, 456); + assertEquals(456, instant.getNanoOfSecond(), "returns directly set value"); + + instant.initFromEpochMilli(123456, 789012); + assertEquals(456789012, instant.getNanoOfSecond(), "returns converted value when initialized from milllis"); + + MutableInstant other = new MutableInstant(); + other.initFromEpochSecond(788, 456); + instant.initFrom(other); + + assertEquals(456, instant.getNanoOfSecond(), "returns ref value when initialized from instant"); + } + + @Test + void testGetEpochMillisecond() { + MutableInstant instant = new MutableInstant(); + assertEquals(0, instant.getEpochMillisecond(), "initial"); + + instant.initFromEpochMilli(123, 456); + assertEquals(123, instant.getEpochMillisecond(), "returns directly set value"); + + instant.initFromEpochSecond(123, 456789012); + assertEquals(123456, instant.getEpochMillisecond(), "returns converted value when initialized from seconds"); + + MutableInstant other = new MutableInstant(); + other.initFromEpochMilli(788, 456); + instant.initFrom(other); + + assertEquals(788, instant.getEpochMillisecond(), "returns ref value when initialized from instant"); + } + + @Test + void getGetNanoOfMillisecond() { + MutableInstant instant = new MutableInstant(); + assertEquals(0, instant.getNanoOfMillisecond(), "initial"); + + instant.initFromEpochMilli(123, 456); + assertEquals(456, instant.getNanoOfMillisecond(), "returns directly set value"); + + instant.initFromEpochSecond(123, 456789012); + assertEquals(789012, instant.getNanoOfMillisecond(), "returns converted value when initialized from milllis"); + + MutableInstant other = new MutableInstant(); + other.initFromEpochMilli(788, 456); + instant.initFrom(other); + + assertEquals(456, instant.getNanoOfMillisecond(), "returns ref value when initialized from instant"); + } + + @Test + void testInitFromInstantRejectsNull() { + assertThrows(NullPointerException.class, () -> new MutableInstant().initFrom((Instant) null)); + } + + @Test + void testInitFromInstantCopiesValues() { + MutableInstant other = new MutableInstant(); + other.initFromEpochSecond(788, 456); + assertEquals(788, other.getEpochSecond(), "epochSec"); + assertEquals(456, other.getNanoOfSecond(), "NanosOfSec"); + + MutableInstant instant = new MutableInstant(); + instant.initFrom(other); + + assertEquals(788, instant.getEpochSecond(), "epochSec"); + assertEquals(456, instant.getNanoOfSecond(), "NanoOfSec"); + } + + @Test + void testInitFromEpochMillis() { + MutableInstant instant = new MutableInstant(); + instant.initFromEpochMilli(123456, 789012); + assertEquals(123, instant.getEpochSecond(), "epochSec"); + assertEquals(456789012, instant.getNanoOfSecond(), "NanoOfSec"); + assertEquals(123456, instant.getEpochMillisecond(), "epochMilli"); + assertEquals(789012, instant.getNanoOfMillisecond(), "NanoOfMilli"); + } + + @Test + void testInitFromEpochMillisRejectsNegativeNanoOfMilli() { + MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochMilli(123456, -1)); + } + + @Test + void testInitFromEpochMillisRejectsTooLargeNanoOfMilli() { + MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochMilli(123456, 1000_000)); + } + + @Test + void testInitFromEpochMillisAcceptsTooMaxNanoOfMilli() { + MutableInstant instant = new MutableInstant(); + instant.initFromEpochMilli(123456, 999_999); + assertEquals(999_999, instant.getNanoOfMillisecond(), "NanoOfMilli"); + } + + @Test + void testInitFromEpochSecond() { + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123, 456789012); + assertEquals(123, instant.getEpochSecond(), "epochSec"); + assertEquals(456789012, instant.getNanoOfSecond(), "NanoOfSec"); + assertEquals(123456, instant.getEpochMillisecond(), "epochMilli"); + assertEquals(789012, instant.getNanoOfMillisecond(), "NanoOfMilli"); + } + + @Test + void testInitFromEpochSecondRejectsNegativeNanoOfMilli() { + MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochSecond(123456, -1)); + } + + @Test + void testInitFromEpochSecondRejectsTooLargeNanoOfMilli() { + MutableInstant instant = new MutableInstant(); + assertThrows(IllegalArgumentException.class, () -> instant.initFromEpochSecond(123456, 1000_000_000)); + } + + @Test + void testInitFromEpochSecondAcceptsTooMaxNanoOfMilli() { + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123456, 999_999_999); + assertEquals(999_999_999, instant.getNanoOfSecond(), "NanoOfSec"); + } + + @Test + void testInstantToMillisAndNanos() { + long[] values = new long[2]; + MutableInstant.instantToMillisAndNanos(123456, 999_999_999, values); + assertEquals(123456_999, values[0]); + assertEquals(999_999, values[1]); + } + + @Test + void testInitFromClock() { + MutableInstant instant = new MutableInstant(); + + PreciseClock clock = new FixedPreciseClock(123456, 789012); + instant.initFrom(clock); + + assertEquals(123456, instant.getEpochMillisecond()); + assertEquals(789012, instant.getNanoOfMillisecond()); + assertEquals(123, instant.getEpochSecond()); + assertEquals(456789012, instant.getNanoOfSecond()); + } + + @Test + void testEquals() { + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123, 456789012); + + MutableInstant instant2 = new MutableInstant(); + instant2.initFromEpochMilli(123456, 789012); + + assertEquals(instant, instant2); + } + + @Test + void testHashCode() { + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123, 456789012); + + MutableInstant instant2 = new MutableInstant(); + instant2.initFromEpochMilli(123456, 789012); + + assertEquals(instant.hashCode(), instant2.hashCode()); + + + instant2.initFromEpochMilli(123456, 789013); + assertNotEquals(instant.hashCode(), instant2.hashCode()); + } + + @Test + void testToString() { + MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond(123, 456789012); + assertEquals("MutableInstant[epochSecond=123, nano=456789012]", instant.toString()); + + instant.initFromEpochMilli(123456, 789012); + assertEquals("MutableInstant[epochSecond=123, nano=456789012]", instant.toString()); + } + + @Test + void testTemporalAccessor() { + java.time.Instant javaInstant = java.time.Instant.parse("2020-05-10T22:09:04.123456789Z"); + MutableInstant log4jInstant = new MutableInstant(); + log4jInstant.initFromEpochSecond(javaInstant.getEpochSecond(), javaInstant.getNano()); + DateTimeFormatter formatter = DateTimeFormatter + .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'") + .withZone(ZoneId.of("UTC")); + assertEquals(formatter.format(javaInstant), formatter.format(log4jInstant)); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/SystemNanoClockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/SystemNanoClockTest.java similarity index 83% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/SystemNanoClockTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/SystemNanoClockTest.java index 9adea85a4f5..1676b9729b2 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/SystemNanoClockTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/SystemNanoClockTest.java @@ -1,39 +1,39 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.util; - -import java.util.concurrent.TimeUnit; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the SystemNanoClock. - */ -public class SystemNanoClockTest { - - @Test - public void testReturnsSystemNanoTime() { - final NanoClock clock = new SystemNanoClock(); - final long expected = System.nanoTime(); - final long actual = clock.nanoTime(); - assertTrue("smal difference", actual - expected < TimeUnit.SECONDS.toNanos(1)); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.time; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests the SystemNanoClock. + */ +public class SystemNanoClockTest { + + @Test + public void testReturnsSystemNanoTime() { + final NanoClock clock = new SystemNanoClock(); + final long expected = System.nanoTime(); + final long actual = clock.nanoTime(); + assertTrue(actual - expected < TimeUnit.SECONDS.toNanos(1), "smal difference"); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/DummyNanoClockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/DummyNanoClockTest.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/DummyNanoClockTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/DummyNanoClockTest.java index 8fc16b6ac60..e45a0e42266 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/DummyNanoClockTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/DummyNanoClockTest.java @@ -1,39 +1,39 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.util; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the DummyNanoClock. - */ -public class DummyNanoClockTest { - - @Test - public void testReturnsZeroByDefault() { - assertEquals(0, new DummyNanoClock().nanoTime()); - } - - @Test - public void testReturnsConstructorValue() { - assertEquals(123, new DummyNanoClock(123).nanoTime()); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.time.internal; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the DummyNanoClock. + */ +public class DummyNanoClockTest { + + @Test + public void testReturnsZeroByDefault() { + assertEquals(0, new DummyNanoClock().nanoTime()); + } + + @Test + public void testReturnsConstructorValue() { + assertEquals(123, new DummyNanoClock(123).nanoTime()); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/SystemClockTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/SystemClockTest.java similarity index 81% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/SystemClockTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/SystemClockTest.java index 5421a7df634..9e1913a7134 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/SystemClockTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/SystemClockTest.java @@ -14,11 +14,12 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.core.time.internal; -import org.junit.Test; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; public class SystemClockTest { @@ -29,10 +30,11 @@ public void testLessThan2Millis() { final long diff = sysMillis - millis1; - assertTrue("diff too large: " + diff, diff <= 1); + assertTrue(diff <= 1, "diff too large: " + diff); } @Test + @Tag("sleepy") public void testAfterWaitStillLessThan2Millis() throws Exception { Thread.sleep(100); final long millis1 = new SystemClock().currentTimeMillis(); @@ -40,7 +42,7 @@ public void testAfterWaitStillLessThan2Millis() throws Exception { final long diff = sysMillis - millis1; - assertTrue("diff too large: " + diff, diff <= 1); + assertTrue(diff <= 1, "diff too large: " + diff); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserSDFTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParserSDFTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserSDFTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParserSDFTest.java index 247dad81cd8..ad3450268c5 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserSDFTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParserSDFTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -30,14 +30,16 @@ import java.util.Locale; import java.util.TimeZone; +import org.apache.logging.log4j.core.time.internal.format.DateParser; +import org.apache.logging.log4j.core.time.internal.format.FastDateParser; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; /** - * Compare FastDateParser with SimpleDateFormat - * + * Compare FastDateParser with SimpleDateFormat + * * Copied from Apache Commons Lang 3 on 2016-11-16. */ @RunWith(Parameterized.class) @@ -186,9 +188,9 @@ private void checkParse(final String formattedDate) { fdfE = e.getClass(); } if (valid) { - assertEquals(locale.toString()+" "+formattedDate +"\n",expectedTime, actualTime); + assertEquals(locale.toString()+" "+formattedDate +"\n",expectedTime, actualTime); } else { - assertEquals(locale.toString()+" "+formattedDate + " expected same Exception ", sdfE, fdfE); + assertEquals(locale.toString()+" "+formattedDate + " expected same Exception ", sdfE, fdfE); } } private void checkParsePosition(final String formattedDate) { @@ -205,12 +207,12 @@ private void checkParsePosition(final String formattedDate) { final int length = formattedDate.length(); if (endIndex != length) { // Error in test data - throw new RuntimeException("Test data error: expected SDF parse to consume entire string; endindex " + endIndex + " != " + length); + throw new RuntimeException("Test data error: expected SDF parse to consume entire string; endindex " + endIndex + " != " + length); } } else { final int errorIndex = sdfP.getErrorIndex(); if (errorIndex == -1) { - throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime); + throw new RuntimeException("Test data error: expected SDF parse to fail, but got " + expectedTime); } } @@ -227,6 +229,6 @@ private void checkParsePosition(final String formattedDate) { assertNotEquals("Test data error: expected FDF parse to fail, but got " + actualTime, -1, fdferrorIndex); assertTrue("FDF error index ("+ fdferrorIndex + ") should approxiamate SDF index (" + sdferrorIndex + ")", sdferrorIndex - fdferrorIndex <= 4); - } + } } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParserTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParserTest.java index 546780b1de9..abbd6540d4d 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParserTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParserTest.java @@ -14,10 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import java.io.Serializable; @@ -34,12 +34,16 @@ import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.SerializationUtils; +import org.apache.logging.log4j.core.time.internal.format.DateParser; +import org.apache.logging.log4j.core.time.internal.format.FastDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FastDateParser; +import org.apache.logging.log4j.core.time.internal.format.FormatCache; import org.junit.Assert; import org.junit.Test; /** * Unit tests {@link org.apache.commons.lang3.time.FastDateParser}. - * + * * Copied from Apache Commons Lang 3 on 2016-11-16. */ public class FastDateParserTest { @@ -343,7 +347,7 @@ private void testLocales(final String format, final boolean eraBC) throws Except } } } - + @Test public void testJpLocales() { @@ -421,7 +425,7 @@ public void testSpecialCharacters() throws Exception { testSdfAndFdp("''yyyyMMdd'A''B'HHmmssSSS''", "'20030210A'B153320989'", false); // OK testSdfAndFdp("''''yyyyMMdd'A''B'HHmmssSSS''", "''20030210A'B153320989'", false); // OK testSdfAndFdp("'$\\Ed'" ,"$\\Ed", false); // OK - + // quoted charaters are case sensitive testSdfAndFdp("'QED'", "QED", false); testSdfAndFdp("'QED'", "qed", true); @@ -429,7 +433,7 @@ public void testSpecialCharacters() throws Exception { testSdfAndFdp("yyyy-MM-dd 'QED'", "2003-02-10 QED", false); testSdfAndFdp("yyyy-MM-dd 'QED'", "2003-02-10 qed", true); } - + @Test public void testLANG_832() throws Exception { testSdfAndFdp("'d'd" ,"d3", false); // OK @@ -475,7 +479,7 @@ private void testSdfAndFdp(final String format, final String date, final boolean } } // SDF and FDF should produce equivalent results - assertTrue("Should both or neither throw Exceptions", (f==null)==(s==null)); + assertEquals("Should both or neither throw Exceptions", (f == null), (s == null)); assertEquals("Parsed dates should be equal", dsdf, dfdp); } @@ -567,7 +571,7 @@ public void testEquals() { assertEquals(parser1, parser2); assertEquals(parser1.hashCode(), parser2.hashCode()); - assertFalse(parser1.equals(new Object())); + assertNotEquals(parser1, new Object()); } @Test @@ -593,19 +597,19 @@ public void testTimeZoneMatches() { final DateParser parser= getInstance(yMdHmsSZ, REYKJAVIK); assertEquals(REYKJAVIK, parser.getTimeZone()); } - + @Test public void testLang996() throws ParseException { final Calendar expected = Calendar.getInstance(NEW_YORK, Locale.US); expected.clear(); expected.set(2014, Calendar.MAY, 14); - final DateParser fdp = getInstance("ddMMMyyyy", NEW_YORK, Locale.US); + final DateParser fdp = getInstance("ddMMMyyyy", NEW_YORK, Locale.US); assertEquals(expected.getTime(), fdp.parse("14may2014")); assertEquals(expected.getTime(), fdp.parse("14MAY2014")); assertEquals(expected.getTime(), fdp.parse("14May2014")); } - + @Test(expected = IllegalArgumentException.class) public void test1806Argument() { getInstance("XXXX"); @@ -623,12 +627,12 @@ private static Calendar initializeCalendar(final TimeZone tz) { return cal; } - private static enum Expected1806 { - India(INDIA, "+05", "+0530", "+05:30", true), - Greenwich(GMT, "Z", "Z", "Z", false), + private enum Expected1806 { + India(INDIA, "+05", "+0530", "+05:30", true), + Greenwich(GMT, "Z", "Z", "Z", false), NewYork(NEW_YORK, "-05", "-0500", "-05:00", false); - private Expected1806(final TimeZone zone, final String one, final String two, final String three, final boolean hasHalfHourOffset) { + Expected1806(final TimeZone zone, final String one, final String two, final String three, final boolean hasHalfHourOffset) { this.zone = zone; this.one = one; this.two = two; @@ -642,17 +646,17 @@ private Expected1806(final TimeZone zone, final String one, final String two, fi final String three; final long offset; } - + @Test public void test1806() throws ParseException { final String formatStub = "yyyy-MM-dd'T'HH:mm:ss.SSS"; final String dateStub = "2001-02-04T12:08:56.235"; - + for (final Expected1806 trial : Expected1806.values()) { final Calendar cal = initializeCalendar(trial.zone); final String message = trial.zone.getDisplayName()+";"; - + DateParser parser = getInstance(formatStub+"X", trial.zone); assertEquals(message+trial.one, cal.getTime().getTime(), parser.parse(dateStub+trial.one).getTime()-trial.offset); @@ -666,7 +670,7 @@ public void test1806() throws ParseException { @Test public void testLang1121() throws ParseException { - final TimeZone kst = TimeZone.getTimeZone("KST"); + final TimeZone kst = TimeZone.getTimeZone("Asia/Seoul"); final DateParser fdp = getInstance("yyyyMMdd", kst, Locale.KOREA); try { diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_MoreOrLessTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser_MoreOrLessTest.java similarity index 92% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_MoreOrLessTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser_MoreOrLessTest.java index 0caa8becc80..d2ebba1b659 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_MoreOrLessTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser_MoreOrLessTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.text.ParsePosition; import java.util.Calendar; @@ -22,6 +22,7 @@ import java.util.Locale; import java.util.TimeZone; +import org.apache.logging.log4j.core.time.internal.format.FastDateParser; import org.junit.Assert; import org.junit.Test; @@ -31,15 +32,15 @@ public class FastDateParser_MoreOrLessTest { private static final TimeZone NEW_YORK = TimeZone.getTimeZone("America/New_York"); - + @Test public void testInputHasPrecedingCharacters() { final FastDateParser parser = new FastDateParser("MM/dd", TimeZone.getDefault(), Locale.getDefault()); final ParsePosition parsePosition = new ParsePosition(0); final Date date = parser.parse("A 3/23/61", parsePosition); Assert.assertNull(date); - Assert.assertEquals(0, parsePosition.getIndex()); - Assert.assertEquals(0, parsePosition.getErrorIndex()); + Assert.assertEquals(0, parsePosition.getIndex()); + Assert.assertEquals(0, parsePosition.getErrorIndex()); } @Test @@ -54,7 +55,7 @@ public void testInputHasWhitespace() { calendar.setTime(date); Assert.assertEquals(1961, calendar.get(Calendar.YEAR)); Assert.assertEquals(2, calendar.get(Calendar.MONTH)); - Assert.assertEquals(23, calendar.get(Calendar.DATE)); + Assert.assertEquals(23, calendar.get(Calendar.DATE)); } @Test @@ -67,9 +68,9 @@ public void testInputHasMoreCharacters() { final Calendar calendar = Calendar.getInstance(); calendar.setTime(date); Assert.assertEquals(2, calendar.get(Calendar.MONTH)); - Assert.assertEquals(23, calendar.get(Calendar.DATE)); + Assert.assertEquals(23, calendar.get(Calendar.DATE)); } - + @Test public void testInputHasWrongCharacters() { final FastDateParser parser = new FastDateParser("MM-dd-yyy", TimeZone.getDefault(), Locale.getDefault()); @@ -77,7 +78,7 @@ public void testInputHasWrongCharacters() { Assert.assertNull(parser.parse("03/23/1961", parsePosition)); Assert.assertEquals(2, parsePosition.getErrorIndex()); } - + @Test public void testInputHasLessCharacters() { final FastDateParser parser = new FastDateParser("MM/dd/yyy", TimeZone.getDefault(), Locale.getDefault()); @@ -85,21 +86,21 @@ public void testInputHasLessCharacters() { Assert.assertNull(parser.parse("03/23", parsePosition)); Assert.assertEquals(5, parsePosition.getErrorIndex()); } - + @Test public void testInputHasWrongTimeZone() { final FastDateParser parser = new FastDateParser("mm:ss z", NEW_YORK, Locale.US); - + final String input = "11:23 Pacific Standard Time"; final ParsePosition parsePosition = new ParsePosition(0); Assert.assertNotNull(parser.parse(input, parsePosition)); Assert.assertEquals(input.length(), parsePosition.getIndex()); - + parsePosition.setIndex(0); Assert.assertNull(parser.parse( "11:23 Pacific Standard ", parsePosition)); Assert.assertEquals(6, parsePosition.getErrorIndex()); } - + @Test public void testInputHasWrongDay() { final FastDateParser parser = new FastDateParser("EEEE, MM/dd/yyy", NEW_YORK, Locale.US); @@ -107,7 +108,7 @@ public void testInputHasWrongDay() { final ParsePosition parsePosition = new ParsePosition(0); Assert.assertNotNull(parser.parse(input, parsePosition)); Assert.assertEquals(input.length(), parsePosition.getIndex()); - + parsePosition.setIndex(0); Assert.assertNull(parser.parse( "Thorsday, 03/23/61", parsePosition)); Assert.assertEquals(0, parsePosition.getErrorIndex()); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_TimeZoneStrategyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser_TimeZoneStrategyTest.java similarity index 95% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_TimeZoneStrategyTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser_TimeZoneStrategyTest.java index 64d5ed35bbf..e5bb3f98dbd 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FastDateParser_TimeZoneStrategyTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser_TimeZoneStrategyTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.text.DateFormatSymbols; import java.text.ParseException; @@ -22,6 +22,7 @@ import java.util.Locale; import java.util.TimeZone; +import org.apache.logging.log4j.core.time.internal.format.FastDateParser; import org.junit.Assert; import org.junit.Test; diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormatTest.java similarity index 88% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormatTest.java index e41c4e12643..3fe125d3dfb 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/datetime/FixedDateFormatTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormatTest.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -24,10 +24,11 @@ import java.util.TimeZone; import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat; import org.junit.Test; -import static org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat.*; +import static org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat.*; import static org.junit.Assert.*; /** @@ -35,6 +36,11 @@ */ public class FixedDateFormatTest { + private boolean containsNanos(FixedFormat fixedFormat) { + final String pattern = fixedFormat.getPattern(); + return pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*"); + } + @Test public void testFixedFormat_getDatePatternNullIfNoDateInPattern() { assertNull(FixedFormat.ABSOLUTE.getDatePattern()); @@ -131,7 +137,7 @@ public void testCreateIfSupported_defaultTimeZoneIfOptionsArrayWithSecondNullEle public void testCreateIfSupported_customTimeZoneIfOptionsArrayWithTimeZoneElement() { final FixedDateFormat fmt = FixedDateFormat.createIfSupported(new String[] {DEFAULT.getPattern(), "+08:00", ""}); assertEquals(DEFAULT.getPattern(), fmt.getFormat()); - assertEquals(TimeZone.getTimeZone("+08:00"), fmt.getTimeZone()); + assertEquals(TimeZone.getTimeZone("GMT+08:00"), fmt.getTimeZone()); } @Test(expected = NullPointerException.class) @@ -156,15 +162,16 @@ public void testFormatLong() { final long start = now - TimeUnit.HOURS.toMillis(25); final long end = now + TimeUnit.HOURS.toMillis(25); for (final FixedFormat format : FixedFormat.values()) { - if (format.getPattern().endsWith("n")) { - continue; // cannot compile precise timestamp formats with SimpleDateFormat + String pattern = format.getPattern(); + if (containsNanos(format) || format.getTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat } - final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault()); + final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault()); final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault()); for (long time = start; time < end; time += 12345) { final String actual = customTF.format(time); final String expected = simpleDF.format(new Date(time)); - assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual); + assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual); } } } @@ -175,15 +182,16 @@ public void testFormatLong_goingBackInTime() { final long start = now - TimeUnit.HOURS.toMillis(25); final long end = now + TimeUnit.HOURS.toMillis(25); for (final FixedFormat format : FixedFormat.values()) { - if (format.getPattern().endsWith("n")) { - continue; // cannot compile precise timestamp formats with SimpleDateFormat + String pattern = format.getPattern(); + if (containsNanos(format) || format.getTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat } - final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault()); + final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault()); final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault()); for (long time = end; time > start; time -= 12345) { final String actual = customTF.format(time); final String expected = simpleDF.format(new Date(time)); - assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual); + assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual); } } } @@ -195,16 +203,17 @@ public void testFormatLongCharArrayInt() { final long end = now + TimeUnit.HOURS.toMillis(25); final char[] buffer = new char[128]; for (final FixedFormat format : FixedFormat.values()) { - if (format.getPattern().endsWith("n")) { - continue; // cannot compile precise timestamp formats with SimpleDateFormat + String pattern = format.getPattern(); + if (containsNanos(format) || format.getTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat } - final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault()); + final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault()); final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault()); for (long time = start; time < end; time += 12345) { final int length = customTF.format(time, buffer, 23); final String actual = new String(buffer, 23, length); final String expected = simpleDF.format(new Date(time)); - assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual); + assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual); } } } @@ -216,16 +225,17 @@ public void testFormatLongCharArrayInt_goingBackInTime() { final long end = now + TimeUnit.HOURS.toMillis(25); final char[] buffer = new char[128]; for (final FixedFormat format : FixedFormat.values()) { - if (format.getPattern().endsWith("n")) { - continue; // cannot compile precise timestamp formats with SimpleDateFormat + String pattern = format.getPattern(); + if (containsNanos(format) || format.getTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat } - final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault()); + final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault()); final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault()); for (long time = end; time > start; time -= 12345) { final int length = customTF.format(time, buffer, 23); final String actual = new String(buffer, 23, length); final String expected = simpleDF.format(new Date(time)); - assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual); + assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual); } } } @@ -374,15 +384,16 @@ public void testFormatLong_goingBackInTime_DST() { final long end = now + TimeUnit.HOURS.toMillis(1); for (final FixedFormat format : FixedFormat.values()) { - if (format.getPattern().endsWith("n")) { - continue; // cannot compile precise timestamp formats with SimpleDateFormat + String pattern = format.getPattern(); + if (containsNanos(format) || format.getTimeZoneFormat() != null) { + continue; // cannot compile precise timestamp formats with SimpleDateFormat } - final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault()); + final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault()); final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault()); for (long time = end; time > start; time -= 12345) { final String actual = customTF.format(time); final String expected = simpleDF.format(new Date(time)); - assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual); + assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual); } } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java new file mode 100644 index 00000000000..eea9662a166 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.util.MessageSupplier; +import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.Supplier; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("functional") +@SetSystemProperty(key = LoggingSystemProperties.LOGGER_CONTEXT_FACTORY_CLASS, value = "org.apache.logging.log4j.test.TestLoggerContextFactory") +public class GenerateCustomLoggerTest { + + private static final String TEST_SOURCE = "target/test-classes/org/apache/logging/log4j/core/MyCustomLogger.java"; + + @AfterAll + public static void afterClass() { + File file = new File(TEST_SOURCE); + File parent = file.getParentFile(); + if (file.exists()) { + file.delete(); + } + file = new File(parent, "MyCustomLogger.class"); + if (file.exists()) { + file.delete(); + } + } + + @Test + public void testGenerateSource() throws Exception { + final String CLASSNAME = "org.apache.logging.log4j.core.MyCustomLogger"; + + // generate custom logger source + final List values = Arrays.asList("DEFCON1=350 DEFCON2=450 DEFCON3=550".split(" ")); + final List levels = Generate.LevelInfo.parse(values, Generate.CustomLogger.class); + final String src = Generate.generateSource(CLASSNAME, levels, Generate.Type.CUSTOM); + final File f = new File(TEST_SOURCE); + f.getParentFile().mkdirs(); + try (final FileOutputStream out = new FileOutputStream(f)) { + out.write(src.getBytes(Charset.defaultCharset())); + } + + // set up compiler + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + final DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + final List errors = new ArrayList<>(); + try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { + final Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles( + Collections.singletonList(f)); + String classPath = System.getProperty("jdk.module.path"); + List optionList = new ArrayList<>(); + if (Strings.isNotBlank(classPath)) { + optionList.add("-classpath"); + optionList.add(classPath); + } + // compile generated source + compiler.getTask(null, fileManager, diagnostics, optionList, null, compilationUnits).call(); + + // check we don't have any compilation errors + for (final Diagnostic diagnostic : diagnostics.getDiagnostics()) { + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + errors.add(String.format("Compile error: %s%n", diagnostic.getMessage(Locale.getDefault()))); + } + } + } + assertTrue(errors.isEmpty(), errors.toString()); + + // load the compiled class + final Class cls = Class.forName(CLASSNAME); + + // check that all factory methods exist and are static + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create").getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class).getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Object.class).getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", String.class).getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class, MessageFactory.class).getModifiers())); + assertTrue(Modifier + .isStatic(cls.getDeclaredMethod("create", Object.class, MessageFactory.class).getModifiers())); + assertTrue(Modifier + .isStatic(cls.getDeclaredMethod("create", String.class, MessageFactory.class).getModifiers())); + + // check that all log methods exist + final String[] logMethods = { "defcon1", "defcon2", "defcon3" }; + for (final String name : logMethods) { + assertDoesNotThrow(() -> { + cls.getDeclaredMethod(name, Marker.class, Message.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, Object.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, String.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, Message.class); + cls.getDeclaredMethod(name, Marker.class, Object.class); + cls.getDeclaredMethod(name, Marker.class, String.class); + cls.getDeclaredMethod(name, Message.class); + cls.getDeclaredMethod(name, Object.class); + cls.getDeclaredMethod(name, String.class); + cls.getDeclaredMethod(name, Message.class, Throwable.class); + cls.getDeclaredMethod(name, Object.class, Throwable.class); + cls.getDeclaredMethod(name, String.class, Throwable.class); + cls.getDeclaredMethod(name, String.class, Object[].class); + cls.getDeclaredMethod(name, Marker.class, String.class, Object[].class); + + // 2.4 lambda support + cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class); + cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, String.class, Supplier[].class); + cls.getDeclaredMethod(name, Marker.class, Supplier.class); + cls.getDeclaredMethod(name, Marker.class, Supplier.class, Throwable.class); + cls.getDeclaredMethod(name, MessageSupplier.class); + cls.getDeclaredMethod(name, MessageSupplier.class, Throwable.class); + cls.getDeclaredMethod(name, String.class, Supplier[].class); + cls.getDeclaredMethod(name, Supplier.class); + cls.getDeclaredMethod(name, Supplier.class, Throwable.class); + }); + } + + // now see if it actually works... + final Method create = cls.getDeclaredMethod("create", String.class); + final Object customLogger = create.invoke(null, "X.Y.Z"); + int n = 0; + for (final String name : logMethods) { + final Method method = cls.getDeclaredMethod(name, String.class); + method.invoke(customLogger, "This is message " + n++); + } + + final TestLogger underlying = (TestLogger) LogManager.getLogger("X.Y.Z"); + final List lines = underlying.getEntries(); + for (int i = 0; i < lines.size(); i++) { + assertEquals(" " + levels.get(i).name + " This is message " + i, lines.get(i)); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java new file mode 100644 index 00000000000..88df837dc06 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.test.TestLogger; +import org.apache.logging.log4j.util.MessageSupplier; +import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.Supplier; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("functional") +@SetSystemProperty(key = LoggingSystemProperties.LOGGER_CONTEXT_FACTORY_CLASS, value = "org.apache.logging.log4j.test.TestLoggerContextFactory") +public class GenerateExtendedLoggerTest { + + private static final String TEST_SOURCE = "target/test-classes/org/apache/logging/log4j/core/MyExtendedLogger.java"; + + @AfterAll + public static void afterClass() { + File file = new File(TEST_SOURCE); + File parent = file.getParentFile(); + if (file.exists()) { + file.delete(); + } + file = new File(parent, "MyExtendedLogger.class"); + if (file.exists()) { + file.delete(); + } + } + + @Test + public void testGenerateSource() throws Exception { + final String CLASSNAME = "org.apache.logging.log4j.core.MyExtendedLogger"; + + // generate custom logger source + final List values = Arrays.asList("DIAG=350 NOTICE=450 VERBOSE=550".split(" ")); + final List levels = Generate.LevelInfo.parse(values, Generate.ExtendedLogger.class); + final String src = Generate.generateSource(CLASSNAME, levels, Generate.Type.EXTEND); + final File f = new File(TEST_SOURCE); + f.getParentFile().mkdirs(); + try (final FileOutputStream out = new FileOutputStream(f)) { + out.write(src.getBytes(Charset.defaultCharset())); + } + + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + final DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + final List errors = new ArrayList<>(); + try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { + final Iterable compilationUnits = fileManager + .getJavaFileObjectsFromFiles(Collections.singletonList(f)); + String classPath = System.getProperty("jdk.module.path"); + List optionList = new ArrayList<>(); + if (Strings.isNotBlank(classPath)) { + optionList.add("-classpath"); + optionList.add(classPath); + } + // compile generated source + compiler.getTask(null, fileManager, diagnostics, optionList, null, compilationUnits).call(); + + // check we don't have any compilation errors + for (final Diagnostic diagnostic : diagnostics.getDiagnostics()) { + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + errors.add(String.format("Compile error: %s%n", diagnostic.getMessage(Locale.getDefault()))); + } + } + } + assertTrue(errors.isEmpty(), errors.toString()); + + // load the compiled class + final Class cls = Class.forName(CLASSNAME); + + // check that all factory methods exist and are static + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create").getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class).getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Object.class).getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", String.class).getModifiers())); + assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class, MessageFactory.class).getModifiers())); + assertTrue(Modifier + .isStatic(cls.getDeclaredMethod("create", Object.class, MessageFactory.class).getModifiers())); + assertTrue(Modifier + .isStatic(cls.getDeclaredMethod("create", String.class, MessageFactory.class).getModifiers())); + + // check that the extended log methods exist + final String[] extendedMethods = { "diag", "notice", "verbose" }; + for (final String name : extendedMethods) { + assertDoesNotThrow(() -> { + cls.getDeclaredMethod(name, Marker.class, Message.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, Object.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, String.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, Message.class); + cls.getDeclaredMethod(name, Marker.class, Object.class); + cls.getDeclaredMethod(name, Marker.class, String.class); + cls.getDeclaredMethod(name, Message.class); + cls.getDeclaredMethod(name, Object.class); + cls.getDeclaredMethod(name, String.class); + cls.getDeclaredMethod(name, Message.class, Throwable.class); + cls.getDeclaredMethod(name, Object.class, Throwable.class); + cls.getDeclaredMethod(name, String.class, Throwable.class); + cls.getDeclaredMethod(name, String.class, Object[].class); + cls.getDeclaredMethod(name, Marker.class, String.class, Object[].class); + + // 2.4 lambda support + cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class); + cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class, Throwable.class); + cls.getDeclaredMethod(name, Marker.class, String.class, Supplier[].class); + cls.getDeclaredMethod(name, Marker.class, Supplier.class); + cls.getDeclaredMethod(name, Marker.class, Supplier.class, Throwable.class); + cls.getDeclaredMethod(name, MessageSupplier.class); + cls.getDeclaredMethod(name, MessageSupplier.class, Throwable.class); + cls.getDeclaredMethod(name, String.class, Supplier[].class); + cls.getDeclaredMethod(name, Supplier.class); + cls.getDeclaredMethod(name, Supplier.class, Throwable.class); + }); + } + + // now see if it actually works... + final Method create = cls.getDeclaredMethod("create", String.class); + final Object extendedLogger = create.invoke(null, "X.Y.Z"); + int n = 0; + for (final String name : extendedMethods) { + final Method method = cls.getDeclaredMethod(name, String.class); + method.invoke(extendedLogger, "This is message " + n++); + } + + // This logger extends o.a.l.log4j.spi.ExtendedLogger, + // so all the standard logging methods can be used as well + final ExtendedLogger logger = (ExtendedLogger) extendedLogger; + logger.trace("trace message"); + logger.debug("debug message"); + logger.info("info message"); + logger.warn("warn message"); + logger.error("error message"); + logger.fatal("fatal message"); + + final TestLogger underlying = (TestLogger) LogManager.getLogger("X.Y.Z"); + final List lines = underlying.getEntries(); + for (int i = 0; i < lines.size() - 6; i++) { + assertEquals(" " + levels.get(i).name + " This is message " + i, lines.get(i)); + } + + // test that the standard logging methods still work + int i = lines.size() - 6; + assertEquals(" TRACE trace message", lines.get(i++)); + assertEquals(" DEBUG debug message", lines.get(i++)); + assertEquals(" INFO info message", lines.get(i++)); + assertEquals(" WARN warn message", lines.get(i++)); + assertEquals(" ERROR error message", lines.get(i++)); + assertEquals(" FATAL fatal message", lines.get(i++)); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java new file mode 100644 index 00000000000..56b41f59cf7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.impl.ThreadContextDataInjector; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@Tag("functional") +public class ContextDataProviderTest { + + private static Logger logger; + private static ListAppender appender; + + @BeforeAll + public static void beforeClass() { + ThreadContextDataInjector.contextDataProviders.add(new TestContextDataProvider()); + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "log4j-contextData.xml"); + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); + logger = loggerContext.getLogger(ContextDataProviderTest.class.getName()); + appender = loggerContext.getConfiguration().getAppender("List"); + assertNotNull(appender, "No List appender"); + } + + @Test + public void testContextProvider() { + ThreadContext.put("loginId", "jdoe"); + logger.debug("This is a test"); + List messages = appender.getMessages(); + assertEquals(1, messages.size(), "Incorrect number of messages"); + assertTrue(messages.get(0).contains("testKey=testValue"), "Context data missing"); + } + + private static class TestContextDataProvider implements ContextDataProvider { + + @Override + public Map supplyContextData() { + Map contextData = new HashMap<>(); + contextData.put("testKey", "testValue"); + return contextData; + } + + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.java new file mode 100644 index 00000000000..a151de6b430 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import org.junit.jupiter.api.Test; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Class Description goes here. + * Created by rgoers on 11/15/15 + */ +public class CronExpressionTest { + + @Test + public void testDayOfMonth() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 * * ?"); + final Date date = new GregorianCalendar(2015, Calendar.DECEMBER, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, Calendar.DECEMBER, 2, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + public void testDayOfWeek() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 ? * Fri"); + final Date date = new GregorianCalendar(2015, Calendar.DECEMBER, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, Calendar.DECEMBER, 4, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + public void testNextMonth() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 1 * ?"); + final Date date = new GregorianCalendar(2015, Calendar.DECEMBER, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2016, Calendar.JANUARY, 1, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + public void testLastDayOfMonth() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 L * ?"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, Calendar.NOVEMBER, 30, 7, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + public void testNextDay() throws Exception { + final CronExpression parser = new CronExpression("0 0 0 * * ?"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getNextValidTimeAfter(date); + final Date expected = new GregorianCalendar(2015, Calendar.NOVEMBER, 3, 0, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + public void testPrevFireTime1() throws Exception { + final CronExpression parser = new CronExpression("0 */15,12 7-11,13-17 L * ?"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, Calendar.OCTOBER, 31, 17, 45, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + @Test + public void testPrevFireTime2() throws Exception { + final CronExpression parser = new CronExpression("0 0/5 14,18 * * ?"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, Calendar.NOVEMBER, 1, 18, 55, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * 35,45, and 55 minutes past the hour every hour. + */ + @Test + public void testPrevFireTime3() throws Exception { + final CronExpression parser = new CronExpression("0 35/10 * * * ?"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, Calendar.NOVEMBER, 1, 23, 55, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * + * 10:15 every day. + */ + @Test + public void testPrevFireTimeTenFifteen() throws Exception { + final CronExpression parser = new CronExpression("0 15 10 * * ? *"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, Calendar.NOVEMBER, 1, 10, 15, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * Every day from 2 pm to 2:59 pm + */ + @Test + public void testPrevFireTimeTwoPM() throws Exception { + final CronExpression parser = new CronExpression("0 * 14 * * ?"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, Calendar.NOVEMBER, 1, 14, 59, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * 2:10pm and at 2:44pm every Wednesday in the month of March. + */ + @Test + public void testPrevFireTimeMarch() throws Exception { + final CronExpression parser = new CronExpression("0 10,44 14 ? 3 WED"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, Calendar.MARCH, 25, 14, 44, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /** + * Fire at 10:15am on the third Friday of every month. + */ + @Test + public void testPrevFireTimeThirdFriday() throws Exception { + final CronExpression parser = new CronExpression("0 15 10 ? * 6#3"); + final Date date = new GregorianCalendar(2015, Calendar.NOVEMBER, 2).getTime(); + final Date fireDate = parser.getPrevFireTime(date); + final Date expected = new GregorianCalendar(2015, Calendar.OCTOBER, 16, 10, 15, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + + /* + * Input time with milliseconds will correctly return the next + * scheduled time. + */ + @Test + public void testTimeBeforeMilliseconds() throws Exception { + final CronExpression parser = new CronExpression("0 0 0 * * ?"); + final GregorianCalendar cal = new GregorianCalendar(2015, Calendar.NOVEMBER, 2, 0, 0, 0); + cal.set(Calendar.MILLISECOND, 100); + final Date date = cal.getTime(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + System.err.println(sdf.format(date)); + final Date fireDate = parser.getTimeBefore(date); + System.err.println(sdf.format(fireDate)); + final Date expected = new GregorianCalendar(2015, Calendar.NOVEMBER, 1, 0, 0, 0).getTime(); + assertEquals(expected, fireDate, "Dates not equal."); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/CyclicBufferTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CyclicBufferTest.java similarity index 77% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/CyclicBufferTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CyclicBufferTest.java index 91f183ac99f..2c48cb92433 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/CyclicBufferTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/CyclicBufferTest.java @@ -16,11 +16,9 @@ */ package org.apache.logging.log4j.core.util; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.jupiter.api.Test; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; public class CyclicBufferTest { @@ -32,7 +30,7 @@ public void testSize0() { buffer.add(1); assertTrue(buffer.isEmpty()); Integer[] items = buffer.removeAll(); - assertTrue("Incorrect number of items", items.length == 0); + assertEquals(0, items.length, "Incorrect number of items"); assertTrue(buffer.isEmpty()); buffer.add(1); @@ -40,7 +38,7 @@ public void testSize0() { buffer.add(3); buffer.add(4); items = buffer.removeAll(); - assertTrue("Incorrect number of items", items.length == 0); + assertEquals(0, items.length, "Incorrect number of items"); assertTrue(buffer.isEmpty()); } @@ -52,7 +50,7 @@ public void testSize1() { buffer.add(1); assertFalse(buffer.isEmpty()); Integer[] items = buffer.removeAll(); - assertTrue("Incorrect number of items", items.length == 1); + assertEquals(1, items.length, "Incorrect number of items"); assertTrue(buffer.isEmpty()); buffer.add(1); @@ -60,7 +58,7 @@ public void testSize1() { buffer.add(3); buffer.add(4); items = buffer.removeAll(); - assertTrue("Incorrect number of items", items.length == 1); + assertEquals(1, items.length, "Incorrect number of items"); assertArrayEquals(new Integer[] { 4 }, items); assertTrue(buffer.isEmpty()); } @@ -73,7 +71,7 @@ public void testSize3() { buffer.add(1); assertFalse(buffer.isEmpty()); Integer[] items = buffer.removeAll(); - assertTrue("Incorrect number of items", items.length == 1); + assertEquals(1, items.length, "Incorrect number of items"); assertTrue(buffer.isEmpty()); buffer.add(1); @@ -81,14 +79,14 @@ public void testSize3() { buffer.add(3); buffer.add(4); items = buffer.removeAll(); - assertTrue("Incorrect number of items", items.length == 3); + assertEquals(3, items.length, "Incorrect number of items"); assertArrayEquals(new Integer[] { 2, 3, 4 }, items); assertTrue(buffer.isEmpty()); } - @Test(expected = IllegalArgumentException.class) + @Test public void testSizeNegative() { - final CyclicBuffer buffer = new CyclicBuffer<>(Integer.class, -1); + assertThrows(IllegalArgumentException.class, () -> new CyclicBuffer<>(Integer.class, -1)); } } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.java new file mode 100644 index 00000000000..fd1539c132b --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.util; + +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the FileUtils class. + */ +public class FileUtilsTest { + + private static final String LOG4J_CONFIG_WITH_PLUS = "log4j+config+with+plus+characters.xml"; + + @Test + public void testFileFromUriWithPlusCharactersInName() throws Exception { + final String config = "target/test-classes/log4j+config+with+plus+characters.xml"; + final URI uri = new URI(config); + final File file = FileUtils.fileFromUri(uri); + assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName()); + assertTrue(file.exists(), "file exists"); + } + + @Test + public void testAbsoluteFileFromUriWithPlusCharactersInName() throws Exception { + final String config = "target/test-classes/log4j+config+with+plus+characters.xml"; + final URI uri = new File(config).toURI(); + final File file = FileUtils.fileFromUri(uri); + assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName()); + assertTrue(file.exists(), "file exists"); + } + + @Test + public void testAbsoluteFileFromUriWithSpacesInName() throws Exception { + final String config = "target/test-classes/s p a c e s/log4j+config+with+plus+characters.xml"; + final URI uri = new File(config).toURI(); + final File file = FileUtils.fileFromUri(uri); + assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName()); + assertTrue(file.exists(), "file exists"); + } + + @Test + public void testAbsoluteFileFromJBossVFSUri() throws Exception { + final String config = "target/test-classes/log4j+config+with+plus+characters.xml"; + final String uriStr = new File(config).toURI().toString().replaceAll("^file:", "vfsfile:"); + assertTrue(uriStr.startsWith("vfsfile:")); + final URI uri = URI.create(uriStr); + final File file = FileUtils.fileFromUri(uri); + assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName()); + assertTrue(file.exists(), "file exists"); + } + + @Test + public void testFileFromUriWithSpacesAndPlusCharactersInName() throws Exception { + final String config = "target/test-classes/s%20p%20a%20c%20e%20s/log4j%2Bconfig%2Bwith%2Bplus%2Bcharacters.xml"; + final URI uri = new URI(config); + final File file = FileUtils.fileFromUri(uri); + assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName()); + assertTrue(file.exists(), "file exists"); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/InitTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/InitTest.java new file mode 100644 index 00000000000..9da7cd99717 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/InitTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.util.Timer; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test initialization. + */ +@Disabled +public class InitTest { + + @Test + public void initTest() { + Timer timer = new Timer("Log4j Initialization"); + timer.start(); + Logger logger = LogManager.getLogger(); + timer.stop(); + long elapsed = timer.getElapsedNanoTime(); + System.out.println(timer.toString()); + assertTrue(elapsed < 1000000000, "Initialization time exceeded threshold; elapsed " + elapsed); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/IntegersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/IntegersTest.java similarity index 93% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/IntegersTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/IntegersTest.java index 66d50020a10..63756df5443 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/IntegersTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/IntegersTest.java @@ -17,9 +17,9 @@ package org.apache.logging.log4j.core.util; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the Integers class. diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonReaderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonReaderTest.java new file mode 100644 index 00000000000..5da282052db --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonReaderTest.java @@ -0,0 +1,429 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +class JsonReaderTest { + + @Test + void test_null() { + Assertions + .assertThatThrownBy(() -> JsonReader.read(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("json"); + } + + @Test + void test_valid_null() { + test("null", null); + test("[null, null]", Arrays.asList(null, null)); + } + + @Test + void test_invalid_null() { + for (final String json : new String[]{"nuL", "nulL", "nul1"}) { + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("was expecting keyword 'null' at index"); + + } + } + + @Test + void test_valid_boolean() { + test("true", true); + test("false", false); + test("[true, false]", Arrays.asList(true, false)); + } + + @Test + void test_invalid_boolean() { + for (final String json : new String[]{"tru", "truE", "fals", "falsE"}) { + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageMatching("^was expecting keyword '(true|false)' at index [0-9]+: .*$"); + + } + } + + @Test + void test_valid_string() { + test("\"\"", ""); + test("\" \"", " "); + test("\" a\"", " a"); + test("\"a \"", "a "); + test("\"abc\"", "abc"); + test("\"abc\\\"\"", "abc\""); + test("\"\\b\\f\\n\\r\\t\"", "\b\f\n\r\t"); + } + + @Test + void test_invalid_string_start() { + final String json = "abc\""; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("invalid character at index 0: a"); + } + + @Test + void test_invalid_string_end() { + for (final String json : new String[]{"", " ", "\r", "\t", "\"abc"}) { + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("premature end of input"); + } + } + + @Test + void test_invalid_string_escape() { + for (final String json : new String[]{"\"\\k\"", "\"\\d\""}) { + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith( + "was expecting an escape character at index 2: "); + } + } + + @Test + void test_invalid_string_concat() { + final String json = "\"foo\"\"bar\""; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was not expecting input at index 5: \""); + } + + @Test + void test_valid_unicode_string() { + final String json = "\"a\\u00eF4bc\""; + Assertions + .assertThat(JsonReader.read(json)) + .as("json=%s", json) + .isEqualTo("a\u00ef4bc"); + } + + @Test + void test_invalid_unicode() { + Assertions + .assertThatThrownBy(() -> JsonReader.read("\"\\u000x\"")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was expecting a unicode character at index 6: x"); + } + + @Test + void test_valid_integers() { + for (final String integer : new String[]{ + "0", + "1", + "" + Long.MAX_VALUE + "" + Long.MAX_VALUE}) { + for (final String signedInteger : new String[]{integer, '-' + integer}) { + final Object expectedToken = + signedInteger.length() < 3 + ? Integer.parseInt(signedInteger) + : new BigInteger(signedInteger); + test(signedInteger, expectedToken); + } + } + } + + @Test + void test_invalid_integers() { + for (final String integer : new String[]{ + "0-", + "1a"}) { + for (final String signedInteger : new String[]{integer, '-' + integer}) { + Assertions + .assertThatThrownBy(() -> JsonReader.read(signedInteger)) + .as("signedInteger=%s", signedInteger) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("was not expecting input at index"); + } + } + } + + @Test + void test_valid_decimals() { + for (final String decimal : new String[]{ + "0.0", + "1.0", + "1.2", + "1e2", + "1e-2", + "1.2e3", + "1.2e-3"}) { + for (final String signedDecimal : new String[]{decimal, '-' + decimal}) { + test(signedDecimal, new BigDecimal(signedDecimal)); + } + } + } + + @Test + void test_invalid_decimals() { + for (final String decimal : new String[]{ + "0.", + ".1", + "1e", + "1e-", + "1.2e", + "1.2e-"}) { + for (final String signedDecimal : new String[]{decimal, '-' + decimal}) { + Assertions + .assertThatThrownBy(() -> JsonReader.read(signedDecimal)) + .as("signedDecimal=%s", signedDecimal) + .isInstanceOf(IllegalArgumentException.class); + } + } + } + + @Test + void test_valid_arrays() { + for (final String json : new String[]{ + "[]", + "[ ]"}) { + test(json, Collections.emptyList()); + } + for (final String json : new String[]{ + "[1]", + "[ 1]", + "[1 ]", + "[ 1 ]"}) { + test(json, Collections.singletonList(1)); + } + for (final String json : new String[]{ + "[1,2]", + "[1, 2]", + "[ 1, 2]", + "[1 , 2]", + "[ 1 , 2 ]"}) { + test(json, Arrays.asList(1, 2)); + } + } + + @Test + void test_invalid_array_start() { + final String json = "["; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("premature end of input"); + } + + @Test + void test_invalid_array_end_1() { + final String json = "]"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was not expecting ARRAY_END at index 0"); + } + + @Test + void test_invalid_array_comma() { + final String json = "[,"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was expecting an array element at index 1: COMMA"); + } + + @Test + void test_invalid_array_end_2() { + final String json = "[1,"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("premature end of input"); + } + + @Test + void test_invalid_array_end_3() { + final String json = "[1,]"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was expecting an array element at index 3: ARRAY_END"); + } + + @Test + void test_valid_objects() { + test("{}", Collections.emptyMap()); + test("{\"foo\":\"bar\"}", Collections.singletonMap("foo", "bar")); + } + + @Test + void test_invalid_object_start() { + final String json = "{"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("premature end of input"); + } + + @Test + void test_invalid_object_end() { + final String json = "}"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was not expecting OBJECT_END at index 0"); + } + + @Test + void test_invalid_object_colon_1() { + final String json = "{\"foo\"\"bar\"}"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was expecting COLON at index 6: bar"); + } + + @Test + void test_invalid_object_colon_2() { + final String json = "{\"foo\":}"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("premature end of input"); + } + + @Test + void test_invalid_object_token() { + final String json = "{\"foo\":\"bar}"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("premature end of input"); + } + + @Test + void test_invalid_object_comma() { + final String json = "{\"foo\":\"bar\",}"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was expecting an object key at index 13: OBJECT_END"); + } + + @Test + void test_invalid_object_key() { + final String json = "{\"foo\":\"bar\",]}"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("was expecting an object key at index 13: ARRAY_END"); + } + + @Test + @SuppressWarnings("DoubleBraceInitialization") + public void test_nesting() { + test( + "{\"k1\": [true, null, 1e5, {\"k2\": \"v2\", \"k3\": {\"k4\": \"v4\"}}]}", + Collections.singletonMap( + "k1", + Arrays.asList( + true, + null, + new BigDecimal("1e5"), + new LinkedHashMap() {{ + put("k2", "v2"); + put("k3", Collections.singletonMap("k4", "v4")); + }}))); + } + + @ParameterizedTest + @ValueSource(strings = { + "/*comment*/{\"foo\":\"bar\"}", + "{/*comment*/\"foo\":\"bar\"}", + "{\"foo\"/*comment*/:\"bar\"}", + "{\"foo\":/*comment*/\"bar\"}", + "{\"foo\":\"bar\"/*comment*/}", + "{\"foo\":\"bar\"}/*comment*/", + "/*\nmulti\nline\ncomment\n*/{\"foo\":\"bar\"}/*comment*/", + "{\"foo\"/*comment*/:/*comment*/\"bar\"}", + "{\"foo\"/*:*/:/*\"*/\"bar\"}", + "{\"foo\"/*\nanother comment*/:\"bar\"}", + "{\"foo\":\"bar\"/*\n}{}*/}", + }) + void test_comments(final String json) { + test(json, Map.of("foo", "bar")); + } + + @Test + void test_unclosed_comments_error() { + final String json = "{\"foo\": /*"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("premature end of input"); + } + + @Test + void test_unopened_comments_error() { + final String json = "{\"foo\": */"; + Assertions + .assertThatThrownBy(() -> JsonReader.read(json)) + .as("json=%s", json) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("invalid character at index 8: *"); + } + + private void test(final String json, final Object expected) { + // Wrapping the assertion one more time to decorate it with the input. + Assertions + .assertThatCode(() -> Assertions + .assertThat(JsonReader.read(json)) + .isEqualTo(expected)) + .as("json=%s", json) + .doesNotThrowAnyException(); + } + +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java similarity index 96% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java index 76e0f5adca8..a473028ad23 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java @@ -17,9 +17,9 @@ package org.apache.logging.log4j.core.util; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * This class is borrowed from Jackson. diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java similarity index 80% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java index c97d3b6cb42..caab0683a51 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java @@ -17,9 +17,9 @@ package org.apache.logging.log4j.core.util; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the Loader class. @@ -28,13 +28,14 @@ public class LoaderTest { @Test public void testLoadClassWithNullClassloaderReturnNull() throws Exception { - assertNull("Expect null return value for null ClassLoader.", - Loader.loadClass(Loader.class.getCanonicalName(), null)); + assertNull( + Loader.loadClass(Loader.class.getCanonicalName(), null), "Expect null return value for null ClassLoader."); } @Test public void testLoadClassReturnClassForExistingClass() throws Exception { - assertEquals("Expect Class return value for null ClassLoader.", Loader.class, - Loader.loadClass(Loader.class.getCanonicalName(), Loader.getClassLoader())); + assertEquals(Loader.class, + Loader.loadClass(Loader.class.getCanonicalName(), Loader.getClassLoader()), + "Expect Class return value for null ClassLoader."); } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java new file mode 100644 index 00000000000..c0f7ffa927a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.util; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import java.io.File; +import java.net.URI; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class NetUtilsTest { + + @Test + public void testToUriWithoutBackslashes() { + final String config = "file:///path/to/something/on/unix"; + URI uri = NetUtils.toURI(config); + + assertNotNull(uri, "The URI should not be null."); + assertEquals("file:///path/to/something/on/unix", uri.toString(), "The URI is not correct."); + + final String properUriPath = "/path/without/spaces"; + uri = NetUtils.toURI(properUriPath); + + assertNotNull(uri, "The URI should not be null."); + assertEquals(properUriPath, uri.toString(), "The URI is not correct."); + } + + @Test + public void testToUriUnixWithSpaces() { + final String pathWithSpaces = "/ path / with / spaces"; + final URI uri = NetUtils.toURI(pathWithSpaces); + + assertNotNull(uri, "The URI should not be null."); + assertEquals(new File(pathWithSpaces).toURI().toString(), uri.toString(), "The URI is not correct."); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + public void testToUriWindowsWithBackslashes() { + final String config = "file:///D:\\path\\to\\something/on/windows"; + final URI uri = NetUtils.toURI(config); + + assertNotNull(uri, "The URI should not be null."); + assertEquals("file:///D:/path/to/something/on/windows", uri.toString(), "The URI is not correct."); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + public void testToUriWindowsAbsolutePath() { + final String config = "D:\\path\\to\\something\\on\\windows"; + final URI uri = NetUtils.toURI(config); + + assertNotNull(uri, "The URI should not be null."); + assertEquals("file:/D:/path/to/something/on/windows", uri.toString(), "The URI is not correct."); + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/OptionConverterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/OptionConverterTest.java new file mode 100644 index 00000000000..8050d603105 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/OptionConverterTest.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import java.util.Properties; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests {@link OptionConverter}. + */ +public class OptionConverterTest { + + @Test + public void testSubstVars() { + Properties props = new Properties(); + props.setProperty("key", "${key}"); + props.setProperty("testKey", "Log4j"); + assertEquals("Value of key is ${key}.", OptionConverter.substVars("Value of key is ${key}.", props)); + assertEquals("Value of key is .", OptionConverter.substVars("Value of key is ${key2}.", props)); + assertEquals("Value of testKey:testKey is Log4j:Log4j", + OptionConverter.substVars("Value of testKey:testKey is ${testKey}:${testKey}", props)); + } + + /** + * StrSubstitutor would resolve ${key} to Key, append the result to "test" and then resolve ${testKey}. + * Verify that substVars doesn't construct dynamic keys. + */ + @Test + public void testAppend() { + Properties props = new Properties(); + props.setProperty("key", "Key"); + props.setProperty("testKey", "Hello"); + assertEquals("Value of testKey is }", + OptionConverter.substVars("Value of testKey is ${test${key}}", props)); + } + + /** + * StrSubstitutor would resolve ${key}, append the result to "test" and then resolve ${testKey}. + * Verify that substVars will treat the second expression up to the first '}' as part of the key. + */ + @Test + public void testAppend2() { + Properties props = new Properties(); + props.setProperty("test${key", "Hello"); + assertEquals("Value of testKey is Hello}", + OptionConverter.substVars("Value of testKey is ${test${key}}", props)); + } + + @Test + public void testRecursion() { + Properties props = new RecursiveProperties(); + props.setProperty("name", "Neo"); + props.setProperty("greeting", "Hello ${name}"); + + String s = props.getProperty("greeting"); + System.out.println("greeting = '"+s+"'"); + } + + private static class RecursiveProperties extends Properties { + @Override + public String getProperty(String key) + { + System.out.println("getProperty for "+key); + try + { + String val = super.getProperty(key); + // The following call works for log4j 2.17.0 and causes StackOverflowError for 2.17.1 + // This is because substVars change implementation in 2.17.1 to call StrSubstitutor.replace(val, props) + // which calls props.getProperty() for EVERY property making it recursive + return OptionConverter.substVars(val, this); + } + catch (Exception e) + { + return super.getProperty(key); + } + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ProcessIdUtilTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ProcessIdUtilTest.java new file mode 100644 index 00000000000..661f8c8500a --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ProcessIdUtilTest.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ProcessIdUtilTest { + + @Test + public void processIdTest() { + String processId = ProcessIdUtil.getProcessId(); + assertNotEquals(processId, ProcessIdUtil.DEFAULT_PROCESSID, "ProcessId is default"); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java new file mode 100644 index 00000000000..93091019b69 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ShutdownCallbackRegistryTest.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.SingletonFactory; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.concurrent.ConcurrentLinkedQueue; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ShutdownCallbackRegistryTest { + + @SingletonFactory + ShutdownCallbackRegistry shutdownCallbackRegistry() { + return new Registry(); + } + + @Test + @LoggerContextSource(value = "ShutdownCallbackRegistryTest.xml", bootstrap = true) + public void testShutdownCallbackRegistry(final LoggerContext context) { + assertTrue(context.isStarted(), "LoggerContext should be started"); + assertThat(Registry.CALLBACKS, hasSize(1)); + Registry.shutdown(); + assertTrue(context.isStopped(), "LoggerContext should be stopped"); + assertThat(Registry.CALLBACKS, hasSize(0)); + final ContextSelector selector = ((Log4jContextFactory) LogManager.getFactory()).getSelector(); + assertThat(selector.getLoggerContexts(), not(hasItem(context))); + } + + @Singleton + public static class Registry implements ShutdownCallbackRegistry { + private static final Logger LOGGER = StatusLogger.getLogger(); + private static final Collection CALLBACKS = new ConcurrentLinkedQueue<>(); + + @Override + public Cancellable addShutdownCallback(final Runnable callback) { + final Cancellable cancellable = new Cancellable() { + @Override + public void cancel() { + LOGGER.debug("Cancelled shutdown callback: {}", callback); + CALLBACKS.remove(this); + } + + @Override + public void run() { + LOGGER.debug("Called shutdown callback: {}", callback); + callback.run(); + } + }; + CALLBACKS.add(cancellable); + return cancellable; + } + + private static void shutdown() { + for (final Runnable callback : CALLBACKS) { + LOGGER.debug("Calling shutdown callback: {}", callback); + callback.run(); + } + CALLBACKS.clear(); + } + } + +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SourceTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SourceTest.java new file mode 100644 index 00000000000..d0bc54244b7 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/SourceTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.util; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Tests {@link Source}. + */ +public class SourceTest { + + @Test + public void testEqualityFile() { + assertEquals(new Source(new File("foo")), new Source(new File("foo"))); + assertEquals(new Source(new File("foo")), new Source(new File("./foo"))); + assertEquals(new Source(new File("foo.txt")), new Source(new File("./foo.txt"))); + } + + @Test + public void testEqualityPath() { + assertEquals(new Source(Paths.get("foo")), new Source(Paths.get("foo"))); + assertEquals(new Source(Paths.get("foo")), new Source(Paths.get("./foo"))); + assertEquals(new Source(Paths.get("foo.txt")), new Source(Paths.get("./foo.txt"))); + } + + @Test + @Disabled("File URI is broken.") + public void testEqualityURIFile() { + assertEquals(new Source(Paths.get("foo").toUri()), new Source(Paths.get("./foo").toUri())); + } + + @Test + public void testEqualityURIHttp() { + assertEquals(new Source(URI.create("http://www.apache.org/index.html")), new Source(URI.create("http://www.apache.org/index.html"))); + assertEquals(new Source(URI.create("http://www.apache.org/")), new Source(URI.create("http://www.apache.org////"))); + assertEquals(new Source(URI.create("http://www.apache.org/")), new Source(URI.create("http://www.apache.org/./././."))); + } + + public void testEqualityURLFile() throws MalformedURLException { + assertEquals(new Source(Paths.get("foo").toUri().toURL()), new Source(Paths.get("./foo").toUri().toURL())); + } + + public void testEqualityURLHttp() throws MalformedURLException { + assertEquals(new Source(URI.create("http://www.apache.org/index.html").toURL()), new Source(URI.create("http://www.apache.org/index.html").toURL())); + assertEquals(new Source(URI.create("http://www.apache.org").toURL()), new Source(URI.create("http://www.apache.org////").toURL())); + assertEquals(new Source(URI.create("http://www.apache.org").toURL()), new Source(URI.create("http://www.apache.org/./././.").toURL())); + } + + public void testEqualityURLHttps() throws MalformedURLException { + assertEquals(new Source(URI.create("https://www.apache.org/index.html").toURL()), new Source(URI.create("https://www.apache.org/index.html").toURL())); + assertEquals(new Source(URI.create("https://www.apache.org").toURL()), new Source(URI.create("https://www.apache.org////").toURL())); + assertEquals(new Source(URI.create("https://www.apache.org").toURL()), new Source(URI.create("https://www.apache.org/./././.").toURL())); + } + + @Test + public void testFileConstructor() { + final Path path = Paths.get("foo"); + final URI uri = path.toUri(); + final File file = path.toFile(); + final Source source = new Source(file); + assertEquals(file, source.getFile()); + assertEquals(path, source.getFile().toPath()); + assertEquals(path, source.getPath()); + assertEquals(uri, source.getURI()); + } + + @Test + public void testPathStringConstructor() { + final Path path = Paths.get("foo"); + final URI uri = path.toUri(); + final File file = path.toFile(); + final Source source = new Source(path); + assertEquals(file, source.getFile()); + assertEquals(path, source.getFile().toPath()); + assertEquals(path, source.getPath()); + assertEquals(uri, source.getURI()); + } + + public void testPathURIFileConstructor() { + final Path path = Paths.get(URI.create("file:///C:/foo")); + final URI uri = path.toUri(); + final File file = path.toFile(); + final Source source = new Source(path); + assertEquals(file, source.getFile()); + assertEquals(path, source.getFile().toPath()); + assertEquals(path, source.getPath()); + assertEquals(uri, source.getURI()); + } + + @Test + public void testURIConstructor() throws MalformedURLException { + final Path path = Paths.get("foo"); + final URI uri = path.toUri(); + final File file = path.toFile(); + final Source source = new Source(uri); + assertEquals(file.getAbsoluteFile(), source.getFile()); + assertEquals(uri.toString(), source.getLocation()); + assertEquals(path.toAbsolutePath(), source.getPath()); + } + + @Test + public void testURIFileConstructor() throws MalformedURLException { + final URI uri = URI.create("file:///C:/foo"); + final Path path = Paths.get(uri); + final File file = path.toFile(); + final Source source = new Source(uri); + assertEquals(file.getAbsoluteFile(), source.getFile()); + assertEquals(uri.toString(), source.getLocation()); + } + + @Test + public void testURIHttpConstructor() throws MalformedURLException { + final URI uri = URI.create("http://www.apache.org"); + final Source source = new Source(uri); + assertEquals(null, source.getFile()); + assertEquals(uri.toString(), source.getLocation()); + } + + @Test + public void testURIHttpsConstructor() throws MalformedURLException { + final URI uri = URI.create("https://www.apache.org"); + final Source source = new Source(uri); + assertEquals(null, source.getFile()); + assertEquals(uri.toString(), source.getLocation()); + } + + @Test + public void testURLConstructor() throws MalformedURLException { + final Path path = Paths.get("foo"); + final File file = path.toFile(); + final URI uri = path.toUri(); + final URL url = uri.toURL(); + final Source source = new Source(url); + assertEquals(file.getAbsoluteFile(), source.getFile()); + assertEquals(url.toString(), source.getLocation()); + assertEquals(path.toAbsolutePath(), source.getPath()); + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.java new file mode 100644 index 00000000000..098d9ef64cd --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ThrowablesTest { + + @Test + public void testGetRootCauseNone() { + final NullPointerException throwable = new NullPointerException(); + assertEquals(throwable, Throwables.getRootCause(throwable)); + } + + @Test + public void testGetRootCauseDepth1() { + final Throwable cause = new NullPointerException(); + final Throwable error = new UnsupportedOperationException(cause); + assertEquals(cause, Throwables.getRootCause(error)); + } + + @Test + public void testGetRootCauseDepth2() { + final Throwable rootCause = new NullPointerException(); + final Throwable cause = new UnsupportedOperationException(rootCause); + final Throwable error = new IllegalArgumentException(cause); + assertEquals(rootCause, Throwables.getRootCause(error)); + } + + @SuppressWarnings("ThrowableNotThrown") + @Test + public void testGetRootCauseLoop() { + final Throwable cause1 = new RuntimeException(); + final Throwable cause2 = new RuntimeException(cause1); + final Throwable cause3 = new RuntimeException(cause2); + cause1.initCause(cause3); + assertThrows(IllegalArgumentException.class, () -> Throwables.getRootCause(cause3)); + } + + @Test + public void testRethrowRuntimeException() { + assertThrows(NullPointerException.class, () -> Throwables.rethrow(new NullPointerException())); + } + + @Test + public void testRethrowError() { + assertThrows(UnknownError.class, () -> Throwables.rethrow(new UnknownError())); + } + + @Test + public void testRethrowCheckedException() { + assertThrows(NoSuchMethodException.class, () -> Throwables.rethrow(new NoSuchMethodException())); + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/UnexpectedFormatException.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/UnexpectedFormatException.java similarity index 100% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/UnexpectedFormatException.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/UnexpectedFormatException.java diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java similarity index 79% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java index 556f76577ba..84bd5cc94a2 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java @@ -16,14 +16,12 @@ */ package org.apache.logging.log4j.core.util; +import org.junit.jupiter.api.Test; + import java.util.UUID; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.Assert.*; -/** - * - */ public class UuidTest { private static final int COUNT = 200; @@ -37,7 +35,7 @@ public void testTimeBaseUuid() { //final UUID uuid2 = UuidUtil.getTimeBasedUUID(); // unused final long current = (System.currentTimeMillis() * 10000) + NUM_100NS_INTERVALS_SINCE_UUID_EPOCH; final long time = uuid.timestamp(); - assertTrue("Incorrect time", current + 10000 - time > 0); + assertTrue(current + 10000 - time > 0, "Incorrect time"); final UUID[] uuids = new UUID[COUNT]; final long start = System.nanoTime(); for (int i=0; i < COUNT; ++i) { @@ -54,13 +52,29 @@ public void testTimeBaseUuid() { } } } - assertEquals(errors + " duplicate UUIDS", 0, errors); + assertEquals(0, errors, errors + " duplicate UUIDS"); final int variant = uuid.variant(); - assertEquals("Incorrect variant. Expected 2 got " + variant, 2, variant); + assertEquals(2, variant, "Incorrect variant. Expected 2 got " + variant); final int version = uuid.version(); - assertEquals("Incorrect version. Expected 1 got " + version, 1, version); + assertEquals(1, version, "Incorrect version. Expected 1 got " + version); final long node = uuid.node(); - assertTrue("Invalid node", node != 0); + assertTrue(node != 0, "Invalid node"); + } + + @Test + public void testInitialize() { + // Test if no ArrayIndexOutOfBoundsException is thrown when Mac address array is null + UuidUtil.initialize(null); + + // Test if no ArrayIndexOutOfBoundsException is thrown for different Mac address lengths + for (int i=0; i < 10; i++) { + // Create MAC address byte array with i as size + byte[] mac = new byte[i]; + for(int j=0; j < i; j++) { + mac[j] = (byte)j; + } + UuidUtil.initialize(mac); + } } @Test @@ -90,7 +104,7 @@ public void testThreads() throws Exception { } } } - assertEquals(errors + " duplicate UUIDS", 0, errors); + assertEquals(0, errors, errors + " duplicate UUIDS"); } diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java new file mode 100644 index 00000000000..cd73ef6d60e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.util; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Queue; +import java.util.TimeZone; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationListener; +import org.apache.logging.log4j.core.config.ConfigurationScheduler; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.HttpWatcher; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.apache.logging.log4j.core.net.UrlConnectionFactory; +import org.apache.logging.log4j.core.test.net.ssl.TestConstants; +import org.apache.logging.log4j.core.time.internal.format.FastDateFormat; +import org.apache.logging.log4j.test.junit.StatusLoggerRule; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.stubbing.StubMapping; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.removeStub; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * Test the WatchManager + */ +public class WatchHttpTest { + + private static final String FORCE_RUN_KEY = WatchHttpTest.class.getSimpleName() + ".forceRun"; + private final String file = "log4j-test1.xml"; + private static FastDateFormat formatter; + private static final String XML = "application/xml"; + + private static final boolean IS_WINDOWS = PropertiesUtil.getProperties().isOsWindows(); + + @BeforeClass + public static void beforeClass() { + System.setProperty(UrlConnectionFactory.ALLOWED_PROTOCOLS, "http,https"); + try { + formatter = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss", TimeZone.getTimeZone("UTC")); + } catch (Exception ex) { + System.err.println("Unable to create date format."); + ex.printStackTrace(); + throw ex; + } + } + + @ClassRule + public static StatusLoggerRule RULE = new StatusLoggerRule(Level.OFF); + + @Rule + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort() + .dynamicHttpsPort() + .keystorePath(TestConstants.KEYSTORE_FILE) + .keystorePassword(String.valueOf(TestConstants.KEYSTORE_PWD())) + .keyManagerPassword(String.valueOf(TestConstants.KEYSTORE_PWD())) + .keystoreType(TestConstants.KEYSTORE_TYPE)); + + @Test + public void testWatchManager() throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + List listeners = new ArrayList<>(); + listeners.add(new TestConfigurationListener(queue, "log4j-test1.xml")); + TimeZone timeZone = TimeZone.getTimeZone("UTC"); + Calendar now = Calendar.getInstance(timeZone); + Calendar previous = now; + previous.add(Calendar.MINUTE, -5); + Configuration configuration = new DefaultConfiguration(); + Assume.assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY)); + URL url = new URL("http://localhost:" + wireMockRule.port() + "/log4j-test1.xml"); + StubMapping stubMapping = stubFor(get(urlPathEqualTo("/log4j-test1.xml")) + .willReturn(aResponse() + .withBodyFile(file) + .withStatus(200) + .withHeader("Last-Modified", formatter.format(previous) + " GMT") + .withHeader("Content-Type", XML))); + final ConfigurationScheduler scheduler = new ConfigurationScheduler(); + scheduler.incrementScheduledItems(); + final WatchManager watchManager = new WatchManager(scheduler); + watchManager.setIntervalSeconds(1); + scheduler.start(); + watchManager.start(); + try { + watchManager.watch(new Source(url.toURI()), new HttpWatcher(configuration, null, + listeners, previous.getTimeInMillis())); + final String str = queue.poll(2, TimeUnit.SECONDS); + assertNotNull("File change not detected", str); + } finally { + removeStub(stubMapping); + watchManager.stop(); + scheduler.stop(); + } + } + + @Test + public void testNotModified() throws Exception { + BlockingQueue queue = new LinkedBlockingQueue<>(); + List listeners = new ArrayList<>(); + listeners.add(new TestConfigurationListener(queue, "log4j-test2.xml")); + TimeZone timeZone = TimeZone.getTimeZone("UTC"); + Calendar now = Calendar.getInstance(timeZone); + Calendar previous = now; + previous.add(Calendar.MINUTE, -5); + Configuration configuration = new DefaultConfiguration(); + Assume.assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY)); + URL url = new URL("http://localhost:" + wireMockRule.port() + "/log4j-test2.xml"); + StubMapping stubMapping = stubFor(get(urlPathEqualTo("/log4j-test2.xml")) + .willReturn(aResponse() + .withBodyFile(file) + .withStatus(304) + .withHeader("Last-Modified", formatter.format(now) + " GMT") + .withHeader("Content-Type", XML))); + final ConfigurationScheduler scheduler = new ConfigurationScheduler(); + scheduler.incrementScheduledItems(); + final WatchManager watchManager = new WatchManager(scheduler); + watchManager.setIntervalSeconds(1); + scheduler.start(); + watchManager.start(); + try { + watchManager.watch(new Source(url.toURI()), new HttpWatcher(configuration, null, + listeners, previous.getTimeInMillis())); + final String str = queue.poll(2, TimeUnit.SECONDS); + assertNull("File changed.", str); + } finally { + removeStub(stubMapping); + watchManager.stop(); + scheduler.stop(); + } + } + + private static class TestConfigurationListener implements ConfigurationListener { + private final Queue queue; + private final String name; + + public TestConfigurationListener(final Queue queue, String name) { + this.queue = queue; + this.name = name; + } + + @Override + public void onChange(Reconfigurable reconfigurable) { + //System.out.println("Reconfiguration detected for " + name); + queue.add(name); + } + } +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java similarity index 87% rename from log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java index d70ce69761f..c186f064d37 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java @@ -16,8 +16,12 @@ */ package org.apache.logging.log4j.core.util; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import org.apache.logging.log4j.core.config.ConfigurationScheduler; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.junit.jupiter.api.condition.OS; import java.io.File; import java.io.FileOutputStream; @@ -30,26 +34,23 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.core.config.ConfigurationScheduler; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.junit.Assume; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; /** * Test the WatchManager */ +@DisabledOnOs(OS.WINDOWS) +@EnabledIfSystemProperty(named = "WatchManagerTest.forceRun", matches = "true") +@Tag("sleepy") public class WatchManagerTest { - private static final String FORCE_RUN_KEY = WatchManagerTest.class.getSimpleName() + ".forceRun"; private final String testFile = "target/testWatchFile"; private final String originalFile = "target/test-classes/log4j-test1.xml"; private final String newFile = "target/test-classes/log4j-test1.yaml"; - private static final boolean IS_WINDOWS = PropertiesUtil.getProperties().isOsWindows(); - @Test public void testWatchManager() throws Exception { - Assume.assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY)); final ConfigurationScheduler scheduler = new ConfigurationScheduler(); scheduler.incrementScheduledItems(); final WatchManager watchManager = new WatchManager(scheduler); @@ -71,7 +72,7 @@ public void testWatchManager() throws Exception { Files.copy(source, Paths.get(targetFile.toURI()), StandardCopyOption.REPLACE_EXISTING); Thread.sleep(1000); final File f = queue.poll(1, TimeUnit.SECONDS); - assertNotNull("File change not detected", f); + assertNotNull(f, "File change not detected"); } finally { watchManager.stop(); scheduler.stop(); @@ -80,7 +81,6 @@ public void testWatchManager() throws Exception { @Test public void testWatchManagerReset() throws Exception { - Assume.assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY)); final ConfigurationScheduler scheduler = new ConfigurationScheduler(); scheduler.incrementScheduledItems(); final WatchManager watchManager = new WatchManager(scheduler); @@ -105,7 +105,7 @@ public void testWatchManagerReset() throws Exception { watchManager.start(); Thread.sleep(1000); final File f = queue.poll(1, TimeUnit.SECONDS); - assertNull("File change detected", f); + assertNull(f, "File change detected"); } finally { watchManager.stop(); scheduler.stop(); @@ -114,7 +114,6 @@ public void testWatchManagerReset() throws Exception { @Test public void testWatchManagerResetFile() throws Exception { - Assume.assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY)); final ConfigurationScheduler scheduler = new ConfigurationScheduler(); scheduler.incrementScheduledItems(); final WatchManager watchManager = new WatchManager(scheduler); @@ -139,14 +138,14 @@ public void testWatchManagerResetFile() throws Exception { watchManager.start(); Thread.sleep(1000); final File f = queue.poll(1, TimeUnit.SECONDS); - assertNull("File change detected", f); + assertNull(f, "File change detected"); } finally { watchManager.stop(); scheduler.stop(); } } - private class TestWatcher implements FileWatcher { + private static class TestWatcher implements FileWatcher { private final Queue queue; diff --git a/log4j-core-test/src/test/java9/module-info.java b/log4j-core-test/src/test/java9/module-info.java new file mode 100644 index 00000000000..4ffd6d3fae8 --- /dev/null +++ b/log4j-core-test/src/test/java9/module-info.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +open module org.apache.logging.log4j.core { + exports org.apache.logging.log4j.core; + exports org.apache.logging.log4j.core.appender; + exports org.apache.logging.log4j.core.appender.db; + exports org.apache.logging.log4j.core.appender.nosql; + exports org.apache.logging.log4j.core.appender.rewrite; + exports org.apache.logging.log4j.core.appender.rolling; + exports org.apache.logging.log4j.core.appender.rolling.action; + exports org.apache.logging.log4j.core.appender.routing; + exports org.apache.logging.log4j.core.async; + exports org.apache.logging.log4j.core.config; + exports org.apache.logging.log4j.core.config.arbiters; + exports org.apache.logging.log4j.core.config.builder; + exports org.apache.logging.log4j.core.config.di; + exports org.apache.logging.log4j.core.config.plugins; + exports org.apache.logging.log4j.core.config.plugins.convert; + exports org.apache.logging.log4j.core.config.plugins.util; + exports org.apache.logging.log4j.core.config.plugins.validation.validators; + exports org.apache.logging.log4j.core.config.properties; + exports org.apache.logging.log4j.core.config.xml; + exports org.apache.logging.log4j.core.filter; + exports org.apache.logging.log4j.core.impl; + exports org.apache.logging.log4j.core.jmx; + exports org.apache.logging.log4j.core.layout; + exports org.apache.logging.log4j.core.lookup; + exports org.apache.logging.log4j.core.message; + exports org.apache.logging.log4j.core.net; + exports org.apache.logging.log4j.core.net.ssl; + exports org.apache.logging.log4j.core.parser; + exports org.apache.logging.log4j.core.pattern; + exports org.apache.logging.log4j.core.script; + exports org.apache.logging.log4j.core.selector; + exports org.apache.logging.log4j.core.test; + exports org.apache.logging.log4j.core.test.appender; + exports org.apache.logging.log4j.core.test.hamcrest; + exports org.apache.logging.log4j.core.test.junit; + exports org.apache.logging.log4j.core.time; + exports org.apache.logging.log4j.core.tools; + exports org.apache.logging.log4j.core.util; + + requires transitive java.compiler; + requires transitive java.desktop; + requires transitive java.management; + requires transitive java.sql; + requires transitive java.rmi; + requires transitive java.xml; + requires transitive org.apache.logging.log4j; + requires transitive org.apache.logging.log4j.test; + requires transitive org.apache.logging.log4j.plugins; + requires transitive org.apache.logging.log4j.plugins.test; + requires transitive com.lmax.disruptor; + requires transitive org.jctools.core; + requires transitive org.osgi.framework; + requires transitive com.conversantmedia.disruptor; + requires transitive net.bytebuddy; + requires com.fasterxml.jackson.core; + requires com.fasterxml.jackson.databind; + requires transitive com.fasterxml.jackson.dataformat.xml; + requires transitive com.fasterxml.jackson.dataformat.yaml; + requires transitive org.apache.commons.compress; + requires transitive org.fusesource.jansi; + requires transitive org.junit.jupiter.api; + requires transitive org.junit.jupiter.engine; + requires transitive org.junit.jupiter.params; + requires transitive org.junit.platform.commons; + requires transitive org.junit.platform.engine; + requires transitive junit; + + uses org.apache.logging.log4j.core.util.ContextDataProvider; + uses org.apache.logging.log4j.core.util.WatchEventService; + provides org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory with org.apache.logging.log4j.core.message.ExtendedThreadInfoFactory; + provides org.apache.logging.log4j.core.util.ContextDataProvider with org.apache.logging.log4j.core.impl.ThreadContextDataProvider; + provides org.apache.logging.log4j.spi.Provider with org.apache.logging.log4j.core.impl.Log4jProvider; + provides org.apache.logging.log4j.plugins.model.PluginService with org.apache.logging.log4j.core.plugins.Log4jPlugins, + org.apache.logging.log4j.core.test.plugins.Log4jPlugins; +} diff --git a/log4j-core-test/src/test/resources/AsyncAppenderConfigTest-LOG4J2-2032.xml b/log4j-core-test/src/test/resources/AsyncAppenderConfigTest-LOG4J2-2032.xml new file mode 100644 index 00000000000..d70d9173b29 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncAppenderConfigTest-LOG4J2-2032.xml @@ -0,0 +1,40 @@ + + + + + + + + %level{length=1} %date{MMdd-HHmm:ss,SSS} %logger{1.} %message %X [%thread]%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncAppenderExceptionHandlingTest.xml b/log4j-core-test/src/test/resources/AsyncAppenderExceptionHandlingTest.xml new file mode 100644 index 00000000000..84764fe8ab6 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncAppenderExceptionHandlingTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/AsyncLoggerConfigAutoFlushTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerConfigAutoFlushTest.xml similarity index 100% rename from log4j-core/src/test/resources/AsyncLoggerConfigAutoFlushTest.xml rename to log4j-core-test/src/test/resources/AsyncLoggerConfigAutoFlushTest.xml diff --git a/log4j-core-test/src/test/resources/AsyncLoggerConfigErrorOnFormat.xml b/log4j-core-test/src/test/resources/AsyncLoggerConfigErrorOnFormat.xml new file mode 100644 index 00000000000..da8c8342544 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigErrorOnFormat.xml @@ -0,0 +1,36 @@ + + + + + + + + %d %p %c{1.} [%t] %m %ex%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerConfigTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest.xml new file mode 100644 index 00000000000..c6f877a6bfc --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest.xml @@ -0,0 +1,40 @@ + + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerConfigTest2.xml b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest2.xml new file mode 100644 index 00000000000..f6f885d62d2 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest2.xml @@ -0,0 +1,37 @@ + + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerConfigTest4.xml b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest4.xml new file mode 100644 index 00000000000..ac0cbf8ba83 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigTest4.xml @@ -0,0 +1,45 @@ + + + + + + + + %d %p %c{1.} [%t] %testformat %testparameters %m %ex%n + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerConfigThreadContextTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerConfigThreadContextTest.xml new file mode 100644 index 00000000000..2a59f8573e5 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConfigThreadContextTest.xml @@ -0,0 +1,67 @@ + + + + + + + + %p %c{1.} %X{KEY} %x %X %m%ex%n + + + + + + + + %p %c{1.} %X{KEY} %x %X %m%ex%n + + + + + + + + %p %c{1.} %X{KEY} %x %X %m%ex%n + + + + + %p %c{1.} %X{KEY} %x %X %m%ex%n + + + + + + + configValue + configValue2 + + + + + configValue + configValue2 + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerConsoleTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerConsoleTest.xml new file mode 100644 index 00000000000..a34fb79eb28 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerConsoleTest.xml @@ -0,0 +1,33 @@ + + + + + + + + %xEx{0} + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerCustomSelectorLocationTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerCustomSelectorLocationTest.xml new file mode 100644 index 00000000000..611fc477e52 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerCustomSelectorLocationTest.xml @@ -0,0 +1,17 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/AsyncLoggerDefaultLocationTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerDefaultLocationTest.xml new file mode 100644 index 00000000000..f5a3cb32315 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerDefaultLocationTest.xml @@ -0,0 +1,20 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core/src/test/resources/AsyncLoggerLocationTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerLocationTest.xml similarity index 100% rename from log4j-core/src/test/resources/AsyncLoggerLocationTest.xml rename to log4j-core-test/src/test/resources/AsyncLoggerLocationTest.xml diff --git a/log4j-core-test/src/test/resources/AsyncLoggerTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerTest.xml new file mode 100644 index 00000000000..b9cfe505390 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerTest.xml @@ -0,0 +1,34 @@ + + + + + + + + %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerThreadContextTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerThreadContextTest.xml new file mode 100644 index 00000000000..9d57d15dd49 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerThreadContextTest.xml @@ -0,0 +1,36 @@ + + + + + + + + %p %c{1.} %X{KEY} %x %X %m%ex%n + + + + + + + configValue + configValue2 + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggerTimestampMessageTest.xml b/log4j-core-test/src/test/resources/AsyncLoggerTimestampMessageTest.xml new file mode 100644 index 00000000000..a7038fcfbfe --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggerTimestampMessageTest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggersWithAsyncAppenderTest.xml b/log4j-core-test/src/test/resources/AsyncLoggersWithAsyncAppenderTest.xml new file mode 100644 index 00000000000..be780bff587 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggersWithAsyncAppenderTest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml b/log4j-core-test/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml new file mode 100644 index 00000000000..80bea0c2d13 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncLoggersWithAsyncLoggerConfigTest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigGlobalLoggerTest.xml b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigGlobalLoggerTest.xml new file mode 100644 index 00000000000..195bca37cbe --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigGlobalLoggerTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigTest.xml b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigTest.xml new file mode 100644 index 00000000000..b9b0fe81b60 --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyFactoryConfigTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigGlobalLoggerTest.xml b/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigGlobalLoggerTest.xml new file mode 100644 index 00000000000..c65a76ab34c --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigGlobalLoggerTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigTest.xml b/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigTest.xml new file mode 100644 index 00000000000..5a3b025c3ce --- /dev/null +++ b/log4j-core-test/src/test/resources/AsyncWaitStrategyIncorrectFactoryConfigTest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/BlockingQueueFactory-ArrayBlockingQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-ArrayBlockingQueue.xml similarity index 100% rename from log4j-core/src/test/resources/BlockingQueueFactory-ArrayBlockingQueue.xml rename to log4j-core-test/src/test/resources/BlockingQueueFactory-ArrayBlockingQueue.xml diff --git a/log4j-core/src/test/resources/BlockingQueueFactory-DisruptorBlockingQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-DisruptorBlockingQueue.xml similarity index 100% rename from log4j-core/src/test/resources/BlockingQueueFactory-DisruptorBlockingQueue.xml rename to log4j-core-test/src/test/resources/BlockingQueueFactory-DisruptorBlockingQueue.xml diff --git a/log4j-core/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml similarity index 100% rename from log4j-core/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml rename to log4j-core-test/src/test/resources/BlockingQueueFactory-JCToolsBlockingQueue.xml diff --git a/log4j-core/src/test/resources/BlockingQueueFactory-LinkedTransferQueue.xml b/log4j-core-test/src/test/resources/BlockingQueueFactory-LinkedTransferQueue.xml similarity index 100% rename from log4j-core/src/test/resources/BlockingQueueFactory-LinkedTransferQueue.xml rename to log4j-core-test/src/test/resources/BlockingQueueFactory-LinkedTransferQueue.xml diff --git a/log4j-core/src/test/resources/ContextMapLookupTest.xml b/log4j-core-test/src/test/resources/ContextMapLookupTest.xml similarity index 93% rename from log4j-core/src/test/resources/ContextMapLookupTest.xml rename to log4j-core-test/src/test/resources/ContextMapLookupTest.xml index 67374946dd6..6c2391ef01f 100644 --- a/log4j-core/src/test/resources/ContextMapLookupTest.xml +++ b/log4j-core-test/src/test/resources/ContextMapLookupTest.xml @@ -15,7 +15,7 @@ ~ limitations under the license. --> - + @@ -26,4 +26,4 @@ - \ No newline at end of file + diff --git a/log4j-core-test/src/test/resources/EventParameterMemoryLeakTest.xml b/log4j-core-test/src/test/resources/EventParameterMemoryLeakTest.xml new file mode 100644 index 00000000000..6d3e71dc6a4 --- /dev/null +++ b/log4j-core-test/src/test/resources/EventParameterMemoryLeakTest.xml @@ -0,0 +1,36 @@ + + + + + + + + %d %p %c{1.} [%t] %m %throwable{short.message}%n + + + + + + + + + diff --git a/log4j-core/src/test/resources/FlumeFuncTest.xml b/log4j-core-test/src/test/resources/FlumeFuncTest.xml similarity index 100% rename from log4j-core/src/test/resources/FlumeFuncTest.xml rename to log4j-core-test/src/test/resources/FlumeFuncTest.xml diff --git a/log4j-core/src/test/resources/GelfLayoutTest2.xml b/log4j-core-test/src/test/resources/GelfLayout2Test.xml similarity index 100% rename from log4j-core/src/test/resources/GelfLayoutTest2.xml rename to log4j-core-test/src/test/resources/GelfLayout2Test.xml diff --git a/log4j-core-test/src/test/resources/GelfLayout3Test.xml b/log4j-core-test/src/test/resources/GelfLayout3Test.xml new file mode 100644 index 00000000000..90e7b032cae --- /dev/null +++ b/log4j-core-test/src/test/resources/GelfLayout3Test.xml @@ -0,0 +1,36 @@ + + + + + + + %d [%t] %-5p %X{requestId, loginId} %C{1.}.%M:%L - %m%n" + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/GelfLayoutPatternSelectorTest.xml b/log4j-core-test/src/test/resources/GelfLayoutPatternSelectorTest.xml new file mode 100644 index 00000000000..3fc0f744a34 --- /dev/null +++ b/log4j-core-test/src/test/resources/GelfLayoutPatternSelectorTest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/HttpAppenderTest.xml b/log4j-core-test/src/test/resources/HttpAppenderTest.xml similarity index 95% rename from log4j-core/src/test/resources/HttpAppenderTest.xml rename to log4j-core-test/src/test/resources/HttpAppenderTest.xml index 4521ed67f1c..1772bbef8e3 100644 --- a/log4j-core/src/test/resources/HttpAppenderTest.xml +++ b/log4j-core-test/src/test/resources/HttpAppenderTest.xml @@ -15,7 +15,7 @@ ~ See the license for the specific language governing permissions and ~ limitations under the license. --> - + diff --git a/log4j-core/src/test/resources/JsonCompleteFileAppenderTest.xml b/log4j-core-test/src/test/resources/JsonCompleteFileAppenderTest.xml similarity index 100% rename from log4j-core/src/test/resources/JsonCompleteFileAppenderTest.xml rename to log4j-core-test/src/test/resources/JsonCompleteFileAppenderTest.xml diff --git a/log4j-core-test/src/test/resources/LOG4J-2195/log4j2.xml b/log4j-core-test/src/test/resources/LOG4J-2195/log4j2.xml new file mode 100644 index 00000000000..f1f1ea94361 --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J-2195/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/LOG4J2-1100/log4j2-bad.yaml b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-bad.yaml similarity index 93% rename from log4j-core/src/test/resources/LOG4J2-1100/log4j2-bad.yaml rename to log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-bad.yaml index 7bec043637d..909311e3f20 100644 --- a/log4j-core/src/test/resources/LOG4J2-1100/log4j2-bad.yaml +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-bad.yaml @@ -1,30 +1,29 @@ -Configuration: - status: trace - - Appenders: - Console: - - name: Console - target: SYSTEM_OUT - PatternLayout: - Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" - RollingFile: - - name: File - fileName: "${sys:user.home}/.btat/btat.log" - filePattern: "${sys:user.home}/.btat/logs/btat-%d{yyyy-MM}-%i.log.gz" - PatternLayout: - Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" - Policies: - - TimeBasedTriggeringPolicy: - interval: 7 - - SizeBasedTriggeringPolicy: - size: 100 MB - DefaultRolloverStrategy: - max: 20 - - Loggers: - Root: - level: info - AppenderRef: - - ref: Console - - ref: File - \ No newline at end of file +Configuration: + status: 'off' + + Appenders: + Console: + - name: Console + target: SYSTEM_OUT + PatternLayout: + Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + RollingFile: + - name: File + fileName: "${sys:user.home}/.btat/btat.log" + filePattern: "${sys:user.home}/.btat/logs/btat-%d{yyyy-MM}-%i.log.gz" + PatternLayout: + Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + Policies: + - TimeBasedTriggeringPolicy: + interval: 7 + - SizeBasedTriggeringPolicy: + size: 100 MB + DefaultRolloverStrategy: + max: 20 + + Loggers: + Root: + level: info + AppenderRef: + - ref: Console + - ref: File diff --git a/log4j-core/src/test/resources/LOG4J2-1100/log4j2-good.yaml b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-good.yaml similarity index 93% rename from log4j-core/src/test/resources/LOG4J2-1100/log4j2-good.yaml rename to log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-good.yaml index dc555e0996b..1397e9d1d77 100644 --- a/log4j-core/src/test/resources/LOG4J2-1100/log4j2-good.yaml +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2-good.yaml @@ -1,30 +1,29 @@ -Configuration: - status: warn - - Appenders: - Console: - - name: Console - target: SYSTEM_OUT - PatternLayout: - Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" - RollingFile: - - name: File - fileName: "${sys:user.home}/.btat/btat.log" - filePattern: "${sys:user.home}/.btat/logs/btat-%d{yyyy-MM}-%i.log.gz" - PatternLayout: - Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" - Policies: - TimeBasedTriggeringPolicy: - interval: 7 - SizeBasedTriggeringPolicy: - size: 100 MB - DefaultRolloverStrategy: - max: 20 - - Loggers: - Root: - level: info - AppenderRef: - - ref: Console - - ref: File - \ No newline at end of file +Configuration: + status: 'off' + + Appenders: + Console: + - name: Console + target: SYSTEM_OUT + PatternLayout: + Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + RollingFile: + - name: File + fileName: "${sys:user.home}/.btat/btat.log" + filePattern: "${sys:user.home}/.btat/logs/btat-%d{yyyy-MM}-%i.log.gz" + PatternLayout: + Pattern: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + Policies: + TimeBasedTriggeringPolicy: + interval: 7 + SizeBasedTriggeringPolicy: + size: 100 MB + DefaultRolloverStrategy: + max: 20 + + Loggers: + Root: + level: info + AppenderRef: + - ref: Console + - ref: File diff --git a/log4j-core/src/test/resources/LOG4J2-1100/log4j2.json b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.json similarity index 93% rename from log4j-core/src/test/resources/LOG4J2-1100/log4j2.json rename to log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.json index d265cfb0337..fde8a14f958 100644 --- a/log4j-core/src/test/resources/LOG4J2-1100/log4j2.json +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.json @@ -1,42 +1,42 @@ -{ - "configuration": { - "status": "warn", - "appenders": { - "Console": { - "name": "Console", - "target": "SYSTEM_OUT", - "PatternLayout": { - "pattern": "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" - } - }, - "RollingFile": { - "name": "File", - "fileName": "${sys:user.home}/.btat/btat.log", - "filePattern": "${sys:user.home}/.btat/logs/btat-%d{yyyy-MM}-%i.log.gz", - "PatternLayout": { - "pattern": "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" - }, - "Policies": { - "TimeBasedTriggeringPolicy": { - "interval": "7" - }, - "SizeBasedTriggeringPolicy": { - "size": "100 MB" - } - }, - "DefaultRolloverStrategy": { - "max": "20" - } - } - }, - "loggers": { - "Root": { - "level": "info", - "AppenderRef": [ - { "ref": "Console" }, - { "ref": "File" } - ] - } - } - } -} \ No newline at end of file +{ + "configuration": { + "status": "off", + "appenders": { + "Console": { + "name": "Console", + "target": "SYSTEM_OUT", + "PatternLayout": { + "pattern": "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + } + }, + "RollingFile": { + "name": "File", + "fileName": "${sys:user.home}/.btat/btat.log", + "filePattern": "${sys:user.home}/.btat/logs/btat-%d{yyyy-MM}-%i.log.gz", + "PatternLayout": { + "pattern": "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" + }, + "Policies": { + "TimeBasedTriggeringPolicy": { + "interval": "7" + }, + "SizeBasedTriggeringPolicy": { + "size": "100 MB" + } + }, + "DefaultRolloverStrategy": { + "max": "20" + } + } + }, + "loggers": { + "Root": { + "level": "info", + "AppenderRef": [ + { "ref": "Console" }, + { "ref": "File" } + ] + } + } + } +} diff --git a/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.xml b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.xml new file mode 100644 index 00000000000..41e1c1679eb --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-1100/log4j2.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/LOG4J2-739.xml b/log4j-core-test/src/test/resources/LOG4J2-739.xml new file mode 100644 index 00000000000..a9641dbdcd0 --- /dev/null +++ b/log4j-core-test/src/test/resources/LOG4J2-739.xml @@ -0,0 +1,45 @@ + + + + + + app + %d{yyyy-MM-dd HH:mm:ss.SSS} | %-5.5p | %-10.10t | %-20.20C:%-5.5L | %msg%n + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/LOG4J2-807.xml b/log4j-core-test/src/test/resources/LOG4J2-807.xml similarity index 98% rename from log4j-core/src/test/resources/LOG4J2-807.xml rename to log4j-core-test/src/test/resources/LOG4J2-807.xml index bb2249d2162..4a8f583a761 100644 --- a/log4j-core/src/test/resources/LOG4J2-807.xml +++ b/log4j-core-test/src/test/resources/LOG4J2-807.xml @@ -16,7 +16,7 @@ ~ limitations under the license. --> - + @@ -29,4 +29,4 @@ - \ No newline at end of file + diff --git a/log4j-core-test/src/test/resources/LoggerLevelAppenderTest.properties b/log4j-core-test/src/test/resources/LoggerLevelAppenderTest.properties new file mode 100644 index 00000000000..231bb98a887 --- /dev/null +++ b/log4j-core-test/src/test/resources/LoggerLevelAppenderTest.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +# + +status = off +name = LoggerLevelAppenderTest +logger.core-config-properties.name = org.apache.logging.log4j.core.config.properties +# whitespace added for testing trimming +logger.core-config-properties = INFO , first , second +appender.first.type = List +appender.second.type = List diff --git a/log4j-core-test/src/test/resources/LoggerLevelSysPropsAppenderTest.properties b/log4j-core-test/src/test/resources/LoggerLevelSysPropsAppenderTest.properties new file mode 100644 index 00000000000..ffa5c353df6 --- /dev/null +++ b/log4j-core-test/src/test/resources/LoggerLevelSysPropsAppenderTest.properties @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +# + +status = off +name = LoggerLevelAppenderTest +logger.core-config-properties.name = org.apache.logging.log4j.core.config.properties +# whitespace added for testing trimming +logger.core-config-properties = ${sys:coreProps:-ERROR , first} +logger.core-config-properties.additivity = false +appender.first.name = first +appender.first.type = List +appender.second.name = second +appender.second.type = List +appender.third.name = third +appender.third.type = List +rootLogger = INFO, third diff --git a/log4j-core-test/src/test/resources/META-INF/LICENSE b/log4j-core-test/src/test/resources/META-INF/LICENSE new file mode 100644 index 00000000000..6279e5206de --- /dev/null +++ b/log4j-core-test/src/test/resources/META-INF/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 1999-2005 The Apache Software Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/log4j-core/src/test/resources/META-INF/NOTICE b/log4j-core-test/src/test/resources/META-INF/NOTICE similarity index 100% rename from log4j-core/src/test/resources/META-INF/NOTICE rename to log4j-core-test/src/test/resources/META-INF/NOTICE diff --git a/log4j-core/src/test/resources/MemoryMappedFileAppenderLocationTest.xml b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderLocationTest.xml similarity index 84% rename from log4j-core/src/test/resources/MemoryMappedFileAppenderLocationTest.xml rename to log4j-core-test/src/test/resources/MemoryMappedFileAppenderLocationTest.xml index 14b6b25079a..b192995f4ad 100644 --- a/log4j-core/src/test/resources/MemoryMappedFileAppenderLocationTest.xml +++ b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderLocationTest.xml @@ -5,7 +5,7 @@ fileName="target/MemoryMappedFileAppenderLocationTest.log" regionLength="32000" immediateFlush="true" append="false"> - %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + %location: %m%n @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/MemoryMappedFileAppenderRemapTest.xml b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderRemapTest.xml similarity index 85% rename from log4j-core/src/test/resources/MemoryMappedFileAppenderRemapTest.xml rename to log4j-core-test/src/test/resources/MemoryMappedFileAppenderRemapTest.xml index 02799a5a23b..d46b9028315 100644 --- a/log4j-core/src/test/resources/MemoryMappedFileAppenderRemapTest.xml +++ b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderRemapTest.xml @@ -5,7 +5,7 @@ fileName="target/MemoryMappedFileAppenderRemapTest.log" regionLength="256" immediateFlush="true" append="false"> - %d %p %c{1.} [%t] %X{aKey} %m%ex%n + %m%n @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/MemoryMappedFileAppenderTest.xml b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderTest.xml similarity index 85% rename from log4j-core/src/test/resources/MemoryMappedFileAppenderTest.xml rename to log4j-core-test/src/test/resources/MemoryMappedFileAppenderTest.xml index 430c8b1a297..209459de8ad 100644 --- a/log4j-core/src/test/resources/MemoryMappedFileAppenderTest.xml +++ b/log4j-core-test/src/test/resources/MemoryMappedFileAppenderTest.xml @@ -5,7 +5,7 @@ fileName="target/MemoryMappedFileAppenderTest.log" immediateFlush="false" append="false"> - %d %p %c{1.} [%t] %X{aKey} %m%ex%n + %m%n @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/log4j-core-test/src/test/resources/NanoTimeToFileTest.xml b/log4j-core-test/src/test/resources/NanoTimeToFileTest.xml new file mode 100644 index 00000000000..f38bdc83a4d --- /dev/null +++ b/log4j-core-test/src/test/resources/NanoTimeToFileTest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/PatternLayoutRepeat.xml b/log4j-core-test/src/test/resources/PatternLayoutRepeat.xml new file mode 100644 index 00000000000..d4e5ee53e93 --- /dev/null +++ b/log4j-core-test/src/test/resources/PatternLayoutRepeat.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/README.md b/log4j-core-test/src/test/resources/README.md new file mode 100644 index 00000000000..a5f5e891bd2 --- /dev/null +++ b/log4j-core-test/src/test/resources/README.md @@ -0,0 +1,87 @@ +This directory is mainly for storing configuration files used in various unit tests. As such, there are a few things +provided for your convenience in writing and maintaining unit tests. + +Running Unit Tests +------------------ + +First, make sure you've recently run `mvn clean install` in `log4j-api` to get an up to date dependency. Then you can +run `mvn test` to run unit tests, or run `mvn verify` to run both unit tests and integration/performance tests. + +Test Plugins +------------ + +Included are various Log4j plugins that are only useful for writing tests. These test plugins are automatically made +available to test classes. For instance, to use the ListAppender: + + + + + + + + + + + + +Note that if you don't specify a layout for a ListAppender, your log messages will be stored in a list of LogEvents. +With a layout, your log messages will be stored in a list of strings. For more details, check out the class +`org.apache.logging.log4j.test.appender.ListAppender`. + +Specifying Configuration Files in JUnit Tests +--------------------------------------------- + +Added in JUnit 4.9, the concept of test fixtures (i.e., the `@Before`, `@After`, etc. methods) has been expanded into +the concept of test rules. A test rule is a reusable test fixture such as the `TemporaryFolder` JUnit rule for creating +temporary directories and files on a test-by-test (or suite) basis. To use a test rule, you need to use the +`@Rule` or `@ClassRule` annotation to get a method-level or class-level test fixture respectively. For instance, +suppose your test class uses the file named `MyTestConfig.xml` in this directory. Then you can use the following rule +in your test class: + + @Rule + public InitialLoggerContext init = new InitialLoggerContext("MyTestConfig.xml"); + + @Test + public void testSomeAwesomeFeature() { + final LoggerContext ctx = init.getContext(); + final Logger logger = init.getLogger("org.apache.logging.log4j.my.awesome.test.logger"); + final Configuration cfg = init.getConfiguration(); + final ListAppender app = init.getListAppender("List"); + logger.warn("Test message"); + final List events = app.getEvents(); + // etc. + } + +Using this rule will automatically create a new LoggerContext using the specified configuration file for you to +retrieve via the `getContext()` method shown above. After the method finishes (or if you use `@ClassRule` and make +the field `static`), the `LoggerContext` is automatically stopped. No longer do you need to set any system properties, +reset the `StatusLogger` configuration, and all that other fun boilerplate code. + +Cleaning Up Test Log Files +-------------------------- + +The `CleanFiles` rule is also available to automatically delete a list of files after every test. + + @Rule + public CleanFiles files = new CleanFiles("target/file1.log", "target/file2.log", "more files"); + +You can specify either a list of strings or a list of `File`s. + +If you have any questions about writing unit tests, feel free to send an email to the dev mailing list, or check out +the JUnit documentation over at junit.org. + +Specifying Test Categories +-------------------------- + +If your test is something more than just a unit test, it's usually a good idea to add a JUnit category to it. This +can be done at the class or method level: + + @Category(PerformanceTests.class) + @Test + public void testRandomAccessFileLogging() { + // ... + } + +Various pre-defined namespaces are defined in `org.apache.logging.log4j.namespaces` in `log4j-core` test. +If you only want to run your test as part of the `maven-failsafe-plugin` integration tests phase, then simply name +your test `FooIT` instead of `FooTest` for automatic configuration. diff --git a/log4j-core/src/test/resources/RandomAccessFileAppenderLocationTest.xml b/log4j-core-test/src/test/resources/RandomAccessFileAppenderLocationTest.xml similarity index 100% rename from log4j-core/src/test/resources/RandomAccessFileAppenderLocationTest.xml rename to log4j-core-test/src/test/resources/RandomAccessFileAppenderLocationTest.xml diff --git a/log4j-core/src/test/resources/RandomAccessFileAppenderTest.xml b/log4j-core-test/src/test/resources/RandomAccessFileAppenderTest.xml similarity index 100% rename from log4j-core/src/test/resources/RandomAccessFileAppenderTest.xml rename to log4j-core-test/src/test/resources/RandomAccessFileAppenderTest.xml diff --git a/log4j-core-test/src/test/resources/ReliabilityStrategyTest.xml b/log4j-core-test/src/test/resources/ReliabilityStrategyTest.xml new file mode 100644 index 00000000000..a13ea9b7fc1 --- /dev/null +++ b/log4j-core-test/src/test/resources/ReliabilityStrategyTest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/RollingRandomAccessFileAppenderHeaderFooterTest.xml b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderHeaderFooterTest.xml similarity index 100% rename from log4j-core/src/test/resources/RollingRandomAccessFileAppenderHeaderFooterTest.xml rename to log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderHeaderFooterTest.xml diff --git a/log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties similarity index 80% rename from log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties rename to log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties index 295031e2c85..104a0dee087 100644 --- a/log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties +++ b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationPropsTest.properties @@ -1,19 +1,21 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with +# contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 +# The ASF licenses this file to You under the Apache license, Version 2.0 # (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# See the license for the specific language governing permissions and +# limitations under the license. +# -status = error +status = off name = PropertiesConfigTest appenders = rolling diff --git a/log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationTest.xml b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationTest.xml similarity index 100% rename from log4j-core/src/test/resources/RollingRandomAccessFileAppenderLocationTest.xml rename to log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderLocationTest.xml diff --git a/log4j-core/src/test/resources/RollingRandomAccessFileAppenderTest.xml b/log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderTest.xml similarity index 100% rename from log4j-core/src/test/resources/RollingRandomAccessFileAppenderTest.xml rename to log4j-core-test/src/test/resources/RollingRandomAccessFileAppenderTest.xml diff --git a/log4j-core-test/src/test/resources/RootLoggerLevelAppenderTest.properties b/log4j-core-test/src/test/resources/RootLoggerLevelAppenderTest.properties new file mode 100644 index 00000000000..3e08203581c --- /dev/null +++ b/log4j-core-test/src/test/resources/RootLoggerLevelAppenderTest.properties @@ -0,0 +1,21 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +# + +status = off +name = RootLoggerLevelAppenderTest +rootLogger = INFO,app +appender.app.type = List diff --git a/log4j-core/src/test/resources/SequenceNumberPatternConverterTest.yml b/log4j-core-test/src/test/resources/SequenceNumberPatternConverterTest.yml similarity index 100% rename from log4j-core/src/test/resources/SequenceNumberPatternConverterTest.yml rename to log4j-core-test/src/test/resources/SequenceNumberPatternConverterTest.yml diff --git a/log4j-core-test/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yml b/log4j-core-test/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yml new file mode 100644 index 00000000000..3dea4d7a6d4 --- /dev/null +++ b/log4j-core-test/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yml @@ -0,0 +1,15 @@ +Configuration: + status: OFF + name: SequenceNumberPatternConverterTest + + Appenders: + List: + - name: Padded + PatternLayout: + pattern: '%03sn' + + Loggers: + Root: + level: INFO + AppenderRef: + - ref: Padded diff --git a/log4j-core/src/test/resources/ShutdownCallbackRegistryTest.xml b/log4j-core-test/src/test/resources/ShutdownCallbackRegistryTest.xml similarity index 100% rename from log4j-core/src/test/resources/ShutdownCallbackRegistryTest.xml rename to log4j-core-test/src/test/resources/ShutdownCallbackRegistryTest.xml diff --git a/log4j-core-test/src/test/resources/SmtpAppenderAsyncTest.xml b/log4j-core-test/src/test/resources/SmtpAppenderAsyncTest.xml new file mode 100644 index 00000000000..eaf96a25120 --- /dev/null +++ b/log4j-core-test/src/test/resources/SmtpAppenderAsyncTest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/XmlCompactFileAppenderTest.xml b/log4j-core-test/src/test/resources/XmlCompactFileAppenderTest.xml similarity index 100% rename from log4j-core/src/test/resources/XmlCompactFileAppenderTest.xml rename to log4j-core-test/src/test/resources/XmlCompactFileAppenderTest.xml diff --git a/log4j-core/src/test/resources/XmlCompactFileAppenderValidationTest.xml b/log4j-core-test/src/test/resources/XmlCompactFileAppenderValidationTest.xml similarity index 100% rename from log4j-core/src/test/resources/XmlCompactFileAppenderValidationTest.xml rename to log4j-core-test/src/test/resources/XmlCompactFileAppenderValidationTest.xml diff --git a/log4j-core/src/test/resources/XmlCompactFileAsyncAppenderValidationTest.xml b/log4j-core-test/src/test/resources/XmlCompactFileAsyncAppenderValidationTest.xml similarity index 100% rename from log4j-core/src/test/resources/XmlCompactFileAsyncAppenderValidationTest.xml rename to log4j-core-test/src/test/resources/XmlCompactFileAsyncAppenderValidationTest.xml diff --git a/log4j-core/src/test/resources/XmlCompleteFileAppenderTest.xml b/log4j-core-test/src/test/resources/XmlCompleteFileAppenderTest.xml similarity index 100% rename from log4j-core/src/test/resources/XmlCompleteFileAppenderTest.xml rename to log4j-core-test/src/test/resources/XmlCompleteFileAppenderTest.xml diff --git a/log4j-core/src/test/resources/XmlConfigurationSecurity.xml b/log4j-core-test/src/test/resources/XmlConfigurationSecurity.xml similarity index 100% rename from log4j-core/src/test/resources/XmlConfigurationSecurity.xml rename to log4j-core-test/src/test/resources/XmlConfigurationSecurity.xml diff --git a/log4j-core/src/test/resources/XmlFileAppenderTest.xml b/log4j-core-test/src/test/resources/XmlFileAppenderTest.xml similarity index 100% rename from log4j-core/src/test/resources/XmlFileAppenderTest.xml rename to log4j-core-test/src/test/resources/XmlFileAppenderTest.xml diff --git a/log4j-core/src/test/resources/XmlRandomAccessFileAppenderTest.xml b/log4j-core-test/src/test/resources/XmlRandomAccessFileAppenderTest.xml similarity index 100% rename from log4j-core/src/test/resources/XmlRandomAccessFileAppenderTest.xml rename to log4j-core-test/src/test/resources/XmlRandomAccessFileAppenderTest.xml diff --git a/log4j-core/src/test/resources/log4j-test1.xml b/log4j-core-test/src/test/resources/__files/log4j-test1.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-test1.xml rename to log4j-core-test/src/test/resources/__files/log4j-test1.xml diff --git a/log4j-core-test/src/test/resources/__files/onStartup.log b/log4j-core-test/src/test/resources/__files/onStartup.log new file mode 100644 index 00000000000..677501056bb --- /dev/null +++ b/log4j-core-test/src/test/resources/__files/onStartup.log @@ -0,0 +1,3 @@ +This is test message number 1 +This is test message number 2 +This is test message number 3 \ No newline at end of file diff --git a/log4j-core/src/test/resources/bad/log4j-badfilename.xml b/log4j-core-test/src/test/resources/bad/log4j-badfilename.xml similarity index 100% rename from log4j-core/src/test/resources/bad/log4j-badfilename.xml rename to log4j-core-test/src/test/resources/bad/log4j-badfilename.xml diff --git a/log4j-core/src/test/resources/bad/log4j-badfilterparam.xml b/log4j-core-test/src/test/resources/bad/log4j-badfilterparam.xml similarity index 100% rename from log4j-core/src/test/resources/bad/log4j-badfilterparam.xml rename to log4j-core-test/src/test/resources/bad/log4j-badfilterparam.xml diff --git a/log4j-core/src/test/resources/bad/log4j-badlayout.xml b/log4j-core-test/src/test/resources/bad/log4j-badlayout.xml similarity index 100% rename from log4j-core/src/test/resources/bad/log4j-badlayout.xml rename to log4j-core-test/src/test/resources/bad/log4j-badlayout.xml diff --git a/log4j-core/src/test/resources/bad/log4j-loggers.xml b/log4j-core-test/src/test/resources/bad/log4j-loggers.xml similarity index 100% rename from log4j-core/src/test/resources/bad/log4j-loggers.xml rename to log4j-core-test/src/test/resources/bad/log4j-loggers.xml diff --git a/log4j-core/src/test/resources/bad/log4j-nofilter.xml b/log4j-core-test/src/test/resources/bad/log4j-nofilter.xml similarity index 100% rename from log4j-core/src/test/resources/bad/log4j-nofilter.xml rename to log4j-core-test/src/test/resources/bad/log4j-nofilter.xml diff --git a/log4j-core/src/test/resources/bad/log4j-status.xml b/log4j-core-test/src/test/resources/bad/log4j-status.xml similarity index 100% rename from log4j-core/src/test/resources/bad/log4j-status.xml rename to log4j-core-test/src/test/resources/bad/log4j-status.xml diff --git a/log4j-core/src/test/resources/configPropertyTest.xml b/log4j-core-test/src/test/resources/configPropertyTest.xml similarity index 88% rename from log4j-core/src/test/resources/configPropertyTest.xml rename to log4j-core-test/src/test/resources/configPropertyTest.xml index 261f7cf9c2f..160ca33a3cb 100644 --- a/log4j-core/src/test/resources/configPropertyTest.xml +++ b/log4j-core-test/src/test/resources/configPropertyTest.xml @@ -3,7 +3,7 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. - ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ The ASF licenses this file to You under the Apache license, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ @@ -12,10 +12,10 @@ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. + ~ See the license for the specific language governing permissions and + ~ limitations under the license. --> - + @@ -35,4 +35,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/customplugin/FixedStringLayout.java.source b/log4j-core-test/src/test/resources/customplugin/FixedStringLayout.java.source similarity index 82% rename from log4j-core/src/test/resources/customplugin/FixedStringLayout.java.source rename to log4j-core-test/src/test/resources/customplugin/FixedStringLayout.java.source index e3049c17079..207c2bf160f 100644 --- a/log4j-core/src/test/resources/customplugin/FixedStringLayout.java.source +++ b/log4j-core-test/src/test/resources/customplugin/FixedStringLayout.java.source @@ -1,54 +1,56 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package customplugin; - -import java.util.Collections; -import java.util.Map; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.layout.AbstractStringLayout; - -@Plugin(name = "FixedString", category = "Core", elementType = "layout", printObject = true) -public class FixedStringLayout extends AbstractStringLayout { - - private String fixedString; - - @PluginFactory - public static FixedStringLayout createLayout( - @PluginAttribute("fixedString") final String fixedString) { - return new FixedStringLayout(fixedString); - } - - public FixedStringLayout(String fixedString) { - super(null, null, null); - this.fixedString = fixedString; - } - - @Override - public String toSerializable(LogEvent event) { - return fixedString; - } - - @Override - public Map getContentFormat() { - return Collections.emptyMap(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package customplugin; + +import java.util.Collections; +import java.util.Map; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.AbstractStringLayout; + +@Configurable(elementType = "layout", printObject = true) +@Plugin("FixedString") +public class FixedStringLayout extends AbstractStringLayout { + + private String fixedString; + + @PluginFactory + public static FixedStringLayout createLayout( + @PluginAttribute("fixedString") final String fixedString) { + return new FixedStringLayout(fixedString); + } + + public FixedStringLayout(String fixedString) { + super(null, null, null); + this.fixedString = fixedString; + } + + @Override + public String toSerializable(LogEvent event) { + return fixedString; + } + + @Override + public Map getContentFormat() { + return Collections.emptyMap(); + } +} diff --git a/log4j-core-test/src/test/resources/emptyConfig.json b/log4j-core-test/src/test/resources/emptyConfig.json new file mode 100644 index 00000000000..37086f2b1f6 --- /dev/null +++ b/log4j-core-test/src/test/resources/emptyConfig.json @@ -0,0 +1,4 @@ +{ + "configs": { + } +} \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/filterConfig.json b/log4j-core-test/src/test/resources/filterConfig.json new file mode 100644 index 00000000000..91c8143ec2b --- /dev/null +++ b/log4j-core-test/src/test/resources/filterConfig.json @@ -0,0 +1,6 @@ +{ + "configs": { + "loginId": ["rgoers", "adam"], + "corpAcctNumber": ["30510263"] + } +} \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/jarfile.jar b/log4j-core-test/src/test/resources/jarfile.jar new file mode 100644 index 00000000000..bf34b8c4682 Binary files /dev/null and b/log4j-core-test/src/test/resources/jarfile.jar differ diff --git a/log4j-core-test/src/test/resources/jarfile/config/console.xml b/log4j-core-test/src/test/resources/jarfile/config/console.xml new file mode 100644 index 00000000000..015ac0b95ec --- /dev/null +++ b/log4j-core-test/src/test/resources/jarfile/config/console.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/legacy-plugins.xml b/log4j-core-test/src/test/resources/legacy-plugins.xml new file mode 100644 index 00000000000..7bd39057657 --- /dev/null +++ b/log4j-core-test/src/test/resources/legacy-plugins.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j+config+with+plus+characters.xml b/log4j-core-test/src/test/resources/log4j+config+with+plus+characters.xml similarity index 100% rename from log4j-core/src/test/resources/log4j+config+with+plus+characters.xml rename to log4j-core-test/src/test/resources/log4j+config+with+plus+characters.xml diff --git a/log4j-core/src/test/resources/log4j-Level.xml b/log4j-core-test/src/test/resources/log4j-Level.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-Level.xml rename to log4j-core-test/src/test/resources/log4j-Level.xml diff --git a/log4j-core/src/test/resources/log4j-advertiser.xml b/log4j-core-test/src/test/resources/log4j-advertiser.xml similarity index 94% rename from log4j-core/src/test/resources/log4j-advertiser.xml rename to log4j-core-test/src/test/resources/log4j-advertiser.xml index 5bb9e3d7176..7e6796900ae 100644 --- a/log4j-core/src/test/resources/log4j-advertiser.xml +++ b/log4j-core-test/src/test/resources/log4j-advertiser.xml @@ -16,8 +16,7 @@ limitations under the License. --> - + diff --git a/log4j-core/src/test/resources/log4j-asynch-no-location.xml b/log4j-core-test/src/test/resources/log4j-asynch-no-location.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-asynch-no-location.xml rename to log4j-core-test/src/test/resources/log4j-asynch-no-location.xml diff --git a/log4j-core/src/test/resources/log4j-asynch-queue-full.xml b/log4j-core-test/src/test/resources/log4j-asynch-queue-full.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-asynch-queue-full.xml rename to log4j-core-test/src/test/resources/log4j-asynch-queue-full.xml diff --git a/log4j-core/src/test/resources/log4j-asynch-shutdownTimeout.xml b/log4j-core-test/src/test/resources/log4j-asynch-shutdownTimeout.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-asynch-shutdownTimeout.xml rename to log4j-core-test/src/test/resources/log4j-asynch-shutdownTimeout.xml diff --git a/log4j-core/src/test/resources/log4j-asynch.xml b/log4j-core-test/src/test/resources/log4j-asynch.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-asynch.xml rename to log4j-core-test/src/test/resources/log4j-asynch.xml diff --git a/log4j-core/src/test/resources/log4j-burst.xml b/log4j-core-test/src/test/resources/log4j-burst.xml similarity index 97% rename from log4j-core/src/test/resources/log4j-burst.xml rename to log4j-core-test/src/test/resources/log4j-burst.xml index 205a579781b..7ecb310ec15 100644 --- a/log4j-core/src/test/resources/log4j-burst.xml +++ b/log4j-core-test/src/test/resources/log4j-burst.xml @@ -1,32 +1,32 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core/src/test/resources/log4j-collectionLogging.xml b/log4j-core-test/src/test/resources/log4j-collectionLogging.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-collectionLogging.xml rename to log4j-core-test/src/test/resources/log4j-collectionLogging.xml diff --git a/log4j-core/src/test/resources/log4j-comp-appender.json b/log4j-core-test/src/test/resources/log4j-comp-appender.json similarity index 97% rename from log4j-core/src/test/resources/log4j-comp-appender.json rename to log4j-core-test/src/test/resources/log4j-comp-appender.json index 1f0b02d2c72..134640657a8 100644 --- a/log4j-core/src/test/resources/log4j-comp-appender.json +++ b/log4j-core-test/src/test/resources/log4j-comp-appender.json @@ -1,6 +1,6 @@ { "Configuration" : { - "status": "warn", + "status": "off", "name": "YAMLConfigTest", "appenders": { "Console": { diff --git a/log4j-core-test/src/test/resources/log4j-comp-appender.xml b/log4j-core-test/src/test/resources/log4j-comp-appender.xml new file mode 100644 index 00000000000..2e66dd3b2b5 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-appender.xml @@ -0,0 +1,38 @@ + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-comp-filter.json b/log4j-core-test/src/test/resources/log4j-comp-filter.json similarity index 84% rename from log4j-core/src/test/resources/log4j-comp-filter.json rename to log4j-core-test/src/test/resources/log4j-comp-filter.json index c0102986255..0dd3b9df6db 100644 --- a/log4j-core/src/test/resources/log4j-comp-filter.json +++ b/log4j-core-test/src/test/resources/log4j-comp-filter.json @@ -1,6 +1,6 @@ { "Configuration" : { - "status": "warn", + "status": "off", "name": "YAMLConfigTest", "thresholdFilter" : { "level": "debug" diff --git a/log4j-core-test/src/test/resources/log4j-comp-filter.xml b/log4j-core-test/src/test/resources/log4j-comp-filter.xml new file mode 100644 index 00000000000..5632571d358 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-filter.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-comp-logger-attr-override.json b/log4j-core-test/src/test/resources/log4j-comp-logger-attr-override.json similarity index 97% rename from log4j-core/src/test/resources/log4j-comp-logger-attr-override.json rename to log4j-core-test/src/test/resources/log4j-comp-logger-attr-override.json index a6e28aa37e3..5814a1f8b56 100644 --- a/log4j-core/src/test/resources/log4j-comp-logger-attr-override.json +++ b/log4j-core-test/src/test/resources/log4j-comp-logger-attr-override.json @@ -1,6 +1,6 @@ { "Configuration" : { - "status": "error", + "status": "off", "name": "LoggerConfigTest", "properties" : { "property" : [{ diff --git a/log4j-core/src/test/resources/log4j-comp-logger-ref.json b/log4j-core-test/src/test/resources/log4j-comp-logger-ref.json similarity index 95% rename from log4j-core/src/test/resources/log4j-comp-logger-ref.json rename to log4j-core-test/src/test/resources/log4j-comp-logger-ref.json index ab591c8b383..c32805cbd4a 100644 --- a/log4j-core/src/test/resources/log4j-comp-logger-ref.json +++ b/log4j-core-test/src/test/resources/log4j-comp-logger-ref.json @@ -1,18 +1,19 @@ -{ - "Configuration" : { - "name": "LoggerConfigTest", - "Loggers" : { - "logger" : [ - { - "name" : "cat1", - "level" : "debug", - "additivity" : false, - "AppenderRef" : { - "ref" : "STDOUT", - "RegexFilter": {"regex" : "TEST", "onMatch": "deny", "onMismatch": "accept"} - } - } - ] - } - } -} +{ + "Configuration" : { + "name": "LoggerConfigTest", + "status": "off", + "Loggers" : { + "logger" : [ + { + "name" : "cat1", + "level" : "debug", + "additivity" : false, + "AppenderRef" : { + "ref" : "STDOUT", + "RegexFilter": {"regex" : "TEST", "onMatch": "deny", "onMismatch": "accept"} + } + } + ] + } + } +} diff --git a/log4j-core-test/src/test/resources/log4j-comp-logger-ref.xml b/log4j-core-test/src/test/resources/log4j-comp-logger-ref.xml new file mode 100644 index 00000000000..274bf6926ab --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-logger-ref.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-comp-logger-root.xml b/log4j-core-test/src/test/resources/log4j-comp-logger-root.xml new file mode 100644 index 00000000000..8fb67553984 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-logger-root.xml @@ -0,0 +1,43 @@ + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-comp-logger.json b/log4j-core-test/src/test/resources/log4j-comp-logger.json similarity index 97% rename from log4j-core/src/test/resources/log4j-comp-logger.json rename to log4j-core-test/src/test/resources/log4j-comp-logger.json index fc781d3ecb4..a7383199938 100644 --- a/log4j-core/src/test/resources/log4j-comp-logger.json +++ b/log4j-core-test/src/test/resources/log4j-comp-logger.json @@ -1,6 +1,6 @@ { "Configuration" : { - "status": "error", + "status": "off", "name": "LoggerConfigTest", "properties" : { "property" : [{ diff --git a/log4j-core-test/src/test/resources/log4j-comp-logger.xml b/log4j-core-test/src/test/resources/log4j-comp-logger.xml new file mode 100644 index 00000000000..ace88190e39 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-logger.xml @@ -0,0 +1,40 @@ + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-comp-properties.json b/log4j-core-test/src/test/resources/log4j-comp-properties.json similarity index 92% rename from log4j-core/src/test/resources/log4j-comp-properties.json rename to log4j-core-test/src/test/resources/log4j-comp-properties.json index 83c2d090d06..a737d5fc7a4 100644 --- a/log4j-core/src/test/resources/log4j-comp-properties.json +++ b/log4j-core-test/src/test/resources/log4j-comp-properties.json @@ -1,6 +1,6 @@ { "Configuration" : { - "status": "warn", + "status": "off", "name": "YAMLConfigTest", "properties" : { diff --git a/log4j-core/src/test/resources/log4j-comp-properties.xml b/log4j-core-test/src/test/resources/log4j-comp-properties.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-comp-properties.xml rename to log4j-core-test/src/test/resources/log4j-comp-properties.xml diff --git a/log4j-core/src/test/resources/log4j-comp-reconfig.properties b/log4j-core-test/src/test/resources/log4j-comp-reconfig.properties similarity index 99% rename from log4j-core/src/test/resources/log4j-comp-reconfig.properties rename to log4j-core-test/src/test/resources/log4j-comp-reconfig.properties index 94f939a9ce0..cb1436f98f1 100644 --- a/log4j-core/src/test/resources/log4j-comp-reconfig.properties +++ b/log4j-core-test/src/test/resources/log4j-comp-reconfig.properties @@ -15,7 +15,7 @@ # limitations under the license. # -status = error +status = off name = PropertiesConfigTest monitorInterval = 1 diff --git a/log4j-core-test/src/test/resources/log4j-comp-reconfig.xml b/log4j-core-test/src/test/resources/log4j-comp-reconfig.xml new file mode 100644 index 00000000000..c571176343e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-reconfig.xml @@ -0,0 +1,38 @@ + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-comp-root-loggers.xml b/log4j-core-test/src/test/resources/log4j-comp-root-loggers.xml new file mode 100644 index 00000000000..648dfb9e06d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-comp-root-loggers.xml @@ -0,0 +1,45 @@ + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + server1 + + + + diff --git a/log4j-core/src/test/resources/log4j-config.json b/log4j-core-test/src/test/resources/log4j-config.json similarity index 98% rename from log4j-core/src/test/resources/log4j-config.json rename to log4j-core-test/src/test/resources/log4j-config.json index 74d92b58299..2fc54ea8111 100644 --- a/log4j-core/src/test/resources/log4j-config.json +++ b/log4j-core-test/src/test/resources/log4j-config.json @@ -1,6 +1,6 @@ { "configuration": { - "status": "debug" + "status": "off" ,"name": "Test-Logging" ,"monitorInterval":30 ,"packages": "org.apache.logging.log4j.core" @@ -45,4 +45,4 @@ } } } -} \ No newline at end of file +} diff --git a/log4j-core-test/src/test/resources/log4j-console.xml b/log4j-core-test/src/test/resources/log4j-console.xml new file mode 100644 index 00000000000..d9174775911 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-console.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-contextData.xml b/log4j-core-test/src/test/resources/log4j-contextData.xml new file mode 100644 index 00000000000..b7c08ccec1c --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-contextData.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-cronRolloverApp.xml b/log4j-core-test/src/test/resources/log4j-cronRolloverApp.xml new file mode 100644 index 00000000000..82ffbfd963a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-cronRolloverApp.xml @@ -0,0 +1,41 @@ + + + + + + target/testlog4 + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-customLevel.xml b/log4j-core-test/src/test/resources/log4j-customLevel.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-customLevel.xml rename to log4j-core-test/src/test/resources/log4j-customLevel.xml diff --git a/log4j-core-test/src/test/resources/log4j-customLevels.xml b/log4j-core-test/src/test/resources/log4j-customLevels.xml new file mode 100644 index 00000000000..72790f70339 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-customLevels.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-customLevelsOverride.xml b/log4j-core-test/src/test/resources/log4j-customLevelsOverride.xml new file mode 100644 index 00000000000..32c8679ea46 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-customLevelsOverride.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-customLevelsWithFilters.xml b/log4j-core-test/src/test/resources/log4j-customLevelsWithFilters.xml new file mode 100644 index 00000000000..0a1dce07cae --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-customLevelsWithFilters.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-date.xml b/log4j-core-test/src/test/resources/log4j-date.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-date.xml rename to log4j-core-test/src/test/resources/log4j-date.xml diff --git a/log4j-core/src/test/resources/log4j-deadlock.xml b/log4j-core-test/src/test/resources/log4j-deadlock.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-deadlock.xml rename to log4j-core-test/src/test/resources/log4j-deadlock.xml diff --git a/log4j-core-test/src/test/resources/log4j-empty.xml b/log4j-core-test/src/test/resources/log4j-empty.xml new file mode 100644 index 00000000000..bf2fd98cc3e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-empty.xml @@ -0,0 +1,19 @@ + + + + diff --git a/log4j-core/src/test/resources/log4j-failover.xml b/log4j-core-test/src/test/resources/log4j-failover.xml similarity index 98% rename from log4j-core/src/test/resources/log4j-failover.xml rename to log4j-core-test/src/test/resources/log4j-failover.xml index e71cab70c53..461c3929da5 100644 --- a/log4j-core/src/test/resources/log4j-failover.xml +++ b/log4j-core-test/src/test/resources/log4j-failover.xml @@ -42,4 +42,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/log4j-filetest.xml b/log4j-core-test/src/test/resources/log4j-filetest.xml similarity index 97% rename from log4j-core/src/test/resources/log4j-filetest.xml rename to log4j-core-test/src/test/resources/log4j-filetest.xml index de49d0fb473..c376d4cd4f2 100644 --- a/log4j-core/src/test/resources/log4j-filetest.xml +++ b/log4j-core-test/src/test/resources/log4j-filetest.xml @@ -44,15 +44,15 @@ - > + - > + - \ No newline at end of file + diff --git a/log4j-core-test/src/test/resources/log4j-jira965.xml b/log4j-core-test/src/test/resources/log4j-jira965.xml new file mode 100644 index 00000000000..964e4a8641c --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-jira965.xml @@ -0,0 +1,32 @@ + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-list-lookups.xml b/log4j-core-test/src/test/resources/log4j-list-lookups.xml new file mode 100644 index 00000000000..65c9fce39ea --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-list-lookups.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-list-nolookups.xml b/log4j-core-test/src/test/resources/log4j-list-nolookups.xml similarity index 81% rename from log4j-core/src/test/resources/log4j-list-nolookups.xml rename to log4j-core-test/src/test/resources/log4j-list-nolookups.xml index 2477db7c408..a5f8e4dcde0 100644 --- a/log4j-core/src/test/resources/log4j-list-nolookups.xml +++ b/log4j-core-test/src/test/resources/log4j-list-nolookups.xml @@ -3,7 +3,7 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. - ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ The ASF licenses this file to You under the Apache license, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ @@ -12,10 +12,10 @@ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. + ~ See the license for the specific language governing permissions and + ~ limitations under the license. --> - + @@ -26,4 +26,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/log4j-list.xml b/log4j-core-test/src/test/resources/log4j-list.xml similarity index 81% rename from log4j-core/src/test/resources/log4j-list.xml rename to log4j-core-test/src/test/resources/log4j-list.xml index c2a92c3054f..41c2e9f0e32 100644 --- a/log4j-core/src/test/resources/log4j-list.xml +++ b/log4j-core-test/src/test/resources/log4j-list.xml @@ -3,7 +3,7 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. - ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ The ASF licenses this file to You under the Apache license, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ @@ -12,10 +12,10 @@ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. + ~ See the license for the specific language governing permissions and + ~ limitations under the license. --> - + @@ -26,4 +26,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/log4j-loggerprops.xml b/log4j-core-test/src/test/resources/log4j-loggerprops.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-loggerprops.xml rename to log4j-core-test/src/test/resources/log4j-loggerprops.xml diff --git a/log4j-core/src/test/resources/log4j-lookup-main.xml b/log4j-core-test/src/test/resources/log4j-lookup-main.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-lookup-main.xml rename to log4j-core-test/src/test/resources/log4j-lookup-main.xml diff --git a/log4j-core-test/src/test/resources/log4j-lookup.xml b/log4j-core-test/src/test/resources/log4j-lookup.xml new file mode 100644 index 00000000000..2a3dd942100 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-lookup.xml @@ -0,0 +1,33 @@ + + + + + + org.junit,org.apache.maven,org.eclipse,sun.reflect,java.lang.reflect + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-marker-lookup.yaml b/log4j-core-test/src/test/resources/log4j-marker-lookup.yaml similarity index 98% rename from log4j-core/src/test/resources/log4j-marker-lookup.yaml rename to log4j-core-test/src/test/resources/log4j-marker-lookup.yaml index e2e1affa4b9..5d7c62e5868 100644 --- a/log4j-core/src/test/resources/log4j-marker-lookup.yaml +++ b/log4j-core-test/src/test/resources/log4j-marker-lookup.yaml @@ -1,5 +1,5 @@ Configuration: - status: warn + status: 'off' Appenders: Console: diff --git a/log4j-core/src/test/resources/log4j-message-ansi.xml b/log4j-core-test/src/test/resources/log4j-message-ansi.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-message-ansi.xml rename to log4j-core-test/src/test/resources/log4j-message-ansi.xml diff --git a/log4j-core/src/test/resources/log4j-message-styled.xml b/log4j-core-test/src/test/resources/log4j-message-styled.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-message-styled.xml rename to log4j-core-test/src/test/resources/log4j-message-styled.xml diff --git a/log4j-core-test/src/test/resources/log4j-nested-logging-throwable-message.xml b/log4j-core-test/src/test/resources/log4j-nested-logging-throwable-message.xml new file mode 100644 index 00000000000..42ca9e54c03 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-nested-logging-throwable-message.xml @@ -0,0 +1,43 @@ + + + + + + + + %level %logger{1} %m %throwable{short.message}%n + + + + + %level %logger{1} %m %throwable{short.message}%n + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core/src/test/resources/log4j-posix.xml b/log4j-core-test/src/test/resources/log4j-posix.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-posix.xml rename to log4j-core-test/src/test/resources/log4j-posix.xml diff --git a/log4j-core/src/test/resources/log4j-props.xml b/log4j-core-test/src/test/resources/log4j-props.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-props.xml rename to log4j-core-test/src/test/resources/log4j-props.xml diff --git a/log4j-core/src/test/resources/log4j-props1.xml b/log4j-core-test/src/test/resources/log4j-props1.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-props1.xml rename to log4j-core-test/src/test/resources/log4j-props1.xml diff --git a/log4j-core/src/test/resources/log4j-reference-level.json b/log4j-core-test/src/test/resources/log4j-reference-level.json similarity index 96% rename from log4j-core/src/test/resources/log4j-reference-level.json rename to log4j-core-test/src/test/resources/log4j-reference-level.json index 37586c4fe6f..b2226afe2e9 100644 --- a/log4j-core/src/test/resources/log4j-reference-level.json +++ b/log4j-core-test/src/test/resources/log4j-reference-level.json @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -{ "configuration": { "status": "error", "XMLConfigTest": "RoutingTest", +{ "configuration": { "status": "off", "XMLConfigTest": "RoutingTest", "appenders": { "appender": [ { "type": "Console", "name": "STDOUT", "PatternLayout": { "pattern": "%m%n" }}, @@ -35,4 +35,4 @@ "root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }} } } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/resources/log4j-reference-level.xml b/log4j-core-test/src/test/resources/log4j-reference-level.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-reference-level.xml rename to log4j-core-test/src/test/resources/log4j-reference-level.xml diff --git a/log4j-core/src/test/resources/log4j-replace.xml b/log4j-core-test/src/test/resources/log4j-replace.xml similarity index 85% rename from log4j-core/src/test/resources/log4j-replace.xml rename to log4j-core-test/src/test/resources/log4j-replace.xml index 09ff3b19e3a..684cd7e70bf 100644 --- a/log4j-core/src/test/resources/log4j-replace.xml +++ b/log4j-core-test/src/test/resources/log4j-replace.xml @@ -19,14 +19,14 @@ - - - %logger %msg%n + + + %logger %msg{lookups}%n - - %replace{%logger %C{1.} %msg%n}{\.}{/} + + %replace{%logger %C{1.} %msg{lookups}%n}{\.}{/} diff --git a/log4j-core/src/test/resources/log4j-rewrite.xml b/log4j-core-test/src/test/resources/log4j-rewrite.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-rewrite.xml rename to log4j-core-test/src/test/resources/log4j-rewrite.xml diff --git a/log4j-core-test/src/test/resources/log4j-rollOnStartup.xml b/log4j-core-test/src/test/resources/log4j-rollOnStartup.xml new file mode 100644 index 00000000000..551a8f4f396 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rollOnStartup.xml @@ -0,0 +1,37 @@ + + + + + + + %m%n + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rollOnStartupDirect.xml b/log4j-core-test/src/test/resources/log4j-rollOnStartupDirect.xml new file mode 100644 index 00000000000..b087bfd3687 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rollOnStartupDirect.xml @@ -0,0 +1,36 @@ + + + + + + + %m%n + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-3490.xml b/log4j-core-test/src/test/resources/log4j-rolling-3490.xml new file mode 100644 index 00000000000..fbd262745da --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-3490.xml @@ -0,0 +1,19 @@ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core/src/test/resources/log4j-rolling-7z-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-7z-lazy.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-rolling-7z-lazy.xml rename to log4j-core-test/src/test/resources/log4j-rolling-7z-lazy.xml diff --git a/log4j-core/src/test/resources/log4j-rolling-7z.xml b/log4j-core-test/src/test/resources/log4j-rolling-7z.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-rolling-7z.xml rename to log4j-core-test/src/test/resources/log4j-rolling-7z.xml diff --git a/log4j-core-test/src/test/resources/log4j-rolling-bzip2-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-bzip2-lazy.xml new file mode 100644 index 00000000000..489eb38e918 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-bzip2-lazy.xml @@ -0,0 +1,59 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-bzip2.xml b/log4j-core-test/src/test/resources/log4j-rolling-bzip2.xml new file mode 100644 index 00000000000..ac72d49832b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-bzip2.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-count.xml b/log4j-core-test/src/test/resources/log4j-rolling-count.xml new file mode 100644 index 00000000000..c06a14276ea --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-count.xml @@ -0,0 +1,43 @@ + + + + + target/rolling_count/rolling_test.log + %d{HH:mm:ss.SSS} [%t] %-5level %logger{1.} - %msg%n + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-cron-and-size.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron-and-size.xml new file mode 100644 index 00000000000..d9c7b50f77f --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron-and-size.xml @@ -0,0 +1,55 @@ + + + + + target/rolling1/rollingtest.log + target/rolling-cron-size + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-cron-every2-direct.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron-every2-direct.xml new file mode 100644 index 00000000000..32cbf57e301 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron-every2-direct.xml @@ -0,0 +1,53 @@ + + + + + target/rolling-cron-every2Direct + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-cron-every2.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron-every2.xml new file mode 100644 index 00000000000..c9d13518f50 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron-every2.xml @@ -0,0 +1,53 @@ + + + + + target/rolling-cron-every2/rollingtest.log + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-cron-onStartup.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron-onStartup.xml new file mode 100644 index 00000000000..b0875506cf6 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron-onStartup.xml @@ -0,0 +1,46 @@ + + + + + target/rolling-cron-onStartup + /rollingtest.log + + + + + + %d{MMM dd, yyyy HH:mm:ss a} %c %M %p: %m%n + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-cron-once-a-day.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron-once-a-day.xml new file mode 100644 index 00000000000..0d1622a4eed --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron-once-a-day.xml @@ -0,0 +1,46 @@ + + + + + target/rolling-cron-once-a-day/rollingtest.log + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-cron.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron.xml new file mode 100644 index 00000000000..cbd00ad76d0 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron.xml @@ -0,0 +1,53 @@ + + + + + target/rolling-cron/rollingtest.log + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-cron2.xml b/log4j-core-test/src/test/resources/log4j-rolling-cron2.xml new file mode 100644 index 00000000000..907a345fbb5 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-cron2.xml @@ -0,0 +1,53 @@ + + + + + target/rolling-cron/rollingtest.log + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-deflate-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-deflate-lazy.xml new file mode 100644 index 00000000000..0eb23c950ce --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-deflate-lazy.xml @@ -0,0 +1,59 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-deflate.xml b/log4j-core-test/src/test/resources/log4j-rolling-deflate.xml new file mode 100644 index 00000000000..f4fcce4f3bf --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-deflate.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-direct-1906.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct-1906.xml new file mode 100644 index 00000000000..aa7a741727e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct-1906.xml @@ -0,0 +1,37 @@ + + + + + + target/rolling-direct-1906 + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-direct-cron.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct-cron.xml new file mode 100644 index 00000000000..85d528ed0d0 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct-cron.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + %d{MM-dd-yy-HH-mm-ss} %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/log4j-rolling-direct-reconfigure.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct-reconfigure.xml new file mode 100644 index 00000000000..91aabeafc30 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct-reconfigure.xml @@ -0,0 +1,47 @@ + + + + + target/rolling-direct-reconfigure + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-direct-startup-size.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct-startup-size.xml new file mode 100644 index 00000000000..a094ed833bb --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct-startup-size.xml @@ -0,0 +1,35 @@ + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-direct-tmp-compress-folder.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct-tmp-compress-folder.xml new file mode 100644 index 00000000000..9b3e56eb582 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct-tmp-compress-folder.xml @@ -0,0 +1,51 @@ + + + + + target/rolling-direct + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-direct.xml b/log4j-core-test/src/test/resources/log4j-rolling-direct.xml new file mode 100644 index 00000000000..1f60d284ed1 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-direct.xml @@ -0,0 +1,49 @@ + + + + + target/rolling-direct + + + + + + + + + + %d{MM-dd-yy-HH-mm} %p %C{1.} [%t] %m%n + + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-folder-direct.xml b/log4j-core-test/src/test/resources/log4j-rolling-folder-direct.xml new file mode 100644 index 00000000000..4866bc1091c --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-folder-direct.xml @@ -0,0 +1,38 @@ + + + + + target/rolling-folder-direct + + + + + %d} %p %C{1.} [%t] %m%n + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-gz-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-gz-lazy.xml new file mode 100644 index 00000000000..745c8cff74d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-gz-lazy.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-gz-posix.xml b/log4j-core-test/src/test/resources/log4j-rolling-gz-posix.xml new file mode 100644 index 00000000000..120311f7cff --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-gz-posix.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + %m%n + + + + + + %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-gz-tmp-compress.xml b/log4j-core-test/src/test/resources/log4j-rolling-gz-tmp-compress.xml new file mode 100644 index 00000000000..12a44181987 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-gz-tmp-compress.xml @@ -0,0 +1,61 @@ + + + + + target/rolling2/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-gz.xml b/log4j-core-test/src/test/resources/log4j-rolling-gz.xml new file mode 100644 index 00000000000..80b5a927552 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-gz.xml @@ -0,0 +1,57 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-new-directory.xml b/log4j-core-test/src/test/resources/log4j-rolling-new-directory.xml new file mode 100644 index 00000000000..d9c1bee659a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-new-directory.xml @@ -0,0 +1,38 @@ + + + + + target/rolling-new-directory + + + + + + %d{MM-dd-yy-HH-mm-ss} %p %C{1.} [%t] %m%n + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-numbered-gz.xml b/log4j-core-test/src/test/resources/log4j-rolling-numbered-gz.xml new file mode 100644 index 00000000000..1fcc5ebb87e --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-numbered-gz.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rolling0-0.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-pack200-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-pack200-lazy.xml new file mode 100644 index 00000000000..debeefd7f62 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-pack200-lazy.xml @@ -0,0 +1,59 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-pack200.xml b/log4j-core-test/src/test/resources/log4j-rolling-pack200.xml new file mode 100644 index 00000000000..7168996237d --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-pack200.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-random-direct.xml b/log4j-core-test/src/test/resources/log4j-rolling-random-direct.xml new file mode 100644 index 00000000000..c6505a382f8 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-random-direct.xml @@ -0,0 +1,49 @@ + + + + + target/rolling-random-direct + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-reconfigure.xml b/log4j-core-test/src/test/resources/log4j-rolling-reconfigure.xml new file mode 100644 index 00000000000..53af6da4b08 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-reconfigure.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.current + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-restart.xml b/log4j-core-test/src/test/resources/log4j-rolling-restart.xml new file mode 100644 index 00000000000..0f617a395ee --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-restart.xml @@ -0,0 +1,38 @@ + + + + + target/rolling-restart/test.log + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-1.xml b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-1.xml new file mode 100644 index 00000000000..95525ea3702 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-1.xml @@ -0,0 +1,49 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-2.xml b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-2.xml new file mode 100644 index 00000000000..1382dd43808 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-2.xml @@ -0,0 +1,49 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-3.xml b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-3.xml new file mode 100644 index 00000000000..5a77612e2d3 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-3.xml @@ -0,0 +1,49 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-4.xml b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-4.xml new file mode 100644 index 00000000000..e37698c58ef --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-max-width-4.xml @@ -0,0 +1,53 @@ + + + + + target/rolling-max-width/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/log4j-rolling-size-time-new-directory.xml b/log4j-core-test/src/test/resources/log4j-rolling-size-time-new-directory.xml new file mode 100644 index 00000000000..7c7163766b1 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-time-new-directory.xml @@ -0,0 +1,41 @@ + + + + + target/rolling-size-time-new-directory + + + + + + %d{MM-dd-yy-HH-mm-ss} %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-size-with-time.xml b/log4j-core-test/src/test/resources/log4j-rolling-size-with-time.xml new file mode 100644 index 00000000000..73d907090bd --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size-with-time.xml @@ -0,0 +1,42 @@ + + + + + target/rolling-size-test/rolling.log + + + + + + + %m%n + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-size.xml b/log4j-core-test/src/test/resources/log4j-rolling-size.xml new file mode 100644 index 00000000000..1a67cdeffc1 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-size.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-count1.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-count1.xml new file mode 100644 index 00000000000..2d0b918daac --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-count1.xml @@ -0,0 +1,48 @@ + + + + + target/rolling-with-delete-accum-count1/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-count2.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-count2.xml new file mode 100644 index 00000000000..b2f39d01d62 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-count2.xml @@ -0,0 +1,48 @@ + + + + + target/rolling-with-delete-accum-count2/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-size.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-size.xml new file mode 100644 index 00000000000..a9748eb68fe --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-accum-size.xml @@ -0,0 +1,47 @@ + + + + + target/rolling-with-delete-accum-size/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-maxdepth.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-maxdepth.xml new file mode 100644 index 00000000000..308cf1b99b5 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-maxdepth.xml @@ -0,0 +1,46 @@ + + + + + target/rolling-with-delete-depth/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-nested.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-nested.xml new file mode 100644 index 00000000000..6792556bbd4 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-nested.xml @@ -0,0 +1,50 @@ + + + + + target/rolling-with-delete-nested/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional1.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional1.xml new file mode 100644 index 00000000000..b5012ba996b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional1.xml @@ -0,0 +1,45 @@ + + + + + target/rolling-unconditional-delete1/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional2.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional2.xml new file mode 100644 index 00000000000..10970f79d15 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional2.xml @@ -0,0 +1,46 @@ + + + + + target/rolling-unconditional-delete2/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional3.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional3.xml new file mode 100644 index 00000000000..c3f36012b18 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete-unconditional3.xml @@ -0,0 +1,46 @@ + + + + + target/rolling-unconditional-delete3/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete.xml new file mode 100644 index 00000000000..49f2f8ea074 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-custom-delete.xml @@ -0,0 +1,46 @@ + + + + + target/rolling-with-delete/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-with-padding.xml b/log4j-core-test/src/test/resources/log4j-rolling-with-padding.xml new file mode 100644 index 00000000000..fbc08a9c467 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-with-padding.xml @@ -0,0 +1,41 @@ + + + + + target/rolling-with-padding/ + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-xz-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-xz-lazy.xml new file mode 100644 index 00000000000..52640130256 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-xz-lazy.xml @@ -0,0 +1,59 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-xz.xml b/log4j-core-test/src/test/resources/log4j-rolling-xz.xml new file mode 100644 index 00000000000..0a7794dfdb6 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-xz.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-zip-lazy.xml b/log4j-core-test/src/test/resources/log4j-rolling-zip-lazy.xml new file mode 100644 index 00000000000..87c23bd4b24 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-zip-lazy.xml @@ -0,0 +1,59 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling-zip.xml b/log4j-core-test/src/test/resources/log4j-rolling-zip.xml new file mode 100644 index 00000000000..06895728363 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling-zip.xml @@ -0,0 +1,58 @@ + + + + + target/rolling1/rollingtest.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + > + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-rolling.properties b/log4j-core-test/src/test/resources/log4j-rolling.properties similarity index 85% rename from log4j-core/src/test/resources/log4j-rolling.properties rename to log4j-core-test/src/test/resources/log4j-rolling.properties index d02e0a89906..249d0419189 100644 --- a/log4j-core/src/test/resources/log4j-rolling.properties +++ b/log4j-core-test/src/test/resources/log4j-rolling.properties @@ -1,19 +1,21 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with +# contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 +# The ASF licenses this file to You under the Apache license, Version 2.0 # (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# See the license for the specific language governing permissions and +# limitations under the license. +# -status = error +status = off name = PropertiesConfigTest property.filename = target/rolling/rollingtest.log @@ -59,4 +61,4 @@ logger.rolling.file.appenderRef.rolling.ref = RollingFile rootLogger.level = info rootLogger.appenderRefs = stdout -rootLogger.appenderRef.stdout.ref = STDOUT \ No newline at end of file +rootLogger.appenderRef.stdout.ref = STDOUT diff --git a/log4j-core/src/test/resources/log4j-rolling2.xml b/log4j-core-test/src/test/resources/log4j-rolling2.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-rolling2.xml rename to log4j-core-test/src/test/resources/log4j-rolling2.xml diff --git a/log4j-core-test/src/test/resources/log4j-rolling3-direct.xml b/log4j-core-test/src/test/resources/log4j-rolling3-direct.xml new file mode 100644 index 00000000000..4cfdab96660 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling3-direct.xml @@ -0,0 +1,53 @@ + + + + + target/rolling3Direct + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-rolling3.xml b/log4j-core-test/src/test/resources/log4j-rolling3.xml new file mode 100644 index 00000000000..1677f6d6971 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rolling3.xml @@ -0,0 +1,51 @@ + + + + + target/rolling3/rollingtest.log + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-rolling4.xml b/log4j-core-test/src/test/resources/log4j-rolling4.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-rolling4.xml rename to log4j-core-test/src/test/resources/log4j-rolling4.xml diff --git a/log4j-core-test/src/test/resources/log4j-rootthrowablefilter.xml b/log4j-core-test/src/test/resources/log4j-rootthrowablefilter.xml new file mode 100644 index 00000000000..8b83ba90edd --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-rootthrowablefilter.xml @@ -0,0 +1,39 @@ + + + + + org.junit,org.apache.maven,sun.reflect,java.lang.reflect + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-2767.xml b/log4j-core-test/src/test/resources/log4j-routing-2767.xml new file mode 100644 index 00000000000..edf63f221f8 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-2767.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j-routing-lookup.xml b/log4j-core-test/src/test/resources/log4j-routing-lookup.xml new file mode 100644 index 00000000000..92f31c3a517 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-routing-lookup.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core/src/test/resources/log4j-routing-purge.xml b/log4j-core-test/src/test/resources/log4j-routing-purge.xml similarity index 86% rename from log4j-core/src/test/resources/log4j-routing-purge.xml rename to log4j-core-test/src/test/resources/log4j-routing-purge.xml index 0efc29627d9..910ba80cbea 100644 --- a/log4j-core/src/test/resources/log4j-routing-purge.xml +++ b/log4j-core-test/src/test/resources/log4j-routing-purge.xml @@ -17,10 +17,6 @@ --> - - target/routing-purge-idle/routingtest-$${sd:id}.log - target/routing-purge-manual/routingtest-$${sd:id}.log - @@ -30,24 +26,27 @@ + - + %d %p %C{1.} [%t] %m%n + - + + @@ -55,12 +54,13 @@ - + %d %p %C{1.} [%t] %m%n + diff --git a/log4j-core/src/test/resources/log4j-routing.json b/log4j-core-test/src/test/resources/log4j-routing.json similarity index 86% rename from log4j-core/src/test/resources/log4j-routing.json rename to log4j-core-test/src/test/resources/log4j-routing.json index 809b3c22783..c8b3e648868 100644 --- a/log4j-core/src/test/resources/log4j-routing.json +++ b/log4j-core-test/src/test/resources/log4j-routing.json @@ -14,10 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -{ "configuration": { "status": "error", "name": "RoutingTest", - "properties": { - "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" } - }, +{ "configuration": { "status": "off", "name": "RoutingTest", "ThresholdFilter": { "level": "debug" }, "appenders": { "Console": { "name": "STDOUT", @@ -31,7 +28,7 @@ "Route": [ { "RollingFile": { - "name": "Rolling-${sd:type}", "fileName": "${filename}", + "name": "Rolling-${sd:type}", "fileName": "target/rolling1/rollingtest-${sd:type}.log", "filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz", "PatternLayout": {"pattern": "%d %p %C{1.} [%t] %m%n"}, "SizeBasedTriggeringPolicy": { "size": "500" } @@ -48,4 +45,4 @@ "root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }} } } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/resources/log4j-routing.properties b/log4j-core-test/src/test/resources/log4j-routing.properties similarity index 82% rename from log4j-core/src/test/resources/log4j-routing.properties rename to log4j-core-test/src/test/resources/log4j-routing.properties index c365e35762f..c7fe0ffcc16 100644 --- a/log4j-core/src/test/resources/log4j-routing.properties +++ b/log4j-core-test/src/test/resources/log4j-routing.properties @@ -1,23 +1,23 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with +# contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 +# The ASF licenses this file to You under the Apache license, Version 2.0 # (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# See the license for the specific language governing permissions and +# limitations under the license. +# -status = error +status = off name = RoutingTest -property.filename = target/routing1/routingtestProps-$${sd:type}.log - filter.threshold.type = ThresholdFilter filter.threshold.level = debug @@ -33,7 +33,7 @@ appender.routing.routes.pattern = $${sd:type} appender.routing.routes.route1.type = Route appender.routing.routes.route1.rolling.type = RollingFile appender.routing.routes.route1.rolling.name = Routing-${sd:type} -appender.routing.routes.route1.rolling.fileName = ${filename} +appender.routing.routes.route1.rolling.fileName = target/routing1/routingtestProps-${sd:type}.log appender.routing.routes.route1.rolling.filePattern = target/routing1/test1-${sd:type}.%i.log.gz appender.routing.routes.route1.rolling.layout.type = PatternLayout appender.routing.routes.route1.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n diff --git a/log4j-core/src/test/resources/log4j-routing.xml b/log4j-core-test/src/test/resources/log4j-routing.xml similarity index 90% rename from log4j-core/src/test/resources/log4j-routing.xml rename to log4j-core-test/src/test/resources/log4j-routing.xml index 4d838861dee..003f7d74fa0 100644 --- a/log4j-core/src/test/resources/log4j-routing.xml +++ b/log4j-core-test/src/test/resources/log4j-routing.xml @@ -17,9 +17,6 @@ --> - - target/routing1/routingtest-$${sd:type}.log - @@ -32,7 +29,7 @@ - %d %p %C{1.} [%t] %m%n diff --git a/log4j-core/src/test/resources/log4j-routing2.json b/log4j-core-test/src/test/resources/log4j-routing2.json similarity index 88% rename from log4j-core/src/test/resources/log4j-routing2.json rename to log4j-core-test/src/test/resources/log4j-routing2.json index fe50b37753c..53c7d734f56 100644 --- a/log4j-core/src/test/resources/log4j-routing2.json +++ b/log4j-core-test/src/test/resources/log4j-routing2.json @@ -14,10 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -{ "configuration": { "status": "error", "name": "RoutingTest", - "properties": { - "property": { "name": "filename", "value" : "target/rolling1/rollingtest-$${sd:type}.log" } - }, +{ "configuration": { "status": "off", "name": "RoutingTest", "ThresholdFilter": { "level": "debug" }, "appenders": { "appender": [ @@ -28,7 +25,7 @@ "Route": [ { "RollingFile": { - "name": "Rolling-${sd:type}", "fileName": "${filename}", + "name": "Rolling-${sd:type}", "fileName": "target/rolling1/rollingtest-${sd:type}.log", "filePattern": "target/rolling1/test1-${sd:type}.%i.log.gz", "PatternLayout": {"pattern": "%d %p %C{1.} [%t] %m%n"}, "SizeBasedTriggeringPolicy": { "size": "500" } @@ -50,4 +47,4 @@ "root": { "level": "error", "AppenderRef": { "ref": "STDOUT" }} } } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/resources/log4j-routing3.xml b/log4j-core-test/src/test/resources/log4j-routing3.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-routing3.xml rename to log4j-core-test/src/test/resources/log4j-routing3.xml diff --git a/log4j-core-test/src/test/resources/log4j-set-level.xml b/log4j-core-test/src/test/resources/log4j-set-level.xml new file mode 100644 index 00000000000..06de5cdbb88 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-set-level.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + > + + + + + + + \ No newline at end of file diff --git a/log4j-core/src/test/resources/log4j-socket-options.xml b/log4j-core-test/src/test/resources/log4j-socket-options.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-socket-options.xml rename to log4j-core-test/src/test/resources/log4j-socket-options.xml diff --git a/log4j-core-test/src/test/resources/log4j-socket.xml b/log4j-core-test/src/test/resources/log4j-socket.xml new file mode 100644 index 00000000000..a0ea950ba3b --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-socket.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-socket2.xml b/log4j-core-test/src/test/resources/log4j-socket2.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-socket2.xml rename to log4j-core-test/src/test/resources/log4j-socket2.xml diff --git a/log4j-core/src/test/resources/log4j-ssl-socket-options.xml b/log4j-core-test/src/test/resources/log4j-ssl-socket-options.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-ssl-socket-options.xml rename to log4j-core-test/src/test/resources/log4j-ssl-socket-options.xml diff --git a/log4j-core/src/test/resources/log4j-strict1.xml b/log4j-core-test/src/test/resources/log4j-strict1.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-strict1.xml rename to log4j-core-test/src/test/resources/log4j-strict1.xml diff --git a/log4j-core/src/test/resources/log4j-style.xml b/log4j-core-test/src/test/resources/log4j-style.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-style.xml rename to log4j-core-test/src/test/resources/log4j-style.xml diff --git a/log4j-core/src/test/resources/log4j-sync-to-list.xml b/log4j-core-test/src/test/resources/log4j-sync-to-list.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-sync-to-list.xml rename to log4j-core-test/src/test/resources/log4j-sync-to-list.xml diff --git a/log4j-core/src/test/resources/log4j-test-shutdownTimeout.xml b/log4j-core-test/src/test/resources/log4j-test-shutdownTimeout.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-test-shutdownTimeout.xml rename to log4j-core-test/src/test/resources/log4j-test-shutdownTimeout.xml diff --git a/log4j-core/src/test/resources/log4j-test1.json b/log4j-core-test/src/test/resources/log4j-test1.json similarity index 98% rename from log4j-core/src/test/resources/log4j-test1.json rename to log4j-core-test/src/test/resources/log4j-test1.json index 38a0b22847a..2f419731053 100644 --- a/log4j-core/src/test/resources/log4j-test1.json +++ b/log4j-core-test/src/test/resources/log4j-test1.json @@ -1,6 +1,6 @@ { "Configuration" : { - "status": "warn", + "status": "off", "name": "YAMLConfigTest", "properties" : { @@ -71,4 +71,4 @@ } } } -} \ No newline at end of file +} diff --git a/log4j-core/src/test/resources/log4j-test1.properties b/log4j-core-test/src/test/resources/log4j-test1.properties similarity index 100% rename from log4j-core/src/test/resources/log4j-test1.properties rename to log4j-core-test/src/test/resources/log4j-test1.properties diff --git a/log4j-core-test/src/test/resources/log4j-test1.xml b/log4j-core-test/src/test/resources/log4j-test1.xml new file mode 100644 index 00000000000..3598aec62de --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test1.xml @@ -0,0 +1,58 @@ + + + + + target/test-xml.log + + + + + + + + + + %d %p %C{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core/src/test/resources/log4j-test1.yaml b/log4j-core-test/src/test/resources/log4j-test1.yaml similarity index 98% rename from log4j-core/src/test/resources/log4j-test1.yaml rename to log4j-core-test/src/test/resources/log4j-test1.yaml index d52fdcdadbd..11bc7d1501d 100644 --- a/log4j-core/src/test/resources/log4j-test1.yaml +++ b/log4j-core-test/src/test/resources/log4j-test1.yaml @@ -1,5 +1,5 @@ Configuration: - status: warn + status: 'off' name: YAMLConfigTest properties: property: diff --git a/log4j-core/src/test/resources/log4j-test2.properties b/log4j-core-test/src/test/resources/log4j-test2.properties similarity index 99% rename from log4j-core/src/test/resources/log4j-test2.properties rename to log4j-core-test/src/test/resources/log4j-test2.properties index 25bd5dd62ec..cb1436f98f1 100644 --- a/log4j-core/src/test/resources/log4j-test2.properties +++ b/log4j-core-test/src/test/resources/log4j-test2.properties @@ -15,7 +15,7 @@ # limitations under the license. # -status = debug +status = off name = PropertiesConfigTest monitorInterval = 1 diff --git a/log4j-core/src/test/resources/log4j-test2.xml b/log4j-core-test/src/test/resources/log4j-test2.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-test2.xml rename to log4j-core-test/src/test/resources/log4j-test2.xml diff --git a/log4j-core/src/test/resources/log4j-test3.xml b/log4j-core-test/src/test/resources/log4j-test3.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-test3.xml rename to log4j-core-test/src/test/resources/log4j-test3.xml diff --git a/log4j-core-test/src/test/resources/log4j-test5.xml b/log4j-core-test/src/test/resources/log4j-test5.xml new file mode 100644 index 00000000000..202206bff0f --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j-test5.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j-threaded.xml b/log4j-core-test/src/test/resources/log4j-threaded.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-threaded.xml rename to log4j-core-test/src/test/resources/log4j-threaded.xml diff --git a/log4j-core/src/test/resources/log4j-throwable.xml b/log4j-core-test/src/test/resources/log4j-throwable.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-throwable.xml rename to log4j-core-test/src/test/resources/log4j-throwable.xml diff --git a/log4j-core/src/test/resources/log4j-throwablefilter.xml b/log4j-core-test/src/test/resources/log4j-throwablefilter.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-throwablefilter.xml rename to log4j-core-test/src/test/resources/log4j-throwablefilter.xml diff --git a/log4j-core/src/test/resources/log4j-xinclude-appenders.xml b/log4j-core-test/src/test/resources/log4j-xinclude-appenders.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-xinclude-appenders.xml rename to log4j-core-test/src/test/resources/log4j-xinclude-appenders.xml diff --git a/log4j-core/src/test/resources/log4j-xinclude-loggers.xml b/log4j-core-test/src/test/resources/log4j-xinclude-loggers.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-xinclude-loggers.xml rename to log4j-core-test/src/test/resources/log4j-xinclude-loggers.xml diff --git a/log4j-core/src/test/resources/log4j-xinclude.xml b/log4j-core-test/src/test/resources/log4j-xinclude.xml similarity index 100% rename from log4j-core/src/test/resources/log4j-xinclude.xml rename to log4j-core-test/src/test/resources/log4j-xinclude.xml diff --git a/log4j-core/src/test/resources/log4j.dtd b/log4j-core-test/src/test/resources/log4j.dtd similarity index 100% rename from log4j-core/src/test/resources/log4j.dtd rename to log4j-core-test/src/test/resources/log4j.dtd diff --git a/log4j-core/src/test/resources/log4j12-perf.xml b/log4j-core-test/src/test/resources/log4j12-perf.xml similarity index 100% rename from log4j-core/src/test/resources/log4j12-perf.xml rename to log4j-core-test/src/test/resources/log4j12-perf.xml diff --git a/log4j-core/src/test/resources/log4j2-1002.xml b/log4j-core-test/src/test/resources/log4j2-1002.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-1002.xml rename to log4j-core-test/src/test/resources/log4j2-1002.xml diff --git a/log4j-core-test/src/test/resources/log4j2-1482.xml b/log4j-core-test/src/test/resources/log4j2-1482.xml new file mode 100644 index 00000000000..004dd0ab443 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-1482.xml @@ -0,0 +1,44 @@ + + + + + + target/log4j2-1482 + audit + param1,param2,param3${sys:line.separator} + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-1573.xml b/log4j-core-test/src/test/resources/log4j2-1573.xml new file mode 100644 index 00000000000..33df9d545a2 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-1573.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-180.xml b/log4j-core-test/src/test/resources/log4j2-180.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-180.xml rename to log4j-core-test/src/test/resources/log4j2-180.xml diff --git a/log4j-core-test/src/test/resources/log4j2-2134.yml b/log4j-core-test/src/test/resources/log4j2-2134.yml new file mode 100644 index 00000000000..66531ede92a --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-2134.yml @@ -0,0 +1,14 @@ +configuration: + status: off + name: NUAR_DEFAULT_LOGGING + Appenders: + Console: + name: CONSOLE + target: SYSTEM_OUT + PatternLayout: + pattern: "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" + Loggers: + Root: + level: info + AppenderRef: + - ref: "CONSOLE" diff --git a/log4j-core/src/test/resources/log4j2-272.xml b/log4j-core-test/src/test/resources/log4j2-272.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-272.xml rename to log4j-core-test/src/test/resources/log4j2-272.xml diff --git a/log4j-core/src/test/resources/log4j2-319.xml b/log4j-core-test/src/test/resources/log4j2-319.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-319.xml rename to log4j-core-test/src/test/resources/log4j2-319.xml diff --git a/log4j-core/src/test/resources/log4j2-744.xml b/log4j-core-test/src/test/resources/log4j2-744.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-744.xml rename to log4j-core-test/src/test/resources/log4j2-744.xml diff --git a/log4j-core-test/src/test/resources/log4j2-962.xml b/log4j-core-test/src/test/resources/log4j2-962.xml new file mode 100644 index 00000000000..7e23e28b1ec --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-962.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + %d %m%n + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-arbiters.json b/log4j-core-test/src/test/resources/log4j2-arbiters.json new file mode 100644 index 00000000000..d3dc0d218d0 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-arbiters.json @@ -0,0 +1,56 @@ +{ + "name": "ConfigTest", + "status": "ERROR", + "monitorInterval": 5, + "Appenders": { + "SystemPropertyArbiter": [ + { + "propertyName": "env", + "propertyValue": "dev", + "Console": { + "name": "Out", + "PatternLayout": { + "pattern": "%m%n" + } + } + }, + { + "propertyName": "env", + "propertyValue": "prod", + "List": { + "name": "Out" + } + } + ], + "ClassArbiter": [ + { + "className": "org.apache.logging.log4j.core.config.arbiters.ClassArbiter", + "List": { + "name": "ShouldExist" + } + }, + { + "className": "org.apache.logging.log4j.core.DoesNotExist", + "List": { + "name": "ShouldNotExist" + } + } + ] + }, + "Loggers": { + "Logger": { + "name": "org.apache.test", + "level": "trace", + "additivity": false, + "AppenderRef": { + "ref": "Out" + } + }, + "Root": { + "level": "error", + "AppenderRef": { + "ref": "Out" + } + } + } +} diff --git a/log4j-core-test/src/test/resources/log4j2-arbiters.xml b/log4j-core-test/src/test/resources/log4j2-arbiters.xml new file mode 100644 index 00000000000..e8094db4415 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-arbiters.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-config.xml b/log4j-core-test/src/test/resources/log4j2-config.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-config.xml rename to log4j-core-test/src/test/resources/log4j2-config.xml diff --git a/log4j-core-test/src/test/resources/log4j2-console-default-suppressed-throwable.xml b/log4j-core-test/src/test/resources/log4j2-console-default-suppressed-throwable.xml new file mode 100644 index 00000000000..6131247ab67 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-console-default-suppressed-throwable.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-console-disableAnsi.xml b/log4j-core-test/src/test/resources/log4j2-console-disableAnsi.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-disableAnsi.xml rename to log4j-core-test/src/test/resources/log4j2-console-disableAnsi.xml diff --git a/log4j-core/src/test/resources/log4j2-console-highlight-default.xml b/log4j-core-test/src/test/resources/log4j2-console-highlight-default.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-highlight-default.xml rename to log4j-core-test/src/test/resources/log4j2-console-highlight-default.xml diff --git a/log4j-core/src/test/resources/log4j2-console-highlight-logback.xml b/log4j-core-test/src/test/resources/log4j2-console-highlight-logback.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-highlight-logback.xml rename to log4j-core-test/src/test/resources/log4j2-console-highlight-logback.xml diff --git a/log4j-core/src/test/resources/log4j2-console-highlight.xml b/log4j-core-test/src/test/resources/log4j2-console-highlight.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-highlight.xml rename to log4j-core-test/src/test/resources/log4j2-console-highlight.xml diff --git a/log4j-core/src/test/resources/log4j2-console-msg-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-msg-ansi.xml rename to log4j-core-test/src/test/resources/log4j2-console-msg-ansi.xml diff --git a/log4j-core/src/test/resources/log4j2-console-noConsoleNoAnsi.xml b/log4j-core-test/src/test/resources/log4j2-console-noConsoleNoAnsi.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-noConsoleNoAnsi.xml rename to log4j-core-test/src/test/resources/log4j2-console-noConsoleNoAnsi.xml diff --git a/log4j-core/src/test/resources/log4j2-console-progress.xml b/log4j-core-test/src/test/resources/log4j2-console-progress.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-progress.xml rename to log4j-core-test/src/test/resources/log4j2-console-progress.xml diff --git a/log4j-core/src/test/resources/log4j2-console-style-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-style-ansi.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-style-ansi.xml rename to log4j-core-test/src/test/resources/log4j2-console-style-ansi.xml diff --git a/log4j-core/src/test/resources/log4j2-console-style-name-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-style-name-ansi.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-style-name-ansi.xml rename to log4j-core-test/src/test/resources/log4j2-console-style-name-ansi.xml diff --git a/log4j-core/src/test/resources/log4j2-console-style-no-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-style-no-ansi.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-style-no-ansi.xml rename to log4j-core-test/src/test/resources/log4j2-console-style-no-ansi.xml diff --git a/log4j-core/src/test/resources/log4j2-console-xex-ansi-custom.xml b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-custom.xml similarity index 97% rename from log4j-core/src/test/resources/log4j2-console-xex-ansi-custom.xml rename to log4j-core-test/src/test/resources/log4j2-console-xex-ansi-custom.xml index 5d54f15d978..d28b1697244 100644 --- a/log4j-core/src/test/resources/log4j2-console-xex-ansi-custom.xml +++ b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-custom.xml @@ -1,31 +1,31 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-console-xex-ansi-kirk.xml b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi-kirk.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console-xex-ansi-kirk.xml rename to log4j-core-test/src/test/resources/log4j2-console-xex-ansi-kirk.xml diff --git a/log4j-core/src/test/resources/log4j2-console-xex-ansi.xml b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi.xml similarity index 97% rename from log4j-core/src/test/resources/log4j2-console-xex-ansi.xml rename to log4j-core-test/src/test/resources/log4j2-console-xex-ansi.xml index 68ad5c54235..5d30cf54f37 100644 --- a/log4j-core/src/test/resources/log4j2-console-xex-ansi.xml +++ b/log4j-core-test/src/test/resources/log4j2-console-xex-ansi.xml @@ -1,31 +1,31 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-console.xml b/log4j-core-test/src/test/resources/log4j2-console.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-console.xml rename to log4j-core-test/src/test/resources/log4j2-console.xml diff --git a/log4j-core/src/test/resources/log4j2-dynamicfilter.xml b/log4j-core-test/src/test/resources/log4j2-dynamicfilter.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-dynamicfilter.xml rename to log4j-core-test/src/test/resources/log4j2-dynamicfilter.xml diff --git a/log4j-core-test/src/test/resources/log4j2-gelf-layout.xml b/log4j-core-test/src/test/resources/log4j2-gelf-layout.xml new file mode 100644 index 00000000000..6b119bbe5aa --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-gelf-layout.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-json-layout.xml b/log4j-core-test/src/test/resources/log4j2-json-layout.xml new file mode 100644 index 00000000000..4c287552203 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-json-layout.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-mapfilter.xml b/log4j-core-test/src/test/resources/log4j2-mapfilter.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-mapfilter.xml rename to log4j-core-test/src/test/resources/log4j2-mapfilter.xml diff --git a/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml b/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml new file mode 100644 index 00000000000..46e7e290790 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/log4j-core-test/src/test/resources/log4j2-pattern-layout-with-context.xml b/log4j-core-test/src/test/resources/log4j2-pattern-layout-with-context.xml new file mode 100644 index 00000000000..56eae7e70c6 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-pattern-layout-with-context.xml @@ -0,0 +1,33 @@ + + + + + %p %c $${ctx:user} $${event:Message} + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-perf-filter.xml b/log4j-core-test/src/test/resources/log4j2-perf-filter.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-perf-filter.xml rename to log4j-core-test/src/test/resources/log4j2-perf-filter.xml diff --git a/log4j-core/src/test/resources/log4j2-perf.xml b/log4j-core-test/src/test/resources/log4j2-perf.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-perf.xml rename to log4j-core-test/src/test/resources/log4j2-perf.xml diff --git a/log4j-core/src/test/resources/log4j2-properties-root-only.properties b/log4j-core-test/src/test/resources/log4j2-properties-root-only.properties similarity index 77% rename from log4j-core/src/test/resources/log4j2-properties-root-only.properties rename to log4j-core-test/src/test/resources/log4j2-properties-root-only.properties index 265686aa181..e70576710b9 100644 --- a/log4j-core/src/test/resources/log4j2-properties-root-only.properties +++ b/log4j-core-test/src/test/resources/log4j2-properties-root-only.properties @@ -1,19 +1,21 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with +# contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 +# The ASF licenses this file to You under the Apache license, Version 2.0 # (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# See the license for the specific language governing permissions and +# limitations under the license. +# -status = ERROR +status = off filter.Threshold.type = ThresholdFilter filter.Threshold.level = DEBUG diff --git a/log4j-core/src/test/resources/log4j2-properties-trailing-space-on-level.properties b/log4j-core-test/src/test/resources/log4j2-properties-trailing-space-on-level.properties similarity index 79% rename from log4j-core/src/test/resources/log4j2-properties-trailing-space-on-level.properties rename to log4j-core-test/src/test/resources/log4j2-properties-trailing-space-on-level.properties index ec188451518..6d86a3d188a 100644 --- a/log4j-core/src/test/resources/log4j2-properties-trailing-space-on-level.properties +++ b/log4j-core-test/src/test/resources/log4j2-properties-trailing-space-on-level.properties @@ -1,19 +1,21 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with +# contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 +# The ASF licenses this file to You under the Apache license, Version 2.0 # (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# See the license for the specific language governing permissions and +# limitations under the license. +# -status = ERROR +status = off dest = err filter.Threshold.type = ThresholdFilter @@ -36,4 +38,4 @@ logger.log4j.level = DEBUG logger.log4j.additivity = false rootLogger.appenderRef.console.ref = StdOut -rootLogger.level = ERROR \ No newline at end of file +rootLogger.level = ERROR diff --git a/log4j-core/src/test/resources/log4j2-properties.properties b/log4j-core-test/src/test/resources/log4j2-properties.properties similarity index 79% rename from log4j-core/src/test/resources/log4j2-properties.properties rename to log4j-core-test/src/test/resources/log4j2-properties.properties index a6517b74bcc..8344f10da84 100644 --- a/log4j-core/src/test/resources/log4j2-properties.properties +++ b/log4j-core-test/src/test/resources/log4j2-properties.properties @@ -1,19 +1,21 @@ +# # Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with +# contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 +# The ASF licenses this file to You under the Apache license, Version 2.0 # (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at +# the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# See the license for the specific language governing permissions and +# limitations under the license. +# -status = ERROR +status = off dest = err filter.Threshold.type = ThresholdFilter diff --git a/log4j-core/src/test/resources/log4j2-queueFull.xml b/log4j-core-test/src/test/resources/log4j2-queueFull.xml similarity index 80% rename from log4j-core/src/test/resources/log4j2-queueFull.xml rename to log4j-core-test/src/test/resources/log4j2-queueFull.xml index c75e3a98ce9..4dba608e31b 100644 --- a/log4j-core/src/test/resources/log4j2-queueFull.xml +++ b/log4j-core-test/src/test/resources/log4j2-queueFull.xml @@ -3,7 +3,7 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. - ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ The ASF licenses this file to You under the Apache license, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ @@ -12,10 +12,10 @@ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. + ~ See the license for the specific language governing permissions and + ~ limitations under the license. --> - + @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/log4j2-queueFullAsyncAppender.xml b/log4j-core-test/src/test/resources/log4j2-queueFullAsyncAppender.xml similarity index 83% rename from log4j-core/src/test/resources/log4j2-queueFullAsyncAppender.xml rename to log4j-core-test/src/test/resources/log4j2-queueFullAsyncAppender.xml index 953e402242a..fed82066f21 100644 --- a/log4j-core/src/test/resources/log4j2-queueFullAsyncAppender.xml +++ b/log4j-core-test/src/test/resources/log4j2-queueFullAsyncAppender.xml @@ -3,7 +3,7 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. - ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ The ASF licenses this file to You under the Apache license, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ @@ -12,10 +12,10 @@ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. + ~ See the license for the specific language governing permissions and + ~ limitations under the license. --> - + diff --git a/log4j-core/src/test/resources/log4j2-queueFullAsyncLoggerConfig.xml b/log4j-core-test/src/test/resources/log4j2-queueFullAsyncLoggerConfig.xml similarity index 80% rename from log4j-core/src/test/resources/log4j2-queueFullAsyncLoggerConfig.xml rename to log4j-core-test/src/test/resources/log4j2-queueFullAsyncLoggerConfig.xml index 127a88c0691..0dc3becf181 100644 --- a/log4j-core/src/test/resources/log4j2-queueFullAsyncLoggerConfig.xml +++ b/log4j-core-test/src/test/resources/log4j2-queueFullAsyncLoggerConfig.xml @@ -3,7 +3,7 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. - ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ The ASF licenses this file to You under the Apache license, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ @@ -12,10 +12,10 @@ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. + ~ See the license for the specific language governing permissions and + ~ limitations under the license. --> - + @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/log4j-core-test/src/test/resources/log4j2-random-1833.xml b/log4j-core-test/src/test/resources/log4j2-random-1833.xml new file mode 100644 index 00000000000..5f795873174 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-random-1833.xml @@ -0,0 +1,41 @@ + + + + + + target/random-1833 + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-rolling-1833.xml b/log4j-core-test/src/test/resources/log4j2-rolling-1833.xml new file mode 100644 index 00000000000..5c821097c53 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-rolling-1833.xml @@ -0,0 +1,41 @@ + + + + + + target/rolling-1833 + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/log4j2-sdfilter.xml b/log4j-core-test/src/test/resources/log4j2-sdfilter.xml similarity index 100% rename from log4j-core/src/test/resources/log4j2-sdfilter.xml rename to log4j-core-test/src/test/resources/log4j2-sdfilter.xml diff --git a/log4j-core-test/src/test/resources/log4j2-selectArbiters.xml b/log4j-core-test/src/test/resources/log4j2-selectArbiters.xml new file mode 100644 index 00000000000..750bd58b631 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-selectArbiters.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/log4j2-xml-layout.xml b/log4j-core-test/src/test/resources/log4j2-xml-layout.xml new file mode 100644 index 00000000000..cd2631b2bd7 --- /dev/null +++ b/log4j-core-test/src/test/resources/log4j2-xml-layout.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/logback-flume.xml b/log4j-core-test/src/test/resources/logback-flume.xml similarity index 100% rename from log4j-core/src/test/resources/logback-flume.xml rename to log4j-core-test/src/test/resources/logback-flume.xml diff --git a/log4j-core/src/test/resources/logback-perf-filter.xml b/log4j-core-test/src/test/resources/logback-perf-filter.xml similarity index 100% rename from log4j-core/src/test/resources/logback-perf-filter.xml rename to log4j-core-test/src/test/resources/logback-perf-filter.xml diff --git a/log4j-core/src/test/resources/logback-perf.xml b/log4j-core-test/src/test/resources/logback-perf.xml similarity index 100% rename from log4j-core/src/test/resources/logback-perf.xml rename to log4j-core-test/src/test/resources/logback-perf.xml diff --git a/log4j-core/src/test/resources/logback-subst.xml b/log4j-core-test/src/test/resources/logback-subst.xml similarity index 100% rename from log4j-core/src/test/resources/logback-subst.xml rename to log4j-core-test/src/test/resources/logback-subst.xml diff --git a/log4j-core-test/src/test/resources/logback-test.xml b/log4j-core-test/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..a7885c9372d --- /dev/null +++ b/log4j-core-test/src/test/resources/logback-test.xml @@ -0,0 +1,31 @@ + + + + target/testlogback.log + false + + false + %d %5p [%t] %c{0} %X{transactionId} - %m%n + + + + + + + diff --git a/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/default-level.xml b/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/default-level.xml new file mode 100644 index 00000000000..20e1ccce5a0 --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/default-level.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/inherit-level.xml b/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/inherit-level.xml new file mode 100644 index 00000000000..55834d52deb --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/AsyncLoggerConfig/inherit-level.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/logger-config/LoggerConfig/default-level.xml b/log4j-core-test/src/test/resources/logger-config/LoggerConfig/default-level.xml new file mode 100644 index 00000000000..a652279c5f7 --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/LoggerConfig/default-level.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/logger-config/LoggerConfig/inherit-level.xml b/log4j-core-test/src/test/resources/logger-config/LoggerConfig/inherit-level.xml new file mode 100644 index 00000000000..e1857adf6ae --- /dev/null +++ b/log4j-core-test/src/test/resources/logger-config/LoggerConfig/inherit-level.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/missingRootLogger.xml b/log4j-core-test/src/test/resources/missingRootLogger.xml new file mode 100644 index 00000000000..c3ac6d6aa8b --- /dev/null +++ b/log4j-core-test/src/test/resources/missingRootLogger.xml @@ -0,0 +1,49 @@ + + + + + + + %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/multipleIncompatibleAppendersTest.xml b/log4j-core-test/src/test/resources/multipleIncompatibleAppendersTest.xml similarity index 100% rename from log4j-core/src/test/resources/multipleIncompatibleAppendersTest.xml rename to log4j-core-test/src/test/resources/multipleIncompatibleAppendersTest.xml diff --git a/log4j-core/src/test/resources/multipleRootLoggersTest.xml b/log4j-core-test/src/test/resources/multipleRootLoggersTest.xml similarity index 84% rename from log4j-core/src/test/resources/multipleRootLoggersTest.xml rename to log4j-core-test/src/test/resources/multipleRootLoggersTest.xml index 0aaa1b6fb3f..208494a92e8 100644 --- a/log4j-core/src/test/resources/multipleRootLoggersTest.xml +++ b/log4j-core-test/src/test/resources/multipleRootLoggersTest.xml @@ -3,7 +3,7 @@ ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE file distributed with ~ this work for additional information regarding copyright ownership. - ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ The ASF licenses this file to You under the Apache license, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the License. You may obtain a copy of the License at ~ @@ -12,10 +12,10 @@ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. + ~ See the license for the specific language governing permissions and + ~ limitations under the license. --> - + @@ -29,4 +29,4 @@ - \ No newline at end of file + diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/impl/ForceNoDefClassFoundError.class b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/impl/ForceNoDefClassFoundError.class similarity index 100% rename from log4j-core/src/test/resources/org/apache/logging/log4j/core/impl/ForceNoDefClassFoundError.class rename to log4j-core-test/src/test/resources/org/apache/logging/log4j/core/impl/ForceNoDefClassFoundError.class diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle.properties b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle.properties similarity index 100% rename from log4j-core/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle.properties rename to log4j-core-test/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle.properties diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle_en.properties b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle_en.properties similarity index 100% rename from log4j-core/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle_en.properties rename to log4j-core-test/src/test/resources/org/apache/logging/log4j/core/lookup/resource-bundle_en.properties diff --git a/log4j-core/src/test/resources/org/apache/logging/log4j/core/net/ssl/README b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/README similarity index 100% rename from log4j-core/src/test/resources/org/apache/logging/log4j/core/net/ssl/README rename to log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/README diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/gencerts.sh b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/gencerts.sh new file mode 100755 index 00000000000..e970f1c5c91 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/gencerts.sh @@ -0,0 +1,28 @@ +mkdir tmp +# Create the CA key and certificate +openssl req -config rootca.conf -new -x509 -nodes -keyout tmp/log4j2-cacert.key -out tmp/log4j2-ca.crt -days 7302 +# Create the trust store and import the certificate +keytool -keystore ../truststore.jks -storetype jks -importcert -file 'tmp/log4j2-ca.crt' -keypass changeit -storepass changeit -alias log4j2-cacert -noprompt +#Import the root certificate +keytool -keystore ../client.log4j2-keystore.jks -alias log4j2-ca -importcert -file tmp/log4j2-ca.crt -keypass changeit -storepass changeit -noprompt +# Create the client private key in the client key store +keytool -genkeypair -keyalg RSA -alias client -keystore ../client.log4j2-keystore.jks -storepass changeit -keypass changeit -validity 7302 -keysize 2048 -dname "CN=client.log4j2, C=US" +# Create a signing request for the client # +keytool -keystore ../client.log4j2-keystore.jks -alias client -certreq -file tmp/client.csr -keypass changeit -storepass changeit +# Sign the client certificate +openssl x509 -req -CA 'tmp/log4j2-ca.crt' -CAkey 'tmp/log4j2-cacert.key' -in tmp/client.csr -out tmp/client.crt_signed -days 7302 -CAcreateserial -passin pass:changeit +# Verify the signed certificate +openssl verify -CAfile 'tmp/log4j2-ca.crt' tmp/client.crt_signed +#Import the client's signed certificate +keytool -keystore ../client.log4j2-keystore.jks -alias client -importcert -file tmp/client.crt_signed -keypass changeit -storepass changeit -noprompt +#Verify the keystore +keytool -list -keystore ../client.log4j2-keystore.jks -storepass changeit +# Create the server private key in the server key store +keytool -genkeypair -keyalg RSA -alias server -keystore tmp/server.log4j2-keystore.p12 -storepass changeit -storetype PKCS12 -keypass changeit -validity 7302 -keysize 2048 -dname "CN=server.log4j2, C=US" +# Create a signing request for the server # +keytool -keystore tmp/server.log4j2-keystore.p12 -alias server -certreq -file tmp/server.csr -keypass changeit -storepass changeit +# Sign the server certificate +openssl x509 -req -CA 'tmp/log4j2-ca.crt' -CAkey 'tmp/log4j2-cacert.key' -in tmp/server.csr -out ../server.log4j2-crt.pem -days 7302 -CAcreateserial -passin pass:changeit +# Extract the private key +openssl pkcs12 -in tmp/server.log4j2-keystore.p12 -passin pass:changeit -nokeys -out ../server.log4j2.pem +rm -rf tmp diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/rootca.conf b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/rootca.conf new file mode 100644 index 00000000000..7d264462de2 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/rootca.conf @@ -0,0 +1,9 @@ +[ req ] +distinguished_name = CA_DN +prompt = no +output_password = changeit +default_bits = 2048 + +[ CA_DN ] +C = US +CN = log4j2-ca diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/server.conf b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/server.conf new file mode 100644 index 00000000000..00fa79b85f9 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/build/server.conf @@ -0,0 +1,9 @@ +[ req ] +distinguished_name = CA_DN +prompt = no +output_password = changeit +default_bits = 2048 + +[ CA_DN ] +C = US +CN = iserver.log4j2 diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks new file mode 100644 index 00000000000..80892ba8899 Binary files /dev/null and b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/client.log4j2-keystore.jks differ diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/server.log4j2-crt.pem b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/server.log4j2-crt.pem new file mode 100644 index 00000000000..e7bba63f926 --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/server.log4j2-crt.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICwjCCAaoCCQCZurToALecATANBgkqhkiG9w0BAQUFADAhMQswCQYDVQQGEwJV +UzESMBAGA1UEAwwJbG9nNGoyLWNhMB4XDTIyMDgyMjE2NDI0MFoXDTQyMDgxOTE2 +NDI0MFowJTELMAkGA1UEBhMCVVMxFjAUBgNVBAMTDXNlcnZlci5sb2c0ajIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCOqJsHWe/IHWm2vGuZHkKccPeE +VsF6cQWzmqSAo29kWRWb7QYlJzoat9W++VnQ1M6x27tnphBBwnh9YTJ6eZpQ+y9h ++FnH8/8nIyB8pyP7VZTn993YvmpkdZwT7LMeANJFrPLmYUl0r0tHJEQWbULq8czF +B/57/hZVfYBs0/Hj1wnURmQ0RGaARhFYKg1TW2AFLUKrWNw0aiZbcjOcVf61Ysmh +99TK1IhL+aOByTB07x9t0xk9caeyfARmHvCh6SFzFGXAc8ov5omZqcBKZS6HS1Ym +kd7oqX1p/ZMugc9i35nn2tqQCQxg/+F31H82IhuTAFvRaHwrCJxXA+mApeI/AgMB +AAEwDQYJKoZIhvcNAQEFBQADggEBAES9R3yiKuFo901AOpR0EYGM8gPwujiUP4O2 +whYqlBhPwhrYGlDUzxgt6VmPYaxuD9xhed99U/LThC6CjJFXnGKJL33BQksyjTM5 +vphUyVXWUiMDhtLdijNOBoI6y3pEL6+k90pfDy8j6SqalukukNfSjNJvPXBpiDyh +aLgoixuul0jwZi7vu8k+IKXjRt0NzQaKPLmOFkoccSB0qFkTA+WTd2vzhgS0hH+C +k9gCS3XtJhPPWNS3JZ6+UWHbJiGLjm/SfhZifKIuoW5S394p78DIhBk3otopdPrk +gz0WZFCA/7m0AtOpmz9YZsS7JocJEvD3RLDZ0owl+9VobKa1kB8= +-----END CERTIFICATE----- diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/server.log4j2.pem b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/server.log4j2.pem new file mode 100644 index 00000000000..dc4339c6b3c --- /dev/null +++ b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/server.log4j2.pem @@ -0,0 +1,23 @@ +Bag Attributes + friendlyName: server + localKeyID: 54 69 6D 65 20 31 36 36 31 31 38 36 35 35 39 37 37 37 +subject=/C=US/CN=server.log4j2 +issuer=/C=US/CN=server.log4j2 +-----BEGIN CERTIFICATE----- +MIIC6TCCAdGgAwIBAgIEdDynVTANBgkqhkiG9w0BAQsFADAlMQswCQYDVQQGEwJV +UzEWMBQGA1UEAxMNc2VydmVyLmxvZzRqMjAeFw0yMjA4MjIxNjQyMzlaFw00MjA4 +MTkxNjQyMzlaMCUxCzAJBgNVBAYTAlVTMRYwFAYDVQQDEw1zZXJ2ZXIubG9nNGoy +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjqibB1nvyB1ptrxrmR5C +nHD3hFbBenEFs5qkgKNvZFkVm+0GJSc6GrfVvvlZ0NTOsdu7Z6YQQcJ4fWEyenma +UPsvYfhZx/P/JyMgfKcj+1WU5/fd2L5qZHWcE+yzHgDSRazy5mFJdK9LRyREFm1C +6vHMxQf+e/4WVX2AbNPx49cJ1EZkNERmgEYRWCoNU1tgBS1Cq1jcNGomW3IznFX+ +tWLJoffUytSIS/mjgckwdO8fbdMZPXGnsnwEZh7woekhcxRlwHPKL+aJmanASmUu +h0tWJpHe6Kl9af2TLoHPYt+Z59rakAkMYP/hd9R/NiIbkwBb0Wh8KwicVwPpgKXi +PwIDAQABoyEwHzAdBgNVHQ4EFgQU0VkuBNTMtKknjRJcZ7IiU1UNOOAwDQYJKoZI +hvcNAQELBQADggEBAFCKDMn065xXejfo+lLEXenVIUn4moX/KCibGZwqhcYWXjrZ +BxSblxrLn3KSeuY3NBlaw/GTXLypNgZlr1skwhl4Zy4B1Yd97m5rHBmlxyWODL3g +ZgjSXWA8zc84wps196YJPP6mrP05bAgoRPEsXIBolFvC/M/NvzWukh5UbGGactJG +eMdVQ88HksimnWHrkWXMURqD/KqCwWvXc7Ppy9XZwb4+tEJ2S/6MgCOApSQU+uol +qxOrgg8rl74JF9/RXLode/KWdIPuYF+FIkXUt49S6s/diIwX6YkGLjAk8rFoaBmf +5sFkbGStttNaZfhkDGLwV9KWE++BbdYy5IpksP0= +-----END CERTIFICATE----- diff --git a/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/truststore.jks b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/truststore.jks new file mode 100644 index 00000000000..2ad25d5715e Binary files /dev/null and b/log4j-core-test/src/test/resources/org/apache/logging/log4j/core/net/ssl/truststore.jks differ diff --git a/log4j-core/src/test/resources/perf-CountingNoOpAppender.xml b/log4j-core-test/src/test/resources/perf-CountingNoOpAppender.xml similarity index 100% rename from log4j-core/src/test/resources/perf-CountingNoOpAppender.xml rename to log4j-core-test/src/test/resources/perf-CountingNoOpAppender.xml diff --git a/log4j-core/src/test/resources/perf-log4j12-async.xml b/log4j-core-test/src/test/resources/perf-log4j12-async.xml similarity index 100% rename from log4j-core/src/test/resources/perf-log4j12-async.xml rename to log4j-core-test/src/test/resources/perf-log4j12-async.xml diff --git a/log4j-core/src/test/resources/perf-log4j12.xml b/log4j-core-test/src/test/resources/perf-log4j12.xml similarity index 100% rename from log4j-core/src/test/resources/perf-log4j12.xml rename to log4j-core-test/src/test/resources/perf-log4j12.xml diff --git a/log4j-core/src/test/resources/perf-logback-async.xml b/log4j-core-test/src/test/resources/perf-logback-async.xml similarity index 100% rename from log4j-core/src/test/resources/perf-logback-async.xml rename to log4j-core-test/src/test/resources/perf-logback-async.xml diff --git a/log4j-core/src/test/resources/perf-logback.xml b/log4j-core-test/src/test/resources/perf-logback.xml similarity index 100% rename from log4j-core/src/test/resources/perf-logback.xml rename to log4j-core-test/src/test/resources/perf-logback.xml diff --git a/log4j-core/src/test/resources/perf/SimplePerfTest.bat b/log4j-core-test/src/test/resources/perf/SimplePerfTest.bat similarity index 100% rename from log4j-core/src/test/resources/perf/SimplePerfTest.bat rename to log4j-core-test/src/test/resources/perf/SimplePerfTest.bat diff --git a/log4j-core/src/test/resources/perf/SimplePerfTest.sh b/log4j-core-test/src/test/resources/perf/SimplePerfTest.sh similarity index 100% rename from log4j-core/src/test/resources/perf/SimplePerfTest.sh rename to log4j-core-test/src/test/resources/perf/SimplePerfTest.sh diff --git a/log4j-core/src/test/resources/perf/runResponseTm.sh b/log4j-core-test/src/test/resources/perf/runResponseTm.sh similarity index 100% rename from log4j-core/src/test/resources/perf/runResponseTm.sh rename to log4j-core-test/src/test/resources/perf/runResponseTm.sh diff --git a/log4j-core/src/test/resources/perf1syncFastFile.xml b/log4j-core-test/src/test/resources/perf1syncFastFile.xml similarity index 100% rename from log4j-core/src/test/resources/perf1syncFastFile.xml rename to log4j-core-test/src/test/resources/perf1syncFastFile.xml diff --git a/log4j-core/src/test/resources/perf1syncFile.xml b/log4j-core-test/src/test/resources/perf1syncFile.xml similarity index 100% rename from log4j-core/src/test/resources/perf1syncFile.xml rename to log4j-core-test/src/test/resources/perf1syncFile.xml diff --git a/log4j-core/src/test/resources/perf2syncRollFastFile.xml b/log4j-core-test/src/test/resources/perf2syncRollFastFile.xml similarity index 100% rename from log4j-core/src/test/resources/perf2syncRollFastFile.xml rename to log4j-core-test/src/test/resources/perf2syncRollFastFile.xml diff --git a/log4j-core/src/test/resources/perf2syncRollFile.xml b/log4j-core-test/src/test/resources/perf2syncRollFile.xml similarity index 100% rename from log4j-core/src/test/resources/perf2syncRollFile.xml rename to log4j-core-test/src/test/resources/perf2syncRollFile.xml diff --git a/log4j-core/src/test/resources/perf3PlainNoLoc.xml b/log4j-core-test/src/test/resources/perf3PlainNoLoc.xml similarity index 100% rename from log4j-core/src/test/resources/perf3PlainNoLoc.xml rename to log4j-core-test/src/test/resources/perf3PlainNoLoc.xml diff --git a/log4j-core/src/test/resources/perf4PlainLocation.xml b/log4j-core-test/src/test/resources/perf4PlainLocation.xml similarity index 100% rename from log4j-core/src/test/resources/perf4PlainLocation.xml rename to log4j-core-test/src/test/resources/perf4PlainLocation.xml diff --git a/log4j-core/src/test/resources/perf5AsyncApndNoLoc.xml b/log4j-core-test/src/test/resources/perf5AsyncApndNoLoc.xml similarity index 100% rename from log4j-core/src/test/resources/perf5AsyncApndNoLoc.xml rename to log4j-core-test/src/test/resources/perf5AsyncApndNoLoc.xml diff --git a/log4j-core/src/test/resources/perf6AsyncApndLoc.xml b/log4j-core-test/src/test/resources/perf6AsyncApndLoc.xml similarity index 100% rename from log4j-core/src/test/resources/perf6AsyncApndLoc.xml rename to log4j-core-test/src/test/resources/perf6AsyncApndLoc.xml diff --git a/log4j-core/src/test/resources/perf7MixedNoLoc.xml b/log4j-core-test/src/test/resources/perf7MixedNoLoc.xml similarity index 100% rename from log4j-core/src/test/resources/perf7MixedNoLoc.xml rename to log4j-core-test/src/test/resources/perf7MixedNoLoc.xml diff --git a/log4j-core/src/test/resources/perf8MixedLoc.xml b/log4j-core-test/src/test/resources/perf8MixedLoc.xml similarity index 100% rename from log4j-core/src/test/resources/perf8MixedLoc.xml rename to log4j-core-test/src/test/resources/perf8MixedLoc.xml diff --git a/log4j-core-test/src/test/resources/perf9MMapNoLoc.xml b/log4j-core-test/src/test/resources/perf9MMapNoLoc.xml new file mode 100644 index 00000000000..3f6b9409998 --- /dev/null +++ b/log4j-core-test/src/test/resources/perf9MMapNoLoc.xml @@ -0,0 +1,36 @@ + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/reconfiguration-deadlock.xml b/log4j-core-test/src/test/resources/reconfiguration-deadlock.xml new file mode 100644 index 00000000000..99829e6d5be --- /dev/null +++ b/log4j-core-test/src/test/resources/reconfiguration-deadlock.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.original.xml b/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.original.xml new file mode 100644 index 00000000000..85b84c0024a --- /dev/null +++ b/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.original.xml @@ -0,0 +1,56 @@ + + + + + + ${sys:logfile} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.xml b/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.xml new file mode 100644 index 00000000000..e507ecdfed8 --- /dev/null +++ b/log4j-core-test/src/test/resources/rolling-file-appender-reconfigure.xml @@ -0,0 +1,56 @@ + + + + + + target/log4j-1967 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/rollover-test.xml b/log4j-core-test/src/test/resources/rollover-test.xml new file mode 100644 index 00000000000..878a523f57a --- /dev/null +++ b/log4j-core-test/src/test/resources/rollover-test.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-29.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.1.log.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-29.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.1.log.gz diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.10.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.10.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.10.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.11.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.11.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.11.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.12.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.12.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.12.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.13.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.13.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.13.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.14.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.14.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.14.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.15.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.15.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.15.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.16.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.16.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.16.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.17.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.17.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.17.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.18.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.18.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.18.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.19.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.19.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.19.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.2.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.2.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.2.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.20.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.20.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.20.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.21.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.21.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.21.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.22.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.22.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.22.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.23.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.23.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.23.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.24.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.24.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.24.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.25.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.25.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.25.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.26.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.26.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.26.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.27.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.27.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.27.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.28.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.28.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.28.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.29.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.29.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.29.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.3.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.3.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.3.log.gz differ diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-30 b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.30.log similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-30 rename to log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.30.log diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.4.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.4.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.4.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.5.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.5.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.5.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.6.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.6.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.6.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.7.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.7.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.7.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.8.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.8.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.8.log.gz differ diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.9.log.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.9.log.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.20211028T194500+0200.9.log.gz differ diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_00-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_00-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_00-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_00-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_00-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_00-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_00-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_00-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_01-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_01-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_01-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_01-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-3.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_02-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_02-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-3.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_03-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_03-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-3.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_04-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_04-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-3.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_05-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_05-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-10.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-10.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-10.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-10.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-11.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-11.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-11.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-11.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-12.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-12.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-12.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-12.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-13.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-13.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-13.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-13.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-14.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-14.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-14.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-14.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-15.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-15.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-15.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-15.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-16.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-16.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-16.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-16.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-17.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-17.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-17.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-17.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-18.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-18.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-18.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-18.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-19.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-19.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-19.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-19.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-20.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-20.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-20.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-20.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-3.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-6.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-6.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-6.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-6.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-7.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-7.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-7.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-7.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-8.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-8.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-8.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-8.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-9.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-9.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_06-9.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_06-9.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-10.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-10.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-10.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-10.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-11.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-11.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-11.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-11.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-12.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-12.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-12.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-12.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-13.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-13.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-13.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-13.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-14.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-14.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-14.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-14.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-15.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-15.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-15.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-15.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-16.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-16.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-16.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-16.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-17.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-17.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-17.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-17.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-18.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-18.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-18.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-18.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-19.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-19.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-19.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-19.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-20.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-20.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-20.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-20.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-21.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-21.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-21.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-21.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-22.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-22.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-22.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-22.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-23.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-23.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-23.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-23.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-24.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-24.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-24.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-24.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-25.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-25.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-25.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-25.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-26.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-26.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-26.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-26.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-27.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-27.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-27.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-27.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-28.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-28.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-28.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-28.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-29.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-29.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-29.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-29.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-3.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-6.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-6.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-6.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-6.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-7.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-7.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-7.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-7.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-8.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-8.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-8.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-8.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-9.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-9.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_07-9.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_07-9.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-10.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-10.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-10.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-10.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-11.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-11.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-11.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-11.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-12.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-12.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-12.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-12.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-13.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-13.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-13.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-13.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-14.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-14.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-14.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-14.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-15.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-15.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-15.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-15.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-16.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-16.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-16.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-16.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-17.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-17.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-17.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-17.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-18.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-18.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-18.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-18.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-19.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-19.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-19.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-19.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-20.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-20.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-20.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-20.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-21.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-21.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-21.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-21.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-22.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-22.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-22.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-22.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-23.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-23.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-23.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-23.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-24.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-24.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-24.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-24.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-25.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-25.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-25.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-25.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-26.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-26.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-26.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-26.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-27.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-27.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-27.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-27.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-28.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-28.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-28.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-28.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-29.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-29.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-29.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-29.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-3.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-6.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-6.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-6.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-6.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-7.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-7.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-7.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-7.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-8.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-8.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-8.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-8.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-9.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-9.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_08-9.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_08-9.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-1.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-1.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-1.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-1.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-10.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-10.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-10.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-10.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-11.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-11.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-11.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-11.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-12.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-12.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-12.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-12.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-13.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-13.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-13.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-13.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-14.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-14.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-14.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-14.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-15.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-15.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-15.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-15.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-16.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-16.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-16.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-16.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-17.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-17.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-17.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-17.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-18.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-18.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-18.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-18.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-19.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-19.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-19.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-19.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-2.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-2.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-2.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-2.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-20.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-20.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-20.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-20.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-21.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-21.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-21.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-21.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-22.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-22.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-22.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-22.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-23.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-23.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-23.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-23.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-24.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-24.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-24.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-24.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-25.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-25.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-25.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-25.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-26.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-26.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-26.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-26.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-27.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-27.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-27.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-27.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-28.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-28.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-28.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-28.gz diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-29.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-29.gz new file mode 100644 index 00000000000..8fa0ab8aac4 Binary files /dev/null and b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-29.gz differ diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-3.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-3.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-3.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-3.gz diff --git a/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-30 b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-30 new file mode 100644 index 00000000000..8d1c8b69c3f --- /dev/null +++ b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-30 @@ -0,0 +1 @@ + diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-4.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-4.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-4.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-4.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-5.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-5.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-5.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-5.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-6.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-6.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-6.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-6.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-7.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-7.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-7.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-7.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-8.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-8.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-8.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-8.gz diff --git a/log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-9.gz b/log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-9.gz similarity index 100% rename from log4j-core/src/test/resources/rolloverPath/log4j.txt.20170112_09-9.gz rename to log4j-core-test/src/test/resources/rolloverPath/log4j.txt.20170112_09-9.gz diff --git a/log4j-core/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml b/log4j-core-test/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml similarity index 100% rename from log4j-core/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml rename to log4j-core-test/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml diff --git a/log4j-core/src/test/resources/serializedEvent.dat b/log4j-core-test/src/test/resources/serializedEvent.dat similarity index 100% rename from log4j-core/src/test/resources/serializedEvent.dat rename to log4j-core-test/src/test/resources/serializedEvent.dat diff --git a/log4j-core/src/test/resources/witness/PatternParser_mdc b/log4j-core-test/src/test/resources/witness/PatternParser_mdc similarity index 100% rename from log4j-core/src/test/resources/witness/PatternParser_mdc rename to log4j-core-test/src/test/resources/witness/PatternParser_mdc diff --git a/log4j-core/src/test/resources/xml-events.xml b/log4j-core-test/src/test/resources/xml-events.xml similarity index 100% rename from log4j-core/src/test/resources/xml-events.xml rename to log4j-core-test/src/test/resources/xml-events.xml diff --git a/log4j-core/README.md b/log4j-core/README.md new file mode 100644 index 00000000000..3e3013c3623 --- /dev/null +++ b/log4j-core/README.md @@ -0,0 +1,53 @@ + + +# Building Log4j Core + +Log4j 2 supports the Java Platform Module System. JPMS has requirements that conflict with several things +Log4j also tries to support: +1. OSGi - OSGi frameworks are not packaged as OSGi modules. Including an OSGi implementation will cause +compiler errors while resolving the JPMS module inforation. +2. Garbage Free - The Google tool Log4j uses to verify that Log4j core is garbage free violates JPMS rules. The test +compilations fail when it is included as a dependency. +3. Compiler bugs - When compiling with module-info.java included the classes in the appender, layout, and filter +directories get "duplicate class" errors. For some reason these directory names are being interpreted as starting +with upper case letters even though they are not. For some reason the compiler is showing an error +that the class cannot be found even though it is being generated. See + [JDK-8265826](https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8265826). +4. Test classes that are used by other modules - Several test classes are used by other log4j modules and need +to be passed to them. This requires these classes exist in a package that is not used in log4j-core. +5. Test classes used by log4j-core must use the same package space to be able to access some methods in the classes +being tested. +6. Once Java has compiled the main module with a module-info.java all test compiles also require one. Likewise, +a test compile with a module-info.java is not allowed if the main compile doesn't have one. + +For these reasons the build will need to be processed as follows: +1. Move the Garbage Free tests to their own module. This will require copying all the test resources. +1. Compile all the main classes except module-info.java with the Plugin preprocessor. +1. Compile the main module-info.java. +1. Compile the test classes used by other modules with module-info.java and with the plugin preprocessor. +1. Package these test classes in a test jar. +1. Delete the module-info and generated source for the test classes. +1. Move the main module-info to a temp location. +1. Compile the unit test classes without module-info.java. +1. Move the main module-info back to the classes directory. +1. Compile module-info.java for unit tests. +1. Run the unit tests. +1. Create the main jar if the unit tests pass. + +Once the JDK bug is fixed this process can be simplified quite a bit since the components will all be able to be +compiled once with the module-info.java file. diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml index 4ae808ac28b..43f632845cf 100644 --- a/log4j-core/pom.xml +++ b/log4j-core/pom.xml @@ -20,8 +20,7 @@ org.apache.logging.log4j log4j - 2.10.1-SNAPSHOT - ../ + 3.0.0-SNAPSHOT log4j-core jar @@ -31,43 +30,46 @@ ${basedir}/.. Core Documentation /core + true - - - org.apache.logging.log4j - log4j-api - - + - org.apache.logging.log4j - log4j-core-java9 + org.osgi + org.osgi.framework provided - zip org.osgi - org.osgi.core + org.osgi.resource provided - + - com.lmax - disruptor + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-plugins + + + + org.apache.commons + commons-compress true com.conversantmedia disruptor - jdk7 true - + - org.jctools - jctools-core + com.lmax + disruptor true @@ -82,23 +84,16 @@ jackson-databind true - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - true - com.fasterxml.jackson.dataformat jackson-dataformat-xml true - + - com.fasterxml.woodstox - woodstox-core - 5.0.3 + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml true @@ -107,324 +102,52 @@ jansi true - - - com.sun.mail - javax.mail - true - - - - org.jboss.spec.javax.jms - jboss-jms-api_1.1_spec - provided - true - - - - org.apache.kafka - kafka-clients - true - - - - org.zeromq - jeromq - true - - - - org.apache.commons - commons-compress - true - - - - org.apache.commons - commons-csv - true - - + - com.beust - jcommander + org.jctools + jctools-core true - - - - + - org.apache.logging.log4j - log4j-api - test-jar - test + javax.activation + javax.activation-api + runtime - + - org.tukaani - xz - test + javax.xml.bind + jaxb-api + runtime - + - org.jmdns - jmdns - 3.5.3 - test - - - - log4j - log4j - 1.2.17 - test - - - - org.slf4j - slf4j-api - test - - - org.slf4j - slf4j-ext - test - - - - junit - junit - test - - - org.hamcrest - hamcrest-all - test - - - - org.mockito - mockito-core - test - - - - org.hsqldb - hsqldb - test - - - com.h2database - h2 - test - - - - org.springframework - spring-test - test - - - - org.apache.activemq - activemq-broker - test - - - org.apache.geronimo.specs - geronimo-jms_1.1_spec - - - - - commons-logging - commons-logging - test - - - - ch.qos.logback - logback-core - test - - - ch.qos.logback - logback-classic - test - - - - org.eclipse.tycho - org.eclipse.osgi - test - - - org.apache.felix - org.apache.felix.framework - test - - - org.codehaus.plexus - plexus-utils - test - - - org.apache.maven - maven-core - test - - - - net.javacrumbs.json-unit - json-unit - test - - - org.xmlunit - xmlunit-core - test - - - org.xmlunit - xmlunit-matchers - test - - - commons-io - commons-io - test - - - - commons-codec - commons-codec - test - - - org.apache.commons - commons-lang3 - test - - - org.apache-extras.beanshell - bsh - test - - - org.codehaus.groovy - groovy-all - test - - - - com.github.tomakehurst - wiremock - test - - - - com.google.code.java-allocation-instrumenter - java-allocation-instrumenter - test - - - org.hdrhistogram - HdrHistogram - test + com.fasterxml.woodstox + woodstox-core + runtime - org.apache.maven.plugins - maven-dependency-plugin - 3.0.2 - - - unpack-classes - prepare-package - - unpack - - - - - org.apache.logging.log4j - log4j-core-java9 - ${project.version} - zip - false - - - **/*.class - **/*.java - ${project.build.directory} - false - true - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.7 - - - add-source - generate-sources - - add-source - - - - ${project.build.directory}/log4j-core-java9 - - - - - - - maven-compiler-plugin - - - - default-compile - - - module-info.java - - none - - - - - process-plugins - - compile - - process-classes - - - module-info.java - - only - - - - - - maven-surefire-plugin + org.apache.felix + maven-bundle-plugin - - org.apache.logging.log4j.categories.PerformanceTests - - 1 - false - - * - + + org.apache.logging.log4j.core + + org.apache.logging.log4j.core.* + + javax.activation;version="[1.2,2)";resolution:=optional, + javax.jms;version="[1.1,3)";resolution:=optional, + javax.mail;version="[1.6,2)";resolution:=optional, + javax.mail.internet;version="[1.6,2)";resolution:=optional, + javax.mail.util;version="[1.6,2)";resolution:=optional, + sun.reflect;resolution:=optional, + * + + org.apache.logging.log4j.core.osgi.Activator + @@ -443,7 +166,7 @@ jar - + ${manifestfile} @@ -456,52 +179,26 @@ org.apache ${maven.compiler.source} ${maven.compiler.target} - org.apache.logging.log4j.core - true + + + + + org.apache.maven.plugins + maven-resources-plugin + - default + default-resources - test-jar + resources - - - ${manifestfile} - - ${project.name} - ${project.version} - ${project.organization.name} - ${project.name} - ${project.version} - ${project.organization.name} - org.apache - ${maven.compiler.source} - ${maven.compiler.target} - - - + generate-resources - - org.apache.felix - maven-bundle-plugin - - - org.apache.logging.log4j.core - - org.apache.logging.log4j.core.* - - sun.reflect;resolution:=optional, - * - - org.apache.logging.log4j.core.osgi.Activator - - - @@ -509,7 +206,6 @@ org.apache.maven.plugins maven-changes-plugin - ${changes.plugin.version} @@ -518,14 +214,14 @@ - %URL%/show_bug.cgi?id=%ISSUE% + %URL%/%ISSUE% true + Appenders, Configuration, Configurators, Core, Filters, Layouts, Lookups, Pattern Converters, Reconfiguration org.apache.maven.plugins maven-checkstyle-plugin - ${checkstyle.plugin.version} ${log4jParentDir}/checkstyle.xml @@ -538,14 +234,15 @@ org.apache.maven.plugins maven-javadoc-plugin - ${javadoc.plugin.version} false - Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.
+ 8 + <p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br /> Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo, - and the Apache Log4j logo are trademarks of The Apache Software Foundation.

]]>
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>
+ ${javadoc.opts} false true @@ -585,22 +282,9 @@
- - org.codehaus.mojo - findbugs-maven-plugin - ${findbugs.plugin.version} - - true - -Duser.language=en - Normal - Default - ${log4jParentDir}/findbugs-exclude-filter.xml - - org.apache.maven.plugins maven-jxr-plugin - ${jxr.plugin.version} non-aggregate @@ -619,12 +303,14 @@ org.apache.maven.plugins maven-pmd-plugin - ${pmd.plugin.version} ${maven.compiler.target} + + com.github.spotbugs + spotbugs-maven-plugin +
- diff --git a/log4j-core/revapi.json b/log4j-core/revapi.json new file mode 100644 index 00000000000..1ee8659df3f --- /dev/null +++ b/log4j-core/revapi.json @@ -0,0 +1,36 @@ +[ + { + "extension": "revapi.java", + "configuration": { + "filter": { + "classes": { + "exclude": [ + "org\\.apache\\.logging\\.log4j\\.corel\\.impl\\.ContextAnchor", + "org\\.apache\\.logging\\.log4j\\.core\\.async\\.AsyncLoggerDisruptor", + "org\\.apache\\.logging\\.log4j\\.core\\.async\\.RingBufferLogEvent\\.Factory", + "org\\.apache\\.logging\\.log4j\\.core\\.layout\\.AbstractJacksonLayout", + "org\\.apache\\.logging\\.log4j\\.core\\.layout\\.AbstractJacksonLayout\\.ResolvableKeyValuePair", + "org\\.apache\\.logging\\.log4j\\.core\\.net\\.SmtpManager\\.FactoryData", + "org\\.apache\\.logging\\.log4j\\.core\\.net\\.TcpSocketManager\\.FactoryData", + "org\\.apache\\.logging\\.log4j\\.core\\.util\\.CronExpression\\.ValueSet", + "org\\.apache\\.logging\\.log4j\\.core\\.util\\.datetime\\.FastDatePrinter\\.NumberRule", + "org\\.apache\\.logging\\.log4j\\.core\\.util\\.datetime\\.FastDatePrinter\\.Rule" + ] + }, + "packages": { + "exclude": [ + "org.apache.logging.log4j.core.jmx", + "org.apache.logging.log4j.core.jackson", + "org.apache.logging.log4j.core.time.internal", + "org.apache.logging.log4j.core.util.datetime" + ] + } + } + } + }, + { + "extension": "revapi.ignore", + "configuration": [ + ] + } +] diff --git a/log4j-core/src/main/java/module-info.java b/log4j-core/src/main/java/module-info.java new file mode 100644 index 00000000000..6c291467115 --- /dev/null +++ b/log4j-core/src/main/java/module-info.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +import org.apache.logging.log4j.core.impl.DefaultCallback; +import org.apache.logging.log4j.core.impl.Log4jProvider; +import org.apache.logging.log4j.core.impl.ThreadContextDataProvider; +import org.apache.logging.log4j.core.message.ExtendedThreadInfoFactory; +import org.apache.logging.log4j.core.plugins.Log4jPlugins; +import org.apache.logging.log4j.core.script.ScriptManagerFactory; +import org.apache.logging.log4j.core.util.ContextDataProvider; +import org.apache.logging.log4j.core.util.WatchEventService; +import org.apache.logging.log4j.message.ThreadDumpMessage.ThreadInfoFactory; +import org.apache.logging.log4j.plugins.di.InjectorCallback; +import org.apache.logging.log4j.plugins.model.PluginService; +import org.apache.logging.log4j.spi.Provider; + +module org.apache.logging.log4j.core { + exports org.apache.logging.log4j.core; + exports org.apache.logging.log4j.core.appender; + exports org.apache.logging.log4j.core.appender.db; + exports org.apache.logging.log4j.core.appender.nosql; + exports org.apache.logging.log4j.core.appender.rewrite; + exports org.apache.logging.log4j.core.appender.rolling; + exports org.apache.logging.log4j.core.appender.rolling.action; + exports org.apache.logging.log4j.core.appender.routing; + exports org.apache.logging.log4j.core.async; + exports org.apache.logging.log4j.core.config; + exports org.apache.logging.log4j.core.config.arbiters; + exports org.apache.logging.log4j.core.config.builder.api; + exports org.apache.logging.log4j.core.config.builder.impl; + exports org.apache.logging.log4j.core.config.composite; + exports org.apache.logging.log4j.core.config.json; + exports org.apache.logging.log4j.core.config.plugins; + exports org.apache.logging.log4j.core.config.plugins.convert; + exports org.apache.logging.log4j.core.config.plugins.util; + exports org.apache.logging.log4j.core.config.plugins.visit; + exports org.apache.logging.log4j.core.config.properties; + exports org.apache.logging.log4j.core.config.status; + exports org.apache.logging.log4j.core.config.xml; + exports org.apache.logging.log4j.core.config.yaml; + exports org.apache.logging.log4j.core.filter; + exports org.apache.logging.log4j.core.impl; + exports org.apache.logging.log4j.core.jmx; + exports org.apache.logging.log4j.core.layout; + exports org.apache.logging.log4j.core.lookup; + exports org.apache.logging.log4j.core.message; + exports org.apache.logging.log4j.core.net; + exports org.apache.logging.log4j.core.net.ssl; + exports org.apache.logging.log4j.core.osgi; + exports org.apache.logging.log4j.core.parser; + exports org.apache.logging.log4j.core.pattern; + exports org.apache.logging.log4j.core.script; + exports org.apache.logging.log4j.core.selector; + exports org.apache.logging.log4j.core.time; + exports org.apache.logging.log4j.core.tools; + exports org.apache.logging.log4j.core.tools.picocli; + exports org.apache.logging.log4j.core.util; + exports org.apache.logging.log4j.core.util.datetime; + + // Required Dependencies + requires transitive org.apache.logging.log4j; + requires transitive org.apache.logging.log4j.plugins; + // Optional Dependencies + requires static java.desktop; + requires static java.management; + requires static java.sql; + requires static java.rmi; + requires static java.xml; + requires static com.lmax.disruptor; + requires static org.jctools.core; + requires static org.osgi.framework; + requires static com.conversantmedia.disruptor; + requires static com.fasterxml.jackson.core; + requires static com.fasterxml.jackson.databind; + requires static com.fasterxml.jackson.dataformat.xml; + requires static com.fasterxml.jackson.dataformat.yaml; + requires static org.apache.commons.compress; + requires static org.fusesource.jansi; + + // sun.misc.Unsafe::invokeCleaner via MemoryMappedFileManager + requires static jdk.unsupported; + + uses ContextDataProvider; + uses WatchEventService; + uses ScriptManagerFactory; + provides ThreadInfoFactory with ExtendedThreadInfoFactory; + provides ContextDataProvider with ThreadContextDataProvider; + provides Provider with Log4jProvider; + provides PluginService with Log4jPlugins; + provides InjectorCallback with DefaultCallback; +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java index cf2961d8cd2..c3aea238c19 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java @@ -27,7 +27,7 @@ * Wraps a {@link LifeCycle.State}. *

*/ -public class AbstractLifeCycle implements LifeCycle2 { +public class AbstractLifeCycle implements LifeCycle { public static final int DEFAULT_STOP_TIMEOUT = 0; public static final TimeUnit DEFAULT_STOP_TIMEUNIT = TimeUnit.MILLISECONDS; @@ -39,7 +39,7 @@ public class AbstractLifeCycle implements LifeCycle2 { /** * Gets the status logger. - * + * * @return the status logger. */ protected static org.apache.logging.log4j.Logger getStatusLogger() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLogEvent.java index b9dd2bbb532..35cbc94c65c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLogEvent.java @@ -16,9 +16,6 @@ */ package org.apache.logging.log4j.core; -import java.util.Collections; -import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -37,7 +34,7 @@ public abstract class AbstractLogEvent implements LogEvent { private static final long serialVersionUID = 1L; - private MutableInstant instant = new MutableInstant(); + private volatile MutableInstant instant; /** * Subclasses should implement this method to provide an immutable version. @@ -52,14 +49,6 @@ public ReadOnlyStringMap getContextData() { return null; } - /** - * Returns {@link Collections#emptyMap()}. - */ - @Override - public Map getContextMap() { - return Collections.emptyMap(); - } - @Override public ContextStack getContextStack() { return ThreadContext.EMPTY_STACK; @@ -127,6 +116,13 @@ public long getTimeMillis() { @Override public Instant getInstant() { + return getMutableInstant(); + } + + protected final MutableInstant getMutableInstant() { + if (instant == null) { + instant = new MutableInstant(); + } return instant; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Appender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Appender.java index 91a09328090..7ca57426b9e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Appender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Appender.java @@ -23,8 +23,8 @@ * as an {@link ErrorHandler}. Typical Appender implementations coordinate with an * implementation of {@link org.apache.logging.log4j.core.appender.AbstractManager} to handle external resources * such as streams, connections, and other shared state. As Appenders are plugins, concrete implementations need to - * be annotated with {@link org.apache.logging.log4j.core.config.plugins.Plugin} and need to provide a static - * factory method annotated with {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}. + * be annotated with {@link org.apache.logging.log4j.plugins.Plugin} and need to provide a static + * factory method annotated with {@link org.apache.logging.log4j.plugins.PluginFactory}. * *

Most core plugins are written using a related Manager class that handle the actual task of serializing a * {@link LogEvent} to some output location. For instance, many Appenders can take @@ -37,12 +37,17 @@ public interface Appender extends LifeCycle { /** - * Main {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() plugin element type} for + * Main {@linkplain org.apache.logging.log4j.plugins.Configurable#elementType() plugin element type} for * Appender plugins. * * @since 2.6 */ String ELEMENT_TYPE = "appender"; + + /** + * Empty array. + */ + Appender[] EMPTY_ARRAY = {}; /** * Logs a LogEvent using whatever logic this Appender wishes to use. It is typically recommended to use a @@ -89,4 +94,12 @@ public interface Appender extends LifeCycle { * @param handler the ErrorHandler to use for handling exceptions. */ void setHandler(ErrorHandler handler); + + /** + * Appenders should return true if they require location information. + * @return true if the Appender requires location information. + */ + default boolean requiresLocation() { + return false; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java index 53512451e35..39da73a9198 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java @@ -16,17 +16,20 @@ */ package org.apache.logging.log4j.core; -import java.util.List; - import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; import org.apache.logging.log4j.core.impl.ThreadContextDataInjector; +import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.StringMap; +import java.util.List; + /** * Responsible for initializing the context data of LogEvents. Context data is data that is set by the application to be * included in all subsequent log events. + *

NOTE: It is no longer recommended that custom implementations of this interface be provided as it is + * difficult to do. Instead, provide a custom ContextDataProvider.

*

* The source of the context data is implementation-specific. The default source for context data is the ThreadContext. *

@@ -53,6 +56,8 @@ * @since 2.7 */ public interface ContextDataInjector { + Key KEY = new Key<>() {}; + /** * Returns a {@code StringMap} object initialized with the specified properties and the appropriate * context data. The returned value may be the specified parameter or a different object. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java index 6a26c0460df..dca48ec14cb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Core.java @@ -14,11 +14,18 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.model.PluginNamespace; + public class Core { - public static final String CATEGORY_NAME = "Core"; + @Deprecated + public static final String CATEGORY_NAME = Node.CORE_NAMESPACE; + + public static final Key PLUGIN_NAMESPACE_KEY = new @Namespace(Node.CORE_NAMESPACE) Key<>() {}; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/DefaultLoggerContextAccessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/DefaultLoggerContextAccessor.java index 4401247e0e6..ef9837e26f7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/DefaultLoggerContextAccessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/DefaultLoggerContextAccessor.java @@ -29,7 +29,7 @@ public class DefaultLoggerContextAccessor implements LoggerContextAccessor { /* * Returns the current LoggerContext. - * + * * @see org.apache.logging.log4j.core.LoggerContextAccessor#getLoggerContext() */ @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Filter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Filter.java index 028649846e7..104263d3e5a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Filter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Filter.java @@ -14,12 +14,12 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.EnglishEnums; /** @@ -33,7 +33,12 @@ public interface Filter extends LifeCycle { /** - * Main {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() plugin element type} for + * Empty array. + */ + Filter[] EMPTY_ARRAY = {}; + + /** + * Main {@linkplain org.apache.logging.log4j.plugins.Configurable#elementType() plugin element type} for * Filter plugins. * * @since 2.1 @@ -294,6 +299,18 @@ Result filter(Logger logger, Level level, Marker marker, String message, Object */ Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t); + /** + * Filter an event. + * @param logger The Logger. + * @param level The event logging Level. + * @param marker The Marker for the event or null. + * @param msg The Message + * @return the Result. + */ + default Result filter(Logger logger, Level level, Marker marker, String msg) { + return filter(logger, level, marker, msg, Constants.EMPTY_OBJECT_ARRAY); + } + /** * Filter an event. * @param event The Event to filter on. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java index 73b5d6f1b1d..d63159687a5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java @@ -47,7 +47,7 @@ public interface Layout extends Encoder { /** - * Main {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() plugin element type} for + * Main {@linkplain org.apache.logging.log4j.plugins.Configurable#elementType() plugin element type} for * Layout plugins. * * @since 2.1 @@ -96,4 +96,12 @@ public interface Layout extends Encoder { * format descriptors are specified. */ Map getContentFormat(); + + /** + * Indicates whether this Layout requires location information. + * @return returns true if the Layout requires location information. + */ + default boolean requiresLocation() { + return false; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java index 4aaae4f8598..87467c00c89 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java @@ -17,6 +17,8 @@ package org.apache.logging.log4j.core; +import java.util.concurrent.TimeUnit; + /** * All proper Java frameworks implement some sort of object life cycle. In Log4j, the main interface for handling * the life cycle context of an object is this one. An object first starts in the {@link State#INITIALIZED} state @@ -66,4 +68,15 @@ enum State { boolean isStopped(); + /** + * Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current + * thread is interrupted, whichever happens first. + * + * @param timeout the maximum time to wait + * @param timeUnit the time unit of the timeout argument + * @return true if the receiver was stopped cleanly and normally, false otherwise. + * @since 2.7 + */ + boolean stop(long timeout, TimeUnit timeUnit); + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle2.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle2.java deleted file mode 100644 index 0046a62f6e2..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle2.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core; - -import java.util.concurrent.TimeUnit; - -/** - * Extends the LifeCycle interface. - *

- * This interface should be merged with the super-interface in 3.0. - *

- * @since 2.7 - */ -public interface LifeCycle2 extends LifeCycle { - - /** - * Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current - * thread is interrupted, whichever happens first. - * - * @param timeout the maximum time to wait - * @param timeUnit the time unit of the timeout argument - * @return true if the receiver was stopped cleanly and normally, false otherwise. - * @since 2.7 - */ - boolean stop(long timeout, TimeUnit timeUnit); -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java index 0879f40a1b3..b73e7874ccd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java @@ -18,7 +18,6 @@ package org.apache.logging.log4j.core; import java.io.Serializable; -import java.util.Map; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -30,41 +29,25 @@ /** * Provides contextual information about a logged message. A LogEvent must be {@link java.io.Serializable} so that it - * may be transmitted over a network connection, output in a - * {@link org.apache.logging.log4j.core.layout.SerializedLayout}, and many other uses. Besides containing a + * may be transmitted over a network connection. Besides containing a * {@link org.apache.logging.log4j.message.Message}, a LogEvent has a corresponding * {@link org.apache.logging.log4j.Level} that the message was logged at. If a * {@link org.apache.logging.log4j.Marker} was used, then it is included here. The contents of the * {@link org.apache.logging.log4j.ThreadContext} at the time of the log call are provided via - * {@link #getContextMap()} and {@link #getContextStack()}. If a {@link java.lang.Throwable} was included in the log + * {@link #getContextData()} and {@link #getContextStack()}. If a {@link java.lang.Throwable} was included in the log * call, then it is provided via {@link #getThrown()}. When this class is serialized, the attached Throwable will * be wrapped into a {@link org.apache.logging.log4j.core.impl.ThrowableProxy} so that it may be safely serialized * and deserialized properly without causing problems if the exception class is not available on the other end. - *

- * Since version 2.7, {@link #getContextMap()} is deprecated in favor of {@link #getContextData()}, which - * can carry both {@code ThreadContext} data as well as other context data supplied by the - * {@linkplain org.apache.logging.log4j.core.impl.ContextDataInjectorFactory configured} - * {@link ContextDataInjector}. - *

*/ public interface LogEvent extends Serializable { /** * Returns an immutable version of this log event, which MAY BE a copy of this event. - * + * * @return an immutable version of this log event */ LogEvent toImmutable(); - /** - * Gets the context map (also know as Mapped Diagnostic Context or MDC). - * - * @return The context map, never {@code null}. - * @deprecated use {@link #getContextData()} instead - */ - @Deprecated - Map getContextMap(); - /** * Returns the {@code ReadOnlyStringMap} object holding context data key-value pairs. *

@@ -133,15 +116,15 @@ public interface LogEvent extends Serializable { long getTimeMillis(); /** - * Returns the timestamp when the message was logged. + * Returns the Instant when the message was logged. *

* Caution: if this {@code LogEvent} implementation is mutable and reused for multiple consecutive log messages, * then the {@code Instant} object returned by this method is also mutable and reused. * Client code should not keep a reference to the returned object but make a copy instead. *

* - * @return the {@code Instant} holding timestamp details for this log event - * @since 2.11 + * @return the {@code Instant} holding Instant details for this log event + * @since 2.11.0 */ Instant getInstant(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java index ef90b404cab..de55a524039 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java @@ -19,6 +19,7 @@ import java.io.ObjectStreamException; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -118,7 +119,7 @@ public synchronized void setLevel(final Level level) { if (level == getLevel()) { return; } - Level actualLevel; + final Level actualLevel; if (level != null) { actualLevel = level; } else { @@ -138,6 +139,11 @@ public LoggerConfig get() { return privateConfig.loggerConfig; } + @Override + protected boolean requiresLocation() { + return privateConfig.requiresLocation; + } + @Override public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) { @@ -146,6 +152,13 @@ public void logMessage(final String fqcn, final Level level, final Marker marker strategy.log(this, getName(), fqcn, marker, level, msg, t); } + @Override + protected void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location, + final Message message, final Throwable throwable) { + final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy(); + strategy.log(this, getName(), fqcn, location, marker, level, message, throwable); + } + @Override public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { return privateConfig.filter(level, marker, message, t); @@ -280,7 +293,7 @@ public Map getAppenders() { public Iterator getFilters() { final Filter filter = privateConfig.loggerConfig.getFilter(); if (filter == null) { - return new ArrayList().iterator(); + return Collections.emptyIterator(); } else if (filter instanceof CompositeFilter) { return ((CompositeFilter) filter).iterator(); } else { @@ -377,6 +390,7 @@ protected class PrivateConfig { private final Level loggerConfigLevel; private final int intLevel; private final Logger logger; + private final boolean requiresLocation; public PrivateConfig(final Configuration config, final Logger logger) { this.config = config; @@ -384,6 +398,7 @@ public PrivateConfig(final Configuration config, final Logger logger) { this.loggerConfigLevel = this.loggerConfig.getLevel(); this.intLevel = this.loggerConfigLevel.intLevel(); this.logger = logger; + this.requiresLocation = this.loggerConfig.requiresLocation(); } public PrivateConfig(final PrivateConfig pc, final Level level) { @@ -392,6 +407,7 @@ public PrivateConfig(final PrivateConfig pc, final Level level) { this.loggerConfigLevel = level; this.intLevel = this.loggerConfigLevel.intLevel(); this.logger = pc.logger; + this.requiresLocation = this.loggerConfig.requiresLocation(); } public PrivateConfig(final PrivateConfig pc, final LoggerConfig lc) { @@ -400,6 +416,7 @@ public PrivateConfig(final PrivateConfig pc, final LoggerConfig lc) { this.loggerConfigLevel = lc.getLevel(); this.intLevel = this.loggerConfigLevel.intLevel(); this.logger = pc.logger; + this.requiresLocation = this.loggerConfig.requiresLocation(); } // LOG4J2-151: changed visibility to public diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java index 8234024393b..ae6e8312f05 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java @@ -16,14 +16,15 @@ */ package org.apache.logging.log4j.core; -import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER; - import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.lang.ref.WeakReference; import java.net.URI; import java.util.Collection; +import java.util.List; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; @@ -31,6 +32,7 @@ import java.util.concurrent.locks.ReentrantLock; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationListener; @@ -38,21 +40,25 @@ import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.config.NullConfiguration; import org.apache.logging.log4j.core.config.Reconfigurable; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.jmx.Server; import org.apache.logging.log4j.core.util.Cancellable; import org.apache.logging.log4j.core.util.ExecutorServices; import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.spi.LoggerContextFactory; +import org.apache.logging.log4j.spi.LoggerContextShutdownAware; +import org.apache.logging.log4j.spi.LoggerContextShutdownEnabled; import org.apache.logging.log4j.spi.LoggerRegistry; import org.apache.logging.log4j.spi.Terminable; -import org.apache.logging.log4j.spi.ThreadContextMapFactory; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; +import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER; /** * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by @@ -60,33 +66,29 @@ * filters, etc and will be atomically updated whenever a reconfigure occurs. */ public class LoggerContext extends AbstractLifeCycle - implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener { - - static { - try { - // LOG4J2-1642 preload ExecutorServices as it is used in shutdown hook - LoaderUtil.loadClass(ExecutorServices.class.getName()); - } catch (final Exception e) { - LOGGER.error("Failed to preload ExecutorServices class.", e); - } - } + implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener, + LoggerContextShutdownEnabled { /** * Property name of the property change event fired if the configuration is changed. */ public static final String PROPERTY_CONFIG = "config"; + public static final Key> KEY = new Key<>() {}; private static final Configuration NULL_CONFIGURATION = new NullConfiguration(); private final LoggerRegistry loggerRegistry = new LoggerRegistry<>(); private final CopyOnWriteArrayList propertyChangeListeners = new CopyOnWriteArrayList<>(); + private volatile List listeners; + private final Injector injector; /** * The Configuration is volatile to guarantee that initialization of the Configuration has completed before the * reference is updated. */ private volatile Configuration configuration = new DefaultConfiguration(); - private Object externalContext; + private static final String EXTERNAL_CONTEXT_KEY = "__EXTERNAL_CONTEXT_KEY__"; + private final ConcurrentMap externalMap = new ConcurrentHashMap<>(); private String contextName; private volatile URI configLocation; private Cancellable shutdownCallback; @@ -120,9 +122,27 @@ public LoggerContext(final String name, final Object externalContext) { * @param configLocn The location of the configuration as a URI. */ public LoggerContext(final String name, final Object externalContext, final URI configLocn) { + this(name, externalContext, configLocn, DI.createInjector()); + injector.init(); + injector.registerBindingIfAbsent(KEY, () -> new WeakReference<>(this)); + } + + /** + * Constructs a LoggerContext with a name, external context, configuration URI, and an Injector. + * + * @param name context name + * @param externalContext external context or null + * @param configLocn location of configuration as a URI + * @param injector initialized Injector instance + */ + public LoggerContext(final String name, final Object externalContext, final URI configLocn, final Injector injector) { this.contextName = name; - this.externalContext = externalContext; + if (externalContext != null) { + externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext); + } this.configLocation = configLocn; + this.injector = injector.copy(); + injector.registerBindingIfAbsent(KEY, () -> new WeakReference<>(this)); } /** @@ -134,8 +154,25 @@ public LoggerContext(final String name, final Object externalContext, final URI * @param configLocn The configuration location. */ public LoggerContext(final String name, final Object externalContext, final String configLocn) { + this(name, externalContext, configLocn, DI.createInjector()); + injector.init(); + injector.registerBindingIfAbsent(KEY, () -> new WeakReference<>(this)); + } + + /** + * Constructs a LoggerContext with a name, external context, configuration location string, and an Injector. + * The location must be resolvable to a File. + * + * @param name context name + * @param externalContext external context or null + * @param configLocn configuration location + * @param injector initialized Injector instance + */ + public LoggerContext(final String name, final Object externalContext, final String configLocn, final Injector injector) { this.contextName = name; - this.externalContext = externalContext; + if (externalContext != null) { + externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext); + } if (configLocn != null) { URI uri; try { @@ -147,6 +184,23 @@ public LoggerContext(final String name, final Object externalContext, final Stri } else { configLocation = null; } + this.injector = injector.copy(); + this.injector.registerBindingIfAbsent(KEY, () -> new WeakReference<>(this)); + } + + public void addShutdownListener(final LoggerContextShutdownAware listener) { + if (listeners == null) { + synchronized(this) { + if (listeners == null) { + listeners = new CopyOnWriteArrayList<>(); + } + } + } + listeners.add(listener); + } + + public List getListeners() { + return listeners; } /** @@ -219,8 +273,8 @@ public static LoggerContext getContext(final ClassLoader loader, final boolean c @Override public void start() { - LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this); - if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) { + LOGGER.debug("Starting {}...", this); + if (PropertiesUtil.getProperties().getBooleanProperty(Log4jProperties.LOGGER_CONTEXT_STACKTRACE_ON_START, false)) { LOGGER.debug("Stack trace to locate invoker", new Exception("Not a real error, showing stack trace to locate invoker")); } @@ -238,7 +292,7 @@ public void start() { configLock.unlock(); } } - LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this); + LOGGER.debug("{} started OK.", this); } /** @@ -247,11 +301,11 @@ public void start() { * @param config The new Configuration. */ public void start(final Configuration config) { - LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config); + LOGGER.debug("Starting {} with configuration {}...", this, config); if (configLock.tryLock()) { try { if (this.isInitialized() || this.isStopped()) { - if (this.configuration.isShutdownHookEnabled()) { + if (config.isShutdownHookEnabled()) { setUpShutdownHook(); } this.setStarted(); @@ -261,7 +315,7 @@ public void start(final Configuration config) { } } setConfiguration(config); - LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config); + LOGGER.debug("{} started OK with configuration {}.", this, config); } private void setUpShutdownHook() { @@ -269,6 +323,8 @@ private void setUpShutdownHook() { final LoggerContextFactory factory = LogManager.getFactory(); if (factory instanceof ShutdownCallbackRegistry) { LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one."); + // LOG4J2-1642 preload ExecutorServices as it is used in shutdown hook + ExecutorServices.ensureInitialized(); try { final long shutdownTimeoutMillis = this.configuration.getShutdownTimeoutMillis(); this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() { @@ -276,8 +332,7 @@ private void setUpShutdownHook() { public void run() { @SuppressWarnings("resource") final LoggerContext context = LoggerContext.this; - LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]", - context.getName(), context); + LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping {}", context); context.stop(shutdownTimeoutMillis, TimeUnit.MILLISECONDS); } @@ -328,7 +383,7 @@ public void terminate() { */ @Override public boolean stop(final long timeout, final TimeUnit timeUnit) { - LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this); + LOGGER.debug("Stopping {}...", this); configLock.lock(); try { if (this.isStopped()) { @@ -349,18 +404,23 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { final Configuration prev = configuration; configuration = NULL_CONFIGURATION; updateLoggers(); - if (prev instanceof LifeCycle2) { - ((LifeCycle2) prev).stop(timeout, timeUnit); - } else { - prev.stop(); - } - externalContext = null; + prev.stop(timeout, timeUnit); + externalMap.clear(); LogManager.getFactory().removeContext(this); } finally { configLock.unlock(); this.setStopped(); } - LOGGER.debug("Stopped LoggerContext[name={}, {}] with status {}", getName(), this, true); + if (listeners != null) { + for (final LoggerContextShutdownAware listener : listeners) { + try { + listener.contextShutdown(this); + } catch (final Exception ex) { + // Ignore the exception. + } + } + } + LOGGER.debug("Stopped {} with status {}", this, true); return true; } @@ -389,7 +449,32 @@ public Logger getRootLogger() { * @throws NullPointerException if the specified name is {@code null} */ public void setName(final String name) { - contextName = Objects.requireNonNull(name); + contextName = Objects.requireNonNull(name); + } + + @Override + public Object getObject(final String key) { + return externalMap.get(key); + } + + @Override + public Object putObject(final String key, final Object value) { + return externalMap.put(key, value); + } + + @Override + public Object putObjectIfAbsent(final String key, final Object value) { + return externalMap.putIfAbsent(key, value); + } + + @Override + public Object removeObject(final String key) { + return externalMap.remove(key); + } + + @Override + public boolean removeObject(final String key, final Object value) { + return externalMap.remove(key, value); } /** @@ -398,7 +483,11 @@ public void setName(final String name) { * @param context The external context. */ public void setExternalContext(final Object context) { - this.externalContext = context; + if (context != null) { + this.externalMap.put(EXTERNAL_CONTEXT_KEY, context); + } else { + this.externalMap.remove(EXTERNAL_CONTEXT_KEY); + } } /** @@ -408,7 +497,7 @@ public void setExternalContext(final Object context) { */ @Override public Object getExternalContext() { - return this.externalContext; + return this.externalMap.get(EXTERNAL_CONTEXT_KEY); } /** @@ -457,6 +546,26 @@ public Logger getLogger(final String name, final MessageFactory messageFactory) return loggerRegistry.getLogger(name, messageFactory); } + /** + * Gets the LoggerRegistry. + * + * @return the LoggerRegistry. + * @since 2.17.2 + */ + public LoggerRegistry getLoggerRegistry() { + return loggerRegistry; + } + + /** + * Gets the Injector. + * + * @return the Injector + * @since 3.0.0 + */ + public Injector getInjector() { + return injector; + } + /** * Determines if the specified Logger exists. * @@ -493,7 +602,8 @@ public boolean hasLogger(final String name, final Class map = config.getComponent(Configuration.CONTEXT_PROPERTIES); try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException - map.putIfAbsent("hostName", NetUtils.getLocalHostname()); + // LOG4J2-2808 don't block unless necessary + map.computeIfAbsent("hostName", s -> NetUtils.getLocalHostname()); } catch (final Exception ex) { LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString()); map.putIfAbsent("hostName", "unknown"); @@ -560,8 +671,6 @@ private Configuration setConfiguration(final Configuration config) { // LOG4J2-716: Android has no java.lang.management LOGGER.error("Could not reconfigure JMX", e); } - // AsyncLoggers update their nanoClock when the configuration changes - Log4jLogEvent.setNanoClock(configuration.getNanoClock()); return prev; } finally { @@ -609,10 +718,12 @@ public void setConfigLocation(final URI configLocation) { * Reconfigures the context. */ private void reconfigure(final URI configURI) { - final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; - LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}", - contextName, configURI, this, cl); - final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl); + final Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY); + final ClassLoader cl = externalContext instanceof ClassLoader ? (ClassLoader) externalContext : null; + LOGGER.debug("Reconfiguration started for {} at URI {} with optional ClassLoader: {}", + this, configURI, cl); + final Configuration instance = + injector.getInstance(ConfigurationFactory.KEY).getConfiguration(this, contextName, configURI, cl); if (instance == null) { LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl); } else { @@ -622,8 +733,8 @@ private void reconfigure(final URI configURI) { * old.stop(); } */ final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource()); - LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}", - contextName, location, this, cl); + LOGGER.debug("Reconfiguration complete for {} at URI {} with optional ClassLoader: {}", + this, location, cl); } } @@ -636,6 +747,17 @@ public void reconfigure() { reconfigure(configLocation); } + public void reconfigure(final Configuration configuration) { + setConfiguration(configuration); + final ConfigurationSource source = configuration.getConfigurationSource(); + if (source != null) { + final URI uri = source.getURI(); + if (uri != null) { + configLocation = uri; + } + } + } + /** * Causes all Loggers to be updated against the current Configuration. */ @@ -663,24 +785,31 @@ public void updateLoggers(final Configuration config) { */ @Override public synchronized void onChange(final Reconfigurable reconfigurable) { + final long startMillis = System.currentTimeMillis(); LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this); initApiModule(); final Configuration newConfig = reconfigurable.reconfigure(); if (newConfig != null) { setConfiguration(newConfig); - LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this); + LOGGER.debug("Reconfiguration completed for {} ({}) in {} milliseconds.", contextName, this, + System.currentTimeMillis() - startMillis); } else { - LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this); + LOGGER.debug("Reconfiguration failed for {} ({}) in {} milliseconds.", contextName, this, + System.currentTimeMillis() - startMillis); } } - private void initApiModule() { - ThreadContextMapFactory.init(); // Or make public and call ThreadContext.init() which calls ThreadContextMapFactory.init(). + @Override + public String toString() { + return "LoggerContext[" + contextName + "]"; + } + + private void initApiModule() { + ThreadContext.init(); } // LOG4J2-151: changed visibility from private to protected protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { return new Logger(ctx, name, messageFactory); } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/StringLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/StringLayout.java index 3eb99e8ff51..355ba6f2045 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/StringLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/StringLayout.java @@ -25,7 +25,7 @@ public interface StringLayout extends Layout { /** * Gets the Charset this layout uses to encode Strings into bytes. - * + * * @return the Charset this layout uses to encode Strings into bytes. */ Charset getCharset(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Version.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Version.java new file mode 100644 index 00000000000..8f4e3313650 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Version.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core; + +public class Version { + + public static void main(final String[] args) { + System.out.println(getProductString()); + } + + public static String getProductString() { + final Package pkg = Version.class.getPackage(); + if (pkg == null || pkg.getSpecificationTitle() == null) { + return "Apache Log4j"; + } + return String.format("%s %s", pkg.getSpecificationTitle(), pkg.getSpecificationVersion()); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java index 57974bdf1c9..98274243a9a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java @@ -26,10 +26,11 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.filter.AbstractFilterable; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.util.Integers; @@ -41,15 +42,15 @@ public abstract class AbstractAppender extends AbstractFilterable implements Appender { /** - * Subclasses can extend this abstract Builder. - * + * Subclasses can extend this abstract Builder. + * * @param The type to build. */ public abstract static class Builder> extends AbstractFilterable.Builder { @PluginBuilderAttribute private boolean ignoreExceptions = true; - + @PluginElement("Layout") private Layout layout; @@ -72,17 +73,17 @@ public Layout getLayout() { return layout; } - public B withName(final String name) { + public B setName(final String name) { this.name = name; return asBuilder(); } - public B withIgnoreExceptions(final boolean ignoreExceptions) { + public B setIgnoreExceptions(final boolean ignoreExceptions) { this.ignoreExceptions = ignoreExceptions; return asBuilder(); } - public B withLayout(final Layout layout) { + public B setLayout(final Layout layout) { this.layout = layout; return asBuilder(); } @@ -93,23 +94,14 @@ public Layout getOrCreateLayout() { } return layout; } - + public Layout getOrCreateLayout(final Charset charset) { if (layout == null) { - return PatternLayout.newBuilder().withCharset(charset).build(); + return PatternLayout.newBuilder().setCharset(charset).build(); } return layout; } - /** - * @deprecated Use {@link #setConfiguration(Configuration)} - */ - @Deprecated - public B withConfiguration(final Configuration configuration) { - this.configuration = configuration; - return asBuilder(); - } - public B setConfiguration(final Configuration configuration) { this.configuration = configuration; return asBuilder(); @@ -118,40 +110,59 @@ public B setConfiguration(final Configuration configuration) { public Configuration getConfiguration() { return configuration; } - + } - + private final String name; private final boolean ignoreExceptions; private final Layout layout; private ErrorHandler handler = new DefaultErrorHandler(this); + /** + * Constructor. + * + * @param name The Appender name. + * @param filter The Filter to associate with the Appender. + * @param layout The layout to use to format the event. + * @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be logged and + * then passed to the application. + * @param properties Optional properties + */ + protected AbstractAppender(final String name, final Filter filter, final Layout layout, + final boolean ignoreExceptions, final Property[] properties) { + super(filter, properties); + this.name = Objects.requireNonNull(name, "name"); + this.layout = layout; + this.ignoreExceptions = ignoreExceptions; + } + /** * Constructor that defaults to suppressing exceptions. - * + * * @param name The Appender name. * @param filter The Filter to associate with the Appender. * @param layout The layout to use to format the event. + * @deprecated Use {@link #AbstractAppender(String, Filter, Layout, boolean, Property[])}. */ + @Deprecated protected AbstractAppender(final String name, final Filter filter, final Layout layout) { - this(name, filter, layout, true); + this(name, filter, layout, true, Property.EMPTY_ARRAY); } /** * Constructor. - * + * * @param name The Appender name. * @param filter The Filter to associate with the Appender. * @param layout The layout to use to format the event. * @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be logged and * then passed to the application. + * @deprecated Use {@link #AbstractAppender(String, Filter, Layout, boolean, Property[])} */ + @Deprecated protected AbstractAppender(final String name, final Filter filter, final Layout layout, final boolean ignoreExceptions) { - super(filter); - this.name = Objects.requireNonNull(name, "name"); - this.layout = layout; - this.ignoreExceptions = ignoreExceptions; + this(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); } public static int parseInt(final String s, final int defaultValue) { @@ -163,9 +174,14 @@ public static int parseInt(final String s, final int defaultValue) { } } + @Override + public boolean requiresLocation() { + return layout != null && layout.requiresLocation(); + } + /** * Handle an error with a message using the {@link ErrorHandler} configured for this Appender. - * + * * @param msg The message. */ public void error(final String msg) { @@ -175,7 +191,7 @@ public void error(final String msg) { /** * Handle an error with a message, exception, and a logging event, using the {@link ErrorHandler} configured for * this Appender. - * + * * @param msg The message. * @param event The LogEvent. * @param t The Throwable. @@ -186,7 +202,7 @@ public void error(final String msg, final LogEvent event, final Throwable t) { /** * Handle an error with a message and an exception using the {@link ErrorHandler} configured for this Appender. - * + * * @param msg The message. * @param t The Throwable. */ @@ -196,7 +212,7 @@ public void error(final String msg, final Throwable t) { /** * Returns the ErrorHandler, if any. - * + * * @return The ErrorHandler. */ @Override @@ -206,7 +222,7 @@ public ErrorHandler getHandler() { /** * Returns the Layout for the appender. - * + * * @return The Layout used to format the event. */ @Override @@ -216,7 +232,7 @@ public Layout getLayout() { /** * Returns the name of the Appender. - * + * * @return The name of the Appender. */ @Override @@ -237,13 +253,14 @@ public boolean ignoreExceptions() { /** * The handler must be set before the appender is started. - * + * * @param handler The ErrorHandler to use. */ @Override public void setHandler(final ErrorHandler handler) { if (handler == null) { LOGGER.error("The handler cannot be set to null"); + return; } if (isStarted()) { LOGGER.error("The handler cannot be changed once the appender is started"); @@ -254,7 +271,7 @@ public void setHandler(final ErrorHandler handler) { /** * Serializes the given event using the appender's layout if present. - * + * * @param event * the event to serialize. * @return the serialized event or null if no layout is present. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractFileAppender.java index 09127c39ed5..83ca21ee99d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractFileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractFileAppender.java @@ -23,8 +23,9 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.net.Advertiser; /** @@ -34,7 +35,7 @@ public abstract class AbstractFileAppender extend /** * Builds FileAppender instances. - * + * * @param * The type to build */ @@ -104,53 +105,53 @@ public String getFileGroup() { return fileGroup; } - public B withAdvertise(final boolean advertise) { + public B setAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } - public B withAdvertiseUri(final String advertiseUri) { + public B setAdvertiseUri(final String advertiseUri) { this.advertiseUri = advertiseUri; return asBuilder(); } - public B withAppend(final boolean append) { + public B setAppend(final boolean append) { this.append = append; return asBuilder(); } - public B withFileName(final String fileName) { + public B setFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } - public B withCreateOnDemand(final boolean createOnDemand) { + public B setCreateOnDemand(final boolean createOnDemand) { this.createOnDemand = createOnDemand; return asBuilder(); } - public B withLocking(final boolean locking) { + public B setLocking(final boolean locking) { this.locking = locking; return asBuilder(); } - public B withFilePermissions(final String filePermissions) { + public B setFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } - public B withFileOwner(final String fileOwner) { + public B setFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } - public B withFileGroup(final String fileGroup) { + public B setFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return asBuilder(); } } - + private final String fileName; private final Advertiser advertiser; @@ -159,9 +160,9 @@ public B withFileGroup(final String fileGroup) { private AbstractFileAppender(final String name, final Layout layout, final Filter filter, final M manager, final String filename, final boolean ignoreExceptions, - final boolean immediateFlush, final Advertiser advertiser) { + final boolean immediateFlush, final Advertiser advertiser, final Property[] properties) { - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager); if (advertiser != null) { final Map configuration = new HashMap<>(layout.getContentFormat()); configuration.putAll(manager.getContentFormat()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java index 5b146f8d4aa..b74c8b14f03 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java @@ -36,7 +36,7 @@ * This class implements {@link AutoCloseable} mostly to allow unit tests to be written safely and succinctly. While * managers do need to allocate resources (usually on construction) and then free these resources, a manager is longer * lived than other auto-closeable objects like streams. None the less, making a manager AutoCloseable forces readers to - * be aware of the the pattern: allocate resources on construction and call {@link #close()} at some point. + * be aware of the pattern: allocate resources on construction and call {@link #close()} at some point. *

*/ public abstract class AbstractManager implements AutoCloseable { @@ -126,6 +126,11 @@ public static M getManager(final String name, fin } } + /** + * Used by Log4j to update the Manager during reconfiguration. This method should be considered private. + * Implementations may not be thread safe. This method may be made protected in a future release. + * @param data The data to update. + */ public void updateData(final Object data) { // This default implementation does nothing. } @@ -164,6 +169,10 @@ protected static M narrow(final Class narrowClass manager.getName() + "'"); } + protected static StatusLogger logger() { + return StatusLogger.getLogger(); + } + /** * May be overridden by managers to perform processing while the manager is being released and the * lock is held. A timeout is passed for implementors to use as they see fit. @@ -191,15 +200,6 @@ public LoggerContext getLoggerContext() { return loggerContext; } - /** - * Called to signify that this Manager is no longer required by an Appender. - * @deprecated In 2.7, use {@link #close()}. - */ - @Deprecated - public void release() { - close(); - } - /** * Returns the name of the Manager. * @return The name of the Manager. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java index 1e6f3e4108f..9189af9231e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java @@ -22,7 +22,8 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.util.Constants; /** @@ -33,12 +34,12 @@ public abstract class AbstractOutputStreamAppender extends AbstractAppender { /** - * Subclasses can extend this abstract Builder. - * + * Subclasses can extend this abstract Builder. + * * @param The type to build. */ public abstract static class Builder> extends AbstractAppender.Builder { - + @PluginBuilderAttribute private boolean bufferedIo = true; @@ -59,24 +60,24 @@ public boolean isBufferedIo() { public boolean isImmediateFlush() { return immediateFlush; } - - public B withImmediateFlush(final boolean immediateFlush) { + + public B setImmediateFlush(final boolean immediateFlush) { this.immediateFlush = immediateFlush; return asBuilder(); } - - public B withBufferedIo(final boolean bufferedIo) { + + public B setBufferedIo(final boolean bufferedIo) { this.bufferedIo = bufferedIo; return asBuilder(); } - public B withBufferSize(final int bufferSize) { + public B setBufferSize(final int bufferSize) { this.bufferSize = bufferSize; return asBuilder(); } } - + /** * Immediate flush means that the underlying writer or output stream will be flushed at the end of each append * operation. Immediate flush is slower but ensures that each append request is actually written. If @@ -93,11 +94,13 @@ public B withBufferSize(final int bufferSize) { * * @param name The name of the Appender. * @param layout The layout to format the message. + * @param properties Optional properties. * @param manager The OutputStreamManager. */ protected AbstractOutputStreamAppender(final String name, final Layout layout, - final Filter filter, final boolean ignoreExceptions, final boolean immediateFlush, final M manager) { - super(name, filter, layout, ignoreExceptions); + final Filter filter, final boolean ignoreExceptions, final boolean immediateFlush, final Property[] properties, + final M manager) { + super(name, filter, layout, ignoreExceptions, properties); this.manager = manager; this.immediateFlush = immediateFlush; } @@ -160,7 +163,7 @@ public void append(final LogEvent event) { try { tryAppend(event); } catch (final AppenderLoggingException ex) { - error("Unable to write to stream " + manager.getName() + " for appender " + getName() + ": " + ex); + error("Unable to write to stream " + manager.getName() + " for appender " + getName(), event, ex); throw ex; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java index 17a0bb43e77..6e4d5af9381 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java @@ -1,126 +1,128 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.appender; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.StringLayout; - -/** - * Appends log events as strings to a writer. - * - * @param - * The kind of {@link WriterManager} under management - */ -public abstract class AbstractWriterAppender extends AbstractAppender { - - /** - * Immediate flush means that the underlying writer will be flushed at the - * end of each append operation. Immediate flush is slower but ensures that - * each append request is actually written. If immediateFlush - * is set to {@code false}, then there is a good chance that the last few - * logs events are not actually written to persistent media if and when the - * application crashes. - */ - protected final boolean immediateFlush; - private final M manager; - private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); - private final Lock readLock = readWriteLock.readLock(); - - /** - * Instantiates. - * - * @param name - * The name of the Appender. - * @param layout - * The layout to format the message. - * @param manager - * The OutputStreamManager. - */ - protected AbstractWriterAppender(final String name, final StringLayout layout, final Filter filter, - final boolean ignoreExceptions, final boolean immediateFlush, final M manager) { - super(name, filter, layout, ignoreExceptions); - this.manager = manager; - this.immediateFlush = immediateFlush; - } - - /** - * Actual writing occurs here. - *

- * Most subclasses will need to override this method. - *

- * - * @param event - * The LogEvent. - */ - @Override - public void append(final LogEvent event) { - readLock.lock(); - try { - final String str = getStringLayout().toSerializable(event); - if (str.length() > 0) { - manager.write(str); - if (this.immediateFlush || event.isEndOfBatch()) { - manager.flush(); - } - } - } catch (final AppenderLoggingException ex) { - error("Unable to write " + manager.getName() + " for appender " + getName() + ": " + ex); - throw ex; - } finally { - readLock.unlock(); - } - } - - /** - * Gets the manager. - * - * @return the manager. - */ - public M getManager() { - return manager; - } - - public StringLayout getStringLayout() { - return (StringLayout) getLayout(); - } - - @Override - public void start() { - if (getLayout() == null) { - LOGGER.error("No layout set for the appender named [{}].", getName()); - } - if (manager == null) { - LOGGER.error("No OutputStreamManager set for the appender named [{}].", getName()); - } - super.start(); - } - - @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { - setStopping(); - boolean stopped = super.stop(timeout, timeUnit, false); - stopped &= manager.stop(timeout, timeUnit); - setStopped(); - return stopped; - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.StringLayout; +import org.apache.logging.log4j.core.config.Property; + +/** + * Appends log events as strings to a writer. + * + * @param + * The kind of {@link WriterManager} under management + */ +public abstract class AbstractWriterAppender extends AbstractAppender { + + /** + * Immediate flush means that the underlying writer will be flushed at the + * end of each append operation. Immediate flush is slower but ensures that + * each append request is actually written. If immediateFlush + * is set to {@code false}, then there is a good chance that the last few + * logs events are not actually written to persistent media if and when the + * application crashes. + */ + protected final boolean immediateFlush; + private final M manager; + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private final Lock readLock = readWriteLock.readLock(); + + /** + * Instantiates. + * + * @param name + * The name of the Appender. + * @param layout + * The layout to format the message. + * @param properties Optional properties. + * @param manager + * The OutputStreamManager. + */ + protected AbstractWriterAppender(final String name, final StringLayout layout, final Filter filter, + final boolean ignoreExceptions, final boolean immediateFlush, final Property[] properties, final M manager) { + super(name, filter, layout, ignoreExceptions, properties); + this.manager = manager; + this.immediateFlush = immediateFlush; + } + + /** + * Actual writing occurs here. + *

+ * Most subclasses will need to override this method. + *

+ * + * @param event + * The LogEvent. + */ + @Override + public void append(final LogEvent event) { + readLock.lock(); + try { + final String str = getStringLayout().toSerializable(event); + if (str.length() > 0) { + manager.write(str); + if (this.immediateFlush || event.isEndOfBatch()) { + manager.flush(); + } + } + } catch (final AppenderLoggingException ex) { + error("Unable to write " + manager.getName() + " for appender " + getName(), event, ex); + throw ex; + } finally { + readLock.unlock(); + } + } + + /** + * Gets the manager. + * + * @return the manager. + */ + public M getManager() { + return manager; + } + + public StringLayout getStringLayout() { + return (StringLayout) getLayout(); + } + + @Override + public void start() { + if (getLayout() == null) { + LOGGER.error("No layout set for the appender named [{}].", getName()); + } + if (manager == null) { + LOGGER.error("No OutputStreamManager set for the appender named [{}].", getName()); + } + super.start(); + } + + @Override + public boolean stop(final long timeout, final TimeUnit timeUnit) { + setStopping(); + boolean stopped = super.stop(timeout, timeUnit, false); + stopped &= manager.stop(timeout, timeUnit); + setStopped(); + return stopped; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderLoggingException.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderLoggingException.java index 4b65a2c08b6..592dbb87fd2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderLoggingException.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderLoggingException.java @@ -24,17 +24,18 @@ * using the {@link org.apache.logging.log4j.status.StatusLogger}. Appenders should only throw exceptions when an error * prevents an event from being written. Appenders must throw an exception in this case so that error-handling * features like the {@link FailoverAppender} work properly. - * + *

* Also note that appenders must provide a way to suppress exceptions when the user desires and abide by * that instruction. See {@link org.apache.logging.log4j.core.Appender#ignoreExceptions()}, which is the standard * way to do this. + *

*/ public class AppenderLoggingException extends LoggingException { private static final long serialVersionUID = 6545990597472958303L; /** - * Construct an exception with a message. + * Constructs an exception with a message. * * @param message The reason for the exception */ @@ -43,7 +44,18 @@ public AppenderLoggingException(final String message) { } /** - * Construct an exception with a message and underlying cause. + * Constructs an exception with a message. + * + * @param format The reason format for the exception, see {@link String#format(String, Object...)}. + * @param args The reason arguments for the exception, see {@link String#format(String, Object...)}. + * @since 2.12.1 + */ + public AppenderLoggingException(final String format, final Object... args) { + super(String.format(format, args)); + } + + /** + * Constructs an exception with a message and underlying cause. * * @param message The reason for the exception * @param cause The underlying cause of the exception @@ -53,11 +65,23 @@ public AppenderLoggingException(final String message, final Throwable cause) { } /** - * Construct an exception with an underlying cause. + * Constructs an exception with an underlying cause. * * @param cause The underlying cause of the exception */ public AppenderLoggingException(final Throwable cause) { super(cause); } + + /** + * Constructs an exception with a message. + * + * @param cause The underlying cause of the exception + * @param format The reason format for the exception, see {@link String#format(String, Object...)}. + * @param args The reason arguments for the exception, see {@link String#format(String, Object...)}. + * @since 2.12.1 + */ + public AppenderLoggingException(final Throwable cause, final String format, final Object... args) { + super(String.format(format, args), cause); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java index 7b3c07b4564..0e95e350d50 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java @@ -16,33 +16,34 @@ */ package org.apache.logging.log4j.core.appender; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginNode; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.PluginNode; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.status.StatusLogger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * A deferred plugin for appenders. */ -@Plugin(name = "AppenderSet", category = Core.CATEGORY_NAME, printObject = true, deferChildren = true) +@Configurable(printObject = true, deferChildren = true) +@Plugin public class AppenderSet { - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginNode private Node node; - @PluginConfiguration + @Inject @Required private Configuration configuration; @@ -50,7 +51,7 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde public AppenderSet build() { if (configuration == null) { LOGGER.error("Configuration is missing from AppenderSet {}", this); - return null; + return null; } if (node == null) { LOGGER.error("No node in AppenderSet {}", this); @@ -65,7 +66,7 @@ public AppenderSet build() { for (final Node childNode : children) { final String key = childNode.getAttributes().get("name"); if (key == null) { - LOGGER.error("The attribute 'name' is missing from from the node {} in AppenderSet {}", + LOGGER.error("The attribute 'name' is missing from the node {} in AppenderSet {}", childNode, children); } else { map.put(key, childNode); @@ -82,12 +83,12 @@ public Configuration getConfiguration() { return configuration; } - public Builder withNode(@SuppressWarnings("hiding") final Node node) { + public Builder setNode(@SuppressWarnings("hiding") final Node node) { this.node = node; return this; } - public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { + public Builder setConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { this.configuration = configuration; return this; } @@ -104,7 +105,7 @@ public String toString() { private final Configuration configuration; private final Map nodeMap; - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } @@ -114,14 +115,14 @@ private AppenderSet(final Configuration configuration, final Map a this.nodeMap = appenders; } - public Appender createAppender(final String appenderName, final String actualName) { - final Node node = nodeMap.get(appenderName); + public Appender createAppender(final String actualAppenderName, final String sourceAppenderName) { + final Node node = nodeMap.get(actualAppenderName); if (node == null) { - LOGGER.error("No node named {} in {}", appenderName, this); + LOGGER.error("No node named {} in {}", actualAppenderName, this); return null; } - node.getAttributes().put("name", actualName); - if (node.getType().getElementName().equals(Appender.ELEMENT_TYPE)) { + node.getAttributes().put("name", sourceAppenderName); + if (node.getType().getElementType().equals(Appender.ELEMENT_TYPE)) { final Node appNode = new Node(node); configuration.createConfiguration(appNode, null); if (appNode.getObject() instanceof Appender) { @@ -132,7 +133,7 @@ public Appender createAppender(final String appenderName, final String actualNam LOGGER.error("Unable to create Appender of type " + node.getName()); return null; } - LOGGER.error("No Appender was configured for name {} " + appenderName); + LOGGER.error("No Appender was configured for name {} " + actualAppenderName); return null; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java index 92e67381717..6a3c9c9d0a8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java @@ -16,17 +16,7 @@ */ package org.apache.logging.log4j.core.appender; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TransferQueue; -import java.util.concurrent.atomic.AtomicLong; - -import org.apache.logging.log4j.core.AbstractLogEvent; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.async.ArrayBlockingQueueFactory; @@ -41,31 +31,36 @@ import org.apache.logging.log4j.core.config.AppenderRef; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationException; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.filter.AbstractFilterable; import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.util.Log4jThread; -import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.spi.AbstractLogger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TransferQueue; + /** * Appends to one or more Appenders asynchronously. You can configure an AsyncAppender with one or more Appenders and an * Appender to append to if the queue is full. The AsyncAppender does not allow a filter to be specified on the Appender * references. */ -@Plugin(name = "Async", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Async") public final class AsyncAppender extends AbstractAppender { private static final int DEFAULT_QUEUE_SIZE = 1024; - private static final LogEvent SHUTDOWN_LOG_EVENT = new AbstractLogEvent() { - }; - - private static final AtomicLong THREAD_SEQUENCE = new AtomicLong(1); private final BlockingQueue queue; private final int queueSize; @@ -76,14 +71,14 @@ public final class AsyncAppender extends AbstractAppender { private final String errorRef; private final boolean includeLocation; private AppenderControl errorAppender; - private AsyncThread thread; + private AsyncAppenderEventDispatcher dispatcher; private AsyncQueueFullPolicy asyncQueueFullPolicy; private AsyncAppender(final String name, final Filter filter, final AppenderRef[] appenderRefs, - final String errorRef, final int queueSize, final boolean blocking, - final boolean ignoreExceptions, final long shutdownTimeout, final Configuration config, - final boolean includeLocation, final BlockingQueueFactory blockingQueueFactory) { - super(name, filter, null, ignoreExceptions); + final String errorRef, final int queueSize, final boolean blocking, final boolean ignoreExceptions, + final long shutdownTimeout, final Configuration config, final boolean includeLocation, + final BlockingQueueFactory blockingQueueFactory, final Property[] properties) { + super(name, filter, null, ignoreExceptions, properties); this.queue = blockingQueueFactory.create(queueSize); this.queueSize = queueSize; this.blocking = blocking; @@ -115,14 +110,14 @@ public void start() { } } if (appenders.size() > 0) { - thread = new AsyncThread(appenders, queue); - thread.setName("AsyncAppender-" + getName()); + dispatcher = new AsyncAppenderEventDispatcher( + getName(), errorAppender, appenders, queue); } else if (errorRef == null) { throw new ConfigurationException("No appenders are available for AsyncAppender " + getName()); } asyncQueueFullPolicy = AsyncQueueFullPolicyFactory.create(); - thread.start(); + dispatcher.start(); super.start(); } @@ -131,10 +126,11 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { setStopping(); super.stop(timeout, timeUnit, false); LOGGER.trace("AsyncAppender stopping. Queue still has {} events.", queue.size()); - thread.shutdown(); try { - thread.join(shutdownTimeout); - } catch (final InterruptedException ex) { + dispatcher.stop(shutdownTimeout); + } catch (final InterruptedException ignored) { + // Restore the interrupted flag cleared when the exception is caught. + Thread.currentThread().interrupt(); LOGGER.warn("Interrupted while stopping AsyncAppender {}", getName()); } LOGGER.trace("AsyncAppender stopped. Queue has {} events.", queue.size()); @@ -163,11 +159,11 @@ public void append(final LogEvent logEvent) { if (blocking) { if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock - final Message message = AsyncQueueFullMessageUtil.transform(logEvent.getMessage()); - logMessageInCurrentThread(new Log4jLogEvent.Builder(logEvent).setMessage(message).build()); + AsyncQueueFullMessageUtil.logWarningToStatusLogger(); + logMessageInCurrentThread(logEvent); } else { // delegate to the event router (which may discard, enqueue and block, or log in current thread) - final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel()); + final EventRoute route = asyncQueueFullPolicy.getRoute(dispatcher.getId(), memento.getLevel()); route.logMessage(this, memento); } } else { @@ -190,8 +186,7 @@ private boolean transfer(final LogEvent memento) { */ public void logMessageInCurrentThread(final LogEvent logEvent) { logEvent.setEndOfBatch(queue.isEmpty()); - final boolean appendSuccessful = thread.callAppenders(logEvent); - logToErrorAppenderIfNecessary(appendSuccessful, logEvent); + dispatcher.dispatch(logEvent); } /** @@ -203,7 +198,7 @@ public void logMessageInBackgroundThread(final LogEvent logEvent) { try { // wait for free slots in the queue queue.put(logEvent); - } catch (final InterruptedException e) { + } catch (final InterruptedException ignored) { final boolean appendSuccessful = handleInterruptedException(logEvent); logToErrorAppenderIfNecessary(appendSuccessful, logEvent); } @@ -237,49 +232,13 @@ private void logToErrorAppenderIfNecessary(final boolean appendSuccessful, final } } - /** - * Create an AsyncAppender. This method is retained for backwards compatibility. New code should use the - * {@link Builder} instead. This factory will use {@link ArrayBlockingQueueFactory} by default as was the behavior - * pre-2.7. - * - * @param appenderRefs The Appenders to reference. - * @param errorRef An optional Appender to write to if the queue is full or other errors occur. - * @param blocking True if the Appender should wait when the queue is full. The default is true. - * @param shutdownTimeout How many milliseconds the Appender should wait to flush outstanding log events - * in the queue on shutdown. The default is zero which means to wait forever. - * @param size The size of the event queue. The default is 128. - * @param name The name of the Appender. - * @param includeLocation whether to include location information. The default is false. - * @param filter The Filter or null. - * @param config The Configuration. - * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; - * otherwise they are propagated to the caller. - * @return The AsyncAppender. - * @deprecated use {@link Builder} instead - */ - @Deprecated - public static AsyncAppender createAppender(final AppenderRef[] appenderRefs, final String errorRef, - final boolean blocking, final long shutdownTimeout, final int size, - final String name, final boolean includeLocation, final Filter filter, - final Configuration config, final boolean ignoreExceptions) { - if (name == null) { - LOGGER.error("No name provided for AsyncAppender"); - return null; - } - if (appenderRefs == null) { - LOGGER.error("No appender references provided to AsyncAppender {}", name); - } - - return new AsyncAppender(name, filter, appenderRefs, errorRef, size, blocking, ignoreExceptions, - shutdownTimeout, config, includeLocation, new ArrayBlockingQueueFactory()); - } - @PluginBuilderFactory public static Builder newBuilder() { return new Builder(); } - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder> extends AbstractFilterable.Builder + implements org.apache.logging.log4j.core.util.Builder { @PluginElement("AppenderRef") @Required(message = "No appender references provided to AsyncAppender") @@ -305,9 +264,6 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde @PluginBuilderAttribute private boolean includeLocation = false; - @PluginElement("Filter") - private Filter filter; - @PluginConfiguration private Configuration configuration; @@ -352,11 +308,6 @@ public Builder setIncludeLocation(final boolean includeLocation) { return this; } - public Builder setFilter(final Filter filter) { - this.filter = filter; - return this; - } - public Builder setConfiguration(final Configuration configuration) { this.configuration = configuration; return this; @@ -374,105 +325,8 @@ public Builder setBlockingQueueFactory(final BlockingQueueFactory bloc @Override public AsyncAppender build() { - return new AsyncAppender(name, filter, appenderRefs, errorRef, bufferSize, blocking, ignoreExceptions, - shutdownTimeout, configuration, includeLocation, blockingQueueFactory); - } - } - - /** - * Thread that calls the Appenders. - */ - private class AsyncThread extends Log4jThread { - - private volatile boolean shutdown = false; - private final List appenders; - private final BlockingQueue queue; - - public AsyncThread(final List appenders, final BlockingQueue queue) { - super("AsyncAppender-" + THREAD_SEQUENCE.getAndIncrement()); - this.appenders = appenders; - this.queue = queue; - setDaemon(true); - } - - @Override - public void run() { - while (!shutdown) { - LogEvent event; - try { - event = queue.take(); - if (event == SHUTDOWN_LOG_EVENT) { - shutdown = true; - continue; - } - } catch (final InterruptedException ex) { - break; // LOG4J2-830 - } - event.setEndOfBatch(queue.isEmpty()); - final boolean success = callAppenders(event); - if (!success && errorAppender != null) { - try { - errorAppender.callAppender(event); - } catch (final Exception ex) { - // Silently accept the error. - } - } - } - // Process any remaining items in the queue. - LOGGER.trace("AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.", - queue.size()); - int count = 0; - int ignored = 0; - while (!queue.isEmpty()) { - try { - final LogEvent event = queue.take(); - if (event instanceof Log4jLogEvent) { - final Log4jLogEvent logEvent = (Log4jLogEvent) event; - logEvent.setEndOfBatch(queue.isEmpty()); - callAppenders(logEvent); - count++; - } else { - ignored++; - LOGGER.trace("Ignoring event of class {}", event.getClass().getName()); - } - } catch (final InterruptedException ex) { - // May have been interrupted to shut down. - // Here we ignore interrupts and try to process all remaining events. - } - } - LOGGER.trace("AsyncAppender.AsyncThread stopped. Queue has {} events remaining. " - + "Processed {} and ignored {} events since shutdown started.", queue.size(), count, ignored); - } - - /** - * Calls {@link AppenderControl#callAppender(LogEvent) callAppender} on all registered {@code AppenderControl} - * objects, and returns {@code true} if at least one appender call was successful, {@code false} otherwise. Any - * exceptions are silently ignored. - * - * @param event the event to forward to the registered appenders - * @return {@code true} if at least one appender call succeeded, {@code false} otherwise - */ - boolean callAppenders(final LogEvent event) { - boolean success = false; - for (final AppenderControl control : appenders) { - try { - control.callAppender(event); - success = true; - } catch (final Exception ex) { - // If no appender is successful the error appender will get it. - } - } - return success; - } - - public void shutdown() { - shutdown = true; - if (queue.isEmpty()) { - queue.offer(SHUTDOWN_LOG_EVENT); - } - if (getState() == State.TIMED_WAITING || getState() == State.WAITING) { - this.interrupt(); // LOG4J2-1422: if underlying appender is stuck in wait/sleep/join/park call - } + return new AsyncAppender(name, getFilter(), appenderRefs, errorRef, bufferSize, blocking, ignoreExceptions, + shutdownTimeout, configuration, includeLocation, blockingQueueFactory, getPropertyArray()); } } @@ -525,4 +379,15 @@ public int getQueueCapacity() { public int getQueueRemainingCapacity() { return queue.remainingCapacity(); } + + /** + * Returns the number of elements in the queue. + * + * @return the number of elements in the queue. + * @since 2.11.1 + */ + public int getQueueSize() { + return queue.size(); + } + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppenderEventDispatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppenderEventDispatcher.java new file mode 100644 index 00000000000..b2d0b66eaf9 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppenderEventDispatcher.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.AppenderControl; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.util.Log4jThread; +import org.apache.logging.log4j.status.StatusLogger; + +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +class AsyncAppenderEventDispatcher extends Log4jThread { + + private static final LogEvent STOP_EVENT = new Log4jLogEvent(); + + private static final AtomicLong THREAD_COUNTER = new AtomicLong(0); + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final AppenderControl errorAppender; + + private final List appenders; + + private final BlockingQueue queue; + + private final AtomicBoolean stoppedRef; + + AsyncAppenderEventDispatcher( + final String name, + final AppenderControl errorAppender, + final List appenders, + final BlockingQueue queue) { + super("AsyncAppenderEventDispatcher-" + THREAD_COUNTER.incrementAndGet() + "-" + name); + this.setDaemon(true); + this.errorAppender = errorAppender; + this.appenders = appenders; + this.queue = queue; + this.stoppedRef = new AtomicBoolean(); + } + + @Override + public void run() { + LOGGER.trace("{} has started.", getName()); + dispatchAll(); + dispatchRemaining(); + } + + private void dispatchAll() { + while (!stoppedRef.get()) { + final LogEvent event; + try { + event = queue.take(); + } catch (final InterruptedException ignored) { + // Restore the interrupted flag cleared when the exception is caught. + interrupt(); + break; + } + if (event == STOP_EVENT) { + break; + } + event.setEndOfBatch(queue.isEmpty()); + dispatch(event); + } + LOGGER.trace("{} has stopped.", getName()); + } + + private void dispatchRemaining() { + int eventCount = 0; + while (true) { + // Note the non-blocking Queue#poll() method! + final LogEvent event = queue.poll(); + if (event == null) { + break; + } + // Allow events that managed to be submitted after the sentinel. + if (event == STOP_EVENT) { + continue; + } + event.setEndOfBatch(queue.isEmpty()); + dispatch(event); + eventCount++; + } + LOGGER.trace( + "{} has processed the last {} remaining event(s).", + getName(), eventCount); + } + + /** + * Dispatches the given {@code event} to the registered appenders in the + * current thread. + */ + void dispatch(final LogEvent event) { + + // Dispatch the event to all registered appenders. + boolean succeeded = false; + // noinspection ForLoopReplaceableByForEach (avoid iterator instantion) + for (int appenderIndex = 0; appenderIndex < appenders.size(); appenderIndex++) { + final AppenderControl control = appenders.get(appenderIndex); + try { + control.callAppender(event); + succeeded = true; + } catch (final Throwable error) { + // If no appender is successful, the error appender will get it. + // It is okay to simply log it here. + LOGGER.trace( + "{} has failed to call appender {}", + getName(), control.getAppenderName(), error); + } + } + + // Fallback to the error appender if none has succeeded so far. + if (!succeeded && errorAppender != null) { + try { + errorAppender.callAppender(event); + } catch (final Throwable error) { + // If the error appender also fails, there is nothing further + // we can do about it. + LOGGER.trace( + "{} has failed to call the error appender {}", + getName(), errorAppender.getAppenderName(), error); + } + } + + } + + void stop(final long timeoutMillis) throws InterruptedException { + + // Mark the completion, if necessary. + final boolean stopped = stoppedRef.compareAndSet(false, true); + if (stopped) { + LOGGER.trace("{} is signaled to stop.", getName()); + } + + // There is a slight chance that the thread is not started yet, wait for + // it to run. Otherwise, interrupt+join might block. + // noinspection StatementWithEmptyBody + while (Thread.State.NEW.equals(getState())); + + // Enqueue the stop event, if there is sufficient room; otherwise, + // fallback to interruption. (We should avoid interrupting the thread if + // at all possible due to the subtleties of Java interruption, which + // will actually close sockets if any blocking operations are in + // progress! This means a socket appender may surprisingly fail to + // deliver final events. I recall some oddities with file I/O as well. + // — ckozak) + final boolean added = queue.offer(STOP_EVENT); + if (!added) { + interrupt(); + } + + // Wait for the completion. + join(timeoutMillis); + + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConfigurationFactoryData.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConfigurationFactoryData.java index cadfea0b0e6..7a93ad10ce9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConfigurationFactoryData.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConfigurationFactoryData.java @@ -40,7 +40,7 @@ public Configuration getConfiguration() { /** * Gets the LoggerContext from the Configuration or null. - * + * * @return the LoggerContext from the Configuration or null. */ public LoggerContext getLoggerContext() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java index 90d16e6a961..64b6ef487ee 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ConsoleAppender.java @@ -28,19 +28,21 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.core.util.Booleans; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.util.CloseShieldOutputStream; +import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.core.util.Throwables; -import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; +import org.apache.logging.log4j.util.Chars; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; /** * Appends log events to System.out or System.err using a layout specified by the user. The @@ -52,12 +54,13 @@ * print byte streams. *

*/ -@Plugin(name = ConsoleAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin(ConsoleAppender.PLUGIN_NAME) public final class ConsoleAppender extends AbstractOutputStreamAppender { public static final String PLUGIN_NAME = "Console"; private static final String JANSI_CLASS = "org.fusesource.jansi.WindowsAnsiOutputStream"; - private static ConsoleManagerFactory factory = new ConsoleManagerFactory(); + private static final ConsoleManagerFactory factory = new ConsoleManagerFactory(); private static final Target DEFAULT_TARGET = Target.SYSTEM_OUT; private static final AtomicInteger COUNT = new AtomicInteger(); @@ -88,99 +91,25 @@ public Charset getDefaultCharset() { public abstract Charset getDefaultCharset(); - protected Charset getCharset(final String property, Charset defaultCharset) { + protected Charset getCharset(final String property, final Charset defaultCharset) { return new PropertiesUtil(PropertiesUtil.getSystemProperties()).getCharsetProperty(property, defaultCharset); } } private ConsoleAppender(final String name, final Layout layout, final Filter filter, - final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) { - super(name, layout, filter, ignoreExceptions, true, manager); + final OutputStreamManager manager, final boolean ignoreExceptions, final Target target, final Property[] properties) { + super(name, layout, filter, ignoreExceptions, true, properties, manager); this.target = target; } - /** - * Creates a Console Appender. - * - * @param layout The layout to use (required). - * @param filter The Filter or null. - * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT". - * @param name The name of the Appender (required). - * @param follow If true will follow changes to the underlying output stream. - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they - * are propagated to the caller. - * @return The ConsoleAppender. - * @deprecated Deprecated in 2.7; use {@link #newBuilder()}. - */ - @Deprecated - public static ConsoleAppender createAppender(Layout layout, - final Filter filter, - final String targetStr, - final String name, - final String follow, - final String ignore) { - if (name == null) { - LOGGER.error("No name provided for ConsoleAppender"); - return null; - } - if (layout == null) { - layout = PatternLayout.createDefaultLayout(); - } - final boolean isFollow = Boolean.parseBoolean(follow); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr); - return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target); - } - - /** - * Creates a Console Appender. - * - * @param layout The layout to use (required). - * @param filter The Filter or null. - * @param target The target (SYSTEM_OUT or SYSTEM_ERR). The default is SYSTEM_OUT. - * @param name The name of the Appender (required). - * @param follow If true will follow changes to the underlying output stream. - * @param direct If true will write directly to {@link java.io.FileDescriptor} and bypass - * {@link System#out}/{@link System#err}. - * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they - * are propagated to the caller. - * @return The ConsoleAppender. - * @deprecated Deprecated in 2.7; use {@link #newBuilder()}. - */ - @Deprecated - public static ConsoleAppender createAppender( - // @formatter:off - Layout layout, - final Filter filter, - Target target, - final String name, - final boolean follow, - final boolean direct, - final boolean ignoreExceptions) { - // @formatter:on - if (name == null) { - LOGGER.error("No name provided for ConsoleAppender"); - return null; - } - if (layout == null) { - layout = PatternLayout.createDefaultLayout(); - } - target = target == null ? Target.SYSTEM_OUT : target; - if (follow && direct) { - LOGGER.error("Cannot use both follow and direct on ConsoleAppender"); - return null; - } - return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target); - } - public static ConsoleAppender createDefaultAppenderForLayout(final Layout layout) { // this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null, - getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET); + getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET, null); } - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } @@ -190,7 +119,7 @@ public static > B newBuilder() { * @param The type to build */ public static class Builder> extends AbstractOutputStreamAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute @Required @@ -224,7 +153,7 @@ public ConsoleAppender build() { } final Layout layout = getOrCreateLayout(target.getDefaultCharset()); return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout), - isIgnoreExceptions(), target); + isIgnoreExceptions(), target, getPropertyArray()); } } @@ -259,13 +188,13 @@ private static OutputStream getOutputStream(final boolean follow, final boolean } catch (final UnsupportedEncodingException ex) { // should never happen throw new IllegalStateException("Unsupported default encoding " + enc, ex); } - final PropertiesUtil propsUtil = PropertiesUtil.getProperties(); - if (!propsUtil.isOsWindows() || propsUtil.getBooleanProperty("log4j.skipJansi", true) || direct) { + final PropertyEnvironment properties = PropertiesUtil.getProperties(); + if (!properties.isOsWindows() || properties.getBooleanProperty(Log4jProperties.JANSI_DISABLED, true) || direct) { return outputStream; } try { // We type the parameter as a wildcard to avoid a hard reference to Jansi. - final Class clazz = LoaderUtil.loadClass(JANSI_CLASS); + final Class clazz = Loader.loadClass(JANSI_CLASS); final Constructor constructor = clazz.getConstructor(OutputStream.class); return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream)); } catch (final ClassNotFoundException cnfe) { @@ -273,11 +202,15 @@ private static OutputStream getOutputStream(final boolean follow, final boolean } catch (final NoSuchMethodException nsme) { LOGGER.warn("{} is missing the proper constructor", JANSI_CLASS); } catch (final Exception ex) { - LOGGER.warn("Unable to instantiate {} due to {}", JANSI_CLASS, Throwables.getRootCause(ex).toString().trim()); + LOGGER.warn("Unable to instantiate {} due to {}", JANSI_CLASS, clean(Throwables.getRootCause(ex).toString()).trim()); } return outputStream; } + private static String clean(final String string) { + return string.replace(Chars.NUL, Chars.SPACE); + } + /** * An implementation of OutputStream that redirects to the current System.err. */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/CountingNoOpAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/CountingNoOpAppender.java index 0e3ac478325..7be35c23e82 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/CountingNoOpAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/CountingNoOpAppender.java @@ -16,27 +16,29 @@ */ package org.apache.logging.log4j.core.appender; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; /** * No-Operation Appender that counts events. */ -@Plugin(name = "CountingNoOp", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("CountingNoOp") public class CountingNoOpAppender extends AbstractAppender { private final AtomicLong total = new AtomicLong(); public CountingNoOpAppender(final String name, final Layout layout) { - super(name, null, layout); + super(name, null, layout, true, Property.EMPTY_ARRAY); } public long getCount() { @@ -52,7 +54,7 @@ public void append(final LogEvent event) { * Creates a CountingNoOp Appender. */ @PluginFactory - public static CountingNoOpAppender createAppender(@PluginAttribute("name") final String name) { + public static CountingNoOpAppender createAppender(@PluginAttribute final String name) { return new CountingNoOpAppender(Objects.requireNonNull(name), null); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/DefaultErrorHandler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/DefaultErrorHandler.java index fa51b95f78c..75da9a2f429 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/DefaultErrorHandler.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/DefaultErrorHandler.java @@ -24,77 +24,97 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.status.StatusLogger; +import static java.util.Objects.requireNonNull; + /** - * + * The default {@link ErrorHandler} implementation falling back to {@link StatusLogger}. + *

+ * It avoids flooding the {@link StatusLogger} by allowing either the first 3 errors or errors once every 5 minutes. + *

*/ public class DefaultErrorHandler implements ErrorHandler { private static final Logger LOGGER = StatusLogger.getLogger(); - private static final int MAX_EXCEPTIONS = 3; + private static final int MAX_EXCEPTION_COUNT = 3; - private static final long EXCEPTION_INTERVAL = TimeUnit.MINUTES.toNanos(5); + private static final long EXCEPTION_INTERVAL_NANOS = TimeUnit.MINUTES.toNanos(5); private int exceptionCount = 0; - private long lastException = System.nanoTime() - EXCEPTION_INTERVAL - 1; + private long lastExceptionInstantNanos = System.nanoTime() - EXCEPTION_INTERVAL_NANOS - 1; private final Appender appender; public DefaultErrorHandler(final Appender appender) { - this.appender = appender; + this.appender = requireNonNull(appender, "appender"); } - /** * Handle an error with a message. - * @param msg The message. + * @param msg a message */ @Override public void error(final String msg) { - final long current = System.nanoTime(); - if (current - lastException > EXCEPTION_INTERVAL || exceptionCount++ < MAX_EXCEPTIONS) { + final boolean allowed = acquirePermit(); + if (allowed) { LOGGER.error(msg); } - lastException = current; } /** * Handle an error with a message and an exception. - * @param msg The message. - * @param t The Throwable. + * + * @param msg a message + * @param error a {@link Throwable} */ @Override - public void error(final String msg, final Throwable t) { - final long current = System.nanoTime(); - if (current - lastException > EXCEPTION_INTERVAL || exceptionCount++ < MAX_EXCEPTIONS) { - LOGGER.error(msg, t); + public void error(final String msg, final Throwable error) { + final boolean allowed = acquirePermit(); + if (allowed) { + LOGGER.error(msg, error); } - lastException = current; - if (!appender.ignoreExceptions() && t != null && !(t instanceof AppenderLoggingException)) { - throw new AppenderLoggingException(msg, t); + if (!appender.ignoreExceptions() && error != null && !(error instanceof AppenderLoggingException)) { + throw new AppenderLoggingException(msg, error); } } /** - * Handle an error with a message, and exception and a logging event. - * @param msg The message. - * @param event The LogEvent. - * @param t The Throwable. + * Handle an error with a message, an exception, and a logging event. + * + * @param msg a message + * @param event a {@link LogEvent} + * @param error a {@link Throwable} */ @Override - public void error(final String msg, final LogEvent event, final Throwable t) { - final long current = System.nanoTime(); - if (current - lastException > EXCEPTION_INTERVAL || exceptionCount++ < MAX_EXCEPTIONS) { - LOGGER.error(msg, t); + public void error(final String msg, final LogEvent event, final Throwable error) { + final boolean allowed = acquirePermit(); + if (allowed) { + LOGGER.error(msg, error); } - lastException = current; - if (!appender.ignoreExceptions() && t != null && !(t instanceof AppenderLoggingException)) { - throw new AppenderLoggingException(msg, t); + if (!appender.ignoreExceptions() && error != null && !(error instanceof AppenderLoggingException)) { + throw new AppenderLoggingException(msg, error); + } + } + + private boolean acquirePermit() { + final long currentInstantNanos = System.nanoTime(); + synchronized (this) { + if (currentInstantNanos - lastExceptionInstantNanos > EXCEPTION_INTERVAL_NANOS) { + lastExceptionInstantNanos = currentInstantNanos; + return true; + } else if (exceptionCount < MAX_EXCEPTION_COUNT) { + exceptionCount++; + lastExceptionInstantNanos = currentInstantNanos; + return true; + } else { + return false; + } } } public Appender getAppender() { return appender; } + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoverAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoverAppender.java index 659caf1241f..40fde52bbb8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoverAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoverAppender.java @@ -16,33 +16,34 @@ */ package org.apache.logging.log4j.core.appender; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.LoggingException; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.AppenderControl; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.util.Booleans; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * The FailoverAppender will capture exceptions in an Appender and then route the event * to a different appender. Hopefully it is obvious that the Appenders must be configured * to not suppress exceptions for the FailoverAppender to work. */ -@Plugin(name = "Failover", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Failover") public final class FailoverAppender extends AbstractAppender { private static final int DEFAULT_INTERVAL_SECONDS = 60; @@ -59,18 +60,18 @@ public final class FailoverAppender extends AbstractAppender { private final long intervalNanos; - private volatile long nextCheckNanos = 0; + private volatile long nextCheckNanos; private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers, - final int intervalMillis, final Configuration config, final boolean ignoreExceptions) { - super(name, filter, null, ignoreExceptions); + final int intervalMillis, final Configuration config, final boolean ignoreExceptions, + final Property[] properties) { + super(name, filter, null, ignoreExceptions, properties); this.primaryRef = primary; this.failovers = failovers; this.config = config; this.intervalNanos = TimeUnit.MILLISECONDS.toNanos(intervalMillis); } - @Override public void start() { final Map map = config.getAppenders(); @@ -181,38 +182,22 @@ public String toString() { */ @PluginFactory public static FailoverAppender createAppender( - @PluginAttribute("name") final String name, - @PluginAttribute("primary") final String primary, - @PluginElement("Failovers") final String[] failovers, + @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name, + @PluginAttribute @Required(message = "A primary Appender must be specified") final String primary, + @PluginElement @Required(message = "At least one failover Appender must be specified") final String[] failovers, @PluginAliases("retryInterval") // deprecated - @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds, - @PluginConfiguration final Configuration config, - @PluginElement("Filter") final Filter filter, - @PluginAttribute("ignoreExceptions") final String ignore) { - if (name == null) { - LOGGER.error("A name for the Appender must be specified"); - return null; - } - if (primary == null) { - LOGGER.error("A primary Appender must be specified"); - return null; - } - if (failovers == null || failovers.length == 0) { - LOGGER.error("At least one failover Appender must be specified"); - return null; - } - - final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS); - int retryIntervalMillis; - if (seconds >= 0) { - retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS; + @PluginAttribute(defaultInt = DEFAULT_INTERVAL_SECONDS) final int retryIntervalSeconds, + final Configuration config, + @PluginElement final Filter filter, + @PluginAttribute(defaultBoolean = true) final boolean ignoreExceptions) { + + final int retryIntervalMillis; + if (retryIntervalSeconds >= 0) { + retryIntervalMillis = retryIntervalSeconds * Constants.MILLIS_IN_SECONDS; } else { - LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default"); + LOGGER.warn("Interval {} is less than zero. Using default", retryIntervalSeconds); retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS; } - - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - - return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions); + return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions, Property.EMPTY_ARRAY); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoversPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoversPlugin.java index c537d91c9cb..b3105ad6763 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoversPlugin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoversPlugin.java @@ -17,17 +17,18 @@ package org.apache.logging.log4j.core.appender; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.config.AppenderRef; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; /** * The array of failover Appenders. */ -@Plugin(name = "failovers", category = Core.CATEGORY_NAME) +@Configurable +@Plugin("failovers") public final class FailoversPlugin { private static final Logger LOGGER = StatusLogger.getLogger(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java index 8e5736f2cfa..cd22aa3e0cb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java @@ -16,40 +16,39 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.net.Advertiser; -import org.apache.logging.log4j.core.util.Booleans; -import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * File Appender. */ -@Plugin(name = FileAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin(FileAppender.PLUGIN_NAME) public final class FileAppender extends AbstractOutputStreamAppender { public static final String PLUGIN_NAME = "File"; /** * Builds FileAppender instances. - * + * * @param * The type to build */ public static class Builder> extends AbstractOutputStreamAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute @Required @@ -99,7 +98,7 @@ public FileAppender build() { } return new FileAppender(getName(), layout, getFilter(), manager, fileName, isIgnoreExceptions(), - !bufferedIo || isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null); + !bufferedIo || isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null, getPropertyArray()); } public String getAdvertiseUri() { @@ -138,116 +137,58 @@ public String getFileGroup() { return fileGroup; } - public B withAdvertise(final boolean advertise) { + public B setAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } - public B withAdvertiseUri(final String advertiseUri) { + public B setAdvertiseUri(final String advertiseUri) { this.advertiseUri = advertiseUri; return asBuilder(); } - public B withAppend(final boolean append) { + public B setAppend(final boolean append) { this.append = append; return asBuilder(); } - public B withFileName(final String fileName) { + public B setFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } - public B withCreateOnDemand(final boolean createOnDemand) { + public B setCreateOnDemand(final boolean createOnDemand) { this.createOnDemand = createOnDemand; return asBuilder(); } - public B withLocking(final boolean locking) { + public B setLocking(final boolean locking) { this.locking = locking; return asBuilder(); } - public B withFilePermissions(final String filePermissions) { + public B setFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } - public B withFileOwner(final String fileOwner) { + public B setFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } - public B withFileGroup(final String fileGroup) { + public B setFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return asBuilder(); } } - - private static final int DEFAULT_BUFFER_SIZE = 8192; - - /** - * Create a File Appender. - * @param fileName The name and path of the file. - * @param append "True" if the file should be appended to, "false" if it should be overwritten. - * The default is "true". - * @param locking "True" if the file should be locked. The default is "false". - * @param name The name of the Appender. - * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default - * is "true". - * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param bufferedIo "true" if I/O should be buffered, "false" otherwise. The default is "true". - * @param bufferSizeStr buffer size for buffered IO (default is 8192). - * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout - * will be used. - * @param filter The filter, if any, to use. - * @param advertise "true" if the appender configuration should be advertised, "false" otherwise. - * @param advertiseUri The advertised URI which can be used to retrieve the file contents. - * @param config The Configuration - * @return The FileAppender. - * @deprecated Use {@link #newBuilder()} - */ - @Deprecated - public static > FileAppender createAppender( - // @formatter:off - final String fileName, - final String append, - final String locking, - final String name, - final String immediateFlush, - final String ignoreExceptions, - final String bufferedIo, - final String bufferSizeStr, - final Layout layout, - final Filter filter, - final String advertise, - final String advertiseUri, - final Configuration config) { - return FileAppender.newBuilder() - .withAdvertise(Boolean.parseBoolean(advertise)) - .withAdvertiseUri(advertiseUri) - .withAppend(Booleans.parseBoolean(append, true)) - .withBufferedIo(Booleans.parseBoolean(bufferedIo, true)) - .withBufferSize(Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE)) - .setConfiguration(config) - .withFileName(fileName) - .withFilter(filter) - .withIgnoreExceptions(Booleans.parseBoolean(ignoreExceptions, true)) - .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true)) - .withLayout(layout) - .withLocking(Boolean.parseBoolean(locking)) - .withName(name) - .build(); - // @formatter:on - } - - @PluginBuilderFactory + + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } - + private final String fileName; private final Advertiser advertiser; @@ -256,9 +197,9 @@ public static > B newBuilder() { private FileAppender(final String name, final Layout layout, final Filter filter, final FileManager manager, final String filename, final boolean ignoreExceptions, - final boolean immediateFlush, final Advertiser advertiser) { + final boolean immediateFlush, final Advertiser advertiser, final Property[] properties) { - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager); if (advertiser != null) { final Map configuration = new HashMap<>(layout.getContentFormat()); configuration.putAll(manager.getContentFormat()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java index 2898c7f48e5..0a673e53601 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java @@ -25,9 +25,11 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.FileOwnerAttributeView; +import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; @@ -60,56 +62,6 @@ public class FileManager extends OutputStreamManager { private final String fileGroup; private final boolean attributeViewEnabled; - /** - * @deprecated - */ - @Deprecated - protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, - final String advertiseURI, final Layout layout, final int bufferSize, - final boolean writeHeader) { - this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); - } - - /** - * @deprecated - * @since 2.6 - */ - @Deprecated - protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, - final String advertiseURI, final Layout layout, final boolean writeHeader, - final ByteBuffer buffer) { - super(os, fileName, layout, writeHeader, buffer); - this.isAppend = append; - this.createOnDemand = false; - this.isLocking = locking; - this.advertiseURI = advertiseURI; - this.bufferSize = buffer.capacity(); - this.filePermissions = null; - this.fileOwner = null; - this.fileGroup = null; - this.attributeViewEnabled = false; - } - - /** - * @deprecated - * @since 2.7 - */ - @Deprecated - protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking, - final boolean createOnDemand, final String advertiseURI, final Layout layout, - final boolean writeHeader, final ByteBuffer buffer) { - super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer); - this.isAppend = append; - this.createOnDemand = createOnDemand; - this.isLocking = locking; - this.advertiseURI = advertiseURI; - this.bufferSize = buffer.capacity(); - this.filePermissions = null; - this.fileOwner = null; - this.fileGroup = null; - this.attributeViewEnabled = false; - } - /** * @since 2.9 */ @@ -164,7 +116,7 @@ protected FileManager(final LoggerContext loggerContext, final String fileName, * @param bufferSize buffer size for buffered IO * @param filePermissions File permissions * @param fileOwner File owner - * @param fileOwner File group + * @param fileGroup File group * @param configuration The configuration. * @return A FileManager for the File. */ @@ -185,11 +137,25 @@ public static FileManager getFileManager(final String fileName, final boolean ap protected OutputStream createOutputStream() throws IOException { final String filename = getFileName(); LOGGER.debug("Now writing to {} at {}", filename, new Date()); - final FileOutputStream fos = new FileOutputStream(filename, isAppend); + final File file = new File(filename); + createParentDir(file); + final FileOutputStream fos = new FileOutputStream(file, isAppend); + if (file.exists() && file.length() == 0) { + try { + final FileTime now = FileTime.fromMillis(System.currentTimeMillis()); + Files.setAttribute(file.toPath(), "creationTime", now); + } catch (final Exception ex) { + LOGGER.warn("Unable to set current file time for {}", filename); + } + writeHeader(fos); + } defineAttributeView(Paths.get(filename)); return fos; } + protected void createParentDir(final File file) { + } + protected void defineAttributeView(final Path path) { if (attributeViewEnabled) { try { @@ -303,9 +269,9 @@ public int getBufferSize() { } /** - * Returns posix file permissions if defined and the OS supports posix file attribute, + * Returns POSIX file permissions if defined and the OS supports POSIX file attribute, * null otherwise. - * @return File posix permissions + * @return File POSIX permissions * @see PosixFileAttributeView */ public Set getFilePermissions() { @@ -323,7 +289,7 @@ public String getFileOwner() { } /** - * Returns file group if defined and the OS supports posix/group file attribute view, + * Returns file group if defined and the OS supports POSIX/group file attribute view, * null otherwise. * @return File group * @see PosixFileAttributeView @@ -335,7 +301,7 @@ public String getFileGroup() { /** * Returns true if file attribute view enabled for this file manager. * - * @return True if posix or owner supported and defined false otherwise. + * @return True if POSIX or owner supported and defined false otherwise. */ public boolean isAttributeViewEnabled() { return attributeViewEnabled; @@ -416,10 +382,10 @@ public FileManager createManager(final String name, final FactoryData data) { final File file = new File(name); try { FileUtils.makeParentDirs(file); - final boolean writeHeader = !data.append || !file.exists(); final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE; final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]); final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append); + final boolean writeHeader = file.exists() && file.length() == 0; final FileManager fm = new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking, data.createOnDemand, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java index 59f93c84349..2a188743455 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java @@ -17,28 +17,30 @@ package org.apache.logging.log4j.core.appender; -import java.io.Serializable; -import java.net.URL; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.io.Serializable; +import java.net.URL; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + /** * Sends log events over HTTP. */ -@Plugin(name = "Http", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Http") public final class HttpAppender extends AbstractAppender { /** @@ -46,7 +48,7 @@ public final class HttpAppender extends AbstractAppender { * @param The type to build */ public static class Builder> extends AbstractAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute @Required(message = "No URL provided for HttpAppender") @@ -74,7 +76,7 @@ public static class Builder> extends AbstractAppender.Build public HttpAppender build() { final HttpManager httpManager = new HttpURLConnectionManager(getConfiguration(), getConfiguration().getLoggerContext(), getName(), url, method, connectTimeoutMillis, readTimeoutMillis, headers, sslConfiguration, verifyHostname); - return new HttpAppender(getName(), getLayout(), getFilter(), isIgnoreExceptions(), httpManager); + return new HttpAppender(getName(), getLayout(), getFilter(), isIgnoreExceptions(), httpManager, getPropertyArray()); } public URL getUrl() { @@ -144,7 +146,7 @@ public B setVerifyHostname(final boolean verifyHostname) { /** * @return a builder for a HttpAppender. */ - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } @@ -152,8 +154,8 @@ public static > B newBuilder() { private final HttpManager manager; private HttpAppender(final String name, final Layout layout, final Filter filter, - final boolean ignoreExceptions, final HttpManager manager) { - super(name, filter, layout, ignoreExceptions); + final boolean ignoreExceptions, final HttpManager manager, final Property[] properties) { + super(name, filter, layout, ignoreExceptions, properties); Objects.requireNonNull(layout, "layout"); this.manager = Objects.requireNonNull(manager, "manager"); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpURLConnectionManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpURLConnectionManager.java index 90c85509642..cafb17a5a01 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpURLConnectionManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpURLConnectionManager.java @@ -23,6 +23,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Objects; import javax.net.ssl.HttpsURLConnection; @@ -39,7 +40,7 @@ public class HttpURLConnectionManager extends HttpManager { - private static final Charset CHARSET = Charset.forName("US-ASCII"); + private static final Charset CHARSET = StandardCharsets.US_ASCII; private final URL url; private final boolean isHttps; @@ -104,18 +105,18 @@ public void send(final Layout layout, final LogEvent event) throws IOExceptio final byte[] msg = layout.toByteArray(event); urlConnection.setFixedLengthStreamingMode(msg.length); urlConnection.connect(); - try (OutputStream os = urlConnection.getOutputStream()) { + try (final OutputStream os = urlConnection.getOutputStream()) { os.write(msg); } final byte[] buffer = new byte[1024]; - try (InputStream is = urlConnection.getInputStream()) { + try (final InputStream is = urlConnection.getInputStream()) { while (IOUtils.EOF != is.read(buffer)) { // empty } } catch (final IOException e) { final StringBuilder errorMessage = new StringBuilder(); - try (InputStream es = urlConnection.getErrorStream()) { + try (final InputStream es = urlConnection.getErrorStream()) { errorMessage.append(urlConnection.getResponseCode()); if (urlConnection.getResponseMessage() != null) { errorMessage.append(' ').append(urlConnection.getResponseMessage()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java index 17f64eeee07..91b08532083 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java @@ -16,40 +16,39 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.net.Advertiser; -import org.apache.logging.log4j.core.util.Booleans; import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * Memory Mapped File Appender. * * @since 2.1 */ -@Plugin(name = "MemoryMappedFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("MemoryMappedFile") public final class MemoryMappedFileAppender extends AbstractOutputStreamAppender { /** * Builds RandomAccessFileAppender instances. - * + * * @param * The type to build */ public static class Builder> extends AbstractOutputStreamAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute("fileName") private String fileName; @@ -88,7 +87,7 @@ public MemoryMappedFileAppender build() { } return new MemoryMappedFileAppender(name, layout, getFilter(), manager, fileName, isIgnoreExceptions(), false, - advertise ? getConfiguration().getAdvertiser() : null); + advertise ? getConfiguration().getAdvertiser() : null, getPropertyArray()); } public B setFileName(final String fileName) { @@ -117,7 +116,7 @@ public B setAdvertiseURI(final String advertiseURI) { } } - + private static final int BIT_POSITION_1GB = 30; // 2^30 ~= 1GB private static final int MAX_REGION_LENGTH = 1 << BIT_POSITION_1GB; private static final int MIN_REGION_LENGTH = 256; @@ -128,8 +127,8 @@ public B setAdvertiseURI(final String advertiseURI) { private MemoryMappedFileAppender(final String name, final Layout layout, final Filter filter, final MemoryMappedFileManager manager, final String filename, - final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) { - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser, final Property[] properties) { + super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager); if (advertiser != null) { final Map configuration = new HashMap<>(layout.getContentFormat()); configuration.putAll(manager.getContentFormat()); @@ -152,24 +151,6 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { return true; } - /** - * Write the log entry rolling over the file when required. - * - * @param event The LogEvent. - */ - @Override - public void append(final LogEvent event) { - - // Leverage the nice batching behaviour of async Loggers/Appenders: - // we can signal the file manager that it needs to flush the buffer - // to disk at the end of a batch. - // From a user's point of view, this means that all log events are - // _always_ available in the log file, without incurring the overhead - // of immediateFlush=true. - getManager().setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted - super.append(event); // TODO should only call force() if immediateFlush && endOfBatch? - } - /** * Returns the file name this appender is associated with. * @@ -188,67 +169,7 @@ public int getRegionLength() { return getManager().getRegionLength(); } - /** - * Create a Memory Mapped File Appender. - * - * @param fileName The name and path of the file. - * @param append "True" if the file should be appended to, "false" if it should be overwritten. The default is - * "true". - * @param name The name of the Appender. - * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default is - * "false". - * @param regionLengthStr The buffer size, defaults to {@value MemoryMappedFileManager#DEFAULT_REGION_LENGTH}. - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they - * are propagated to the caller. - * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout will be - * used. - * @param filter The filter, if any, to use. - * @param advertise "true" if the appender configuration should be advertised, "false" otherwise. - * @param advertiseURI The advertised URI which can be used to retrieve the file contents. - * @param config The Configuration. - * @return The FileAppender. - * @deprecated Use {@link #newBuilder()}. - */ - @Deprecated - public static > MemoryMappedFileAppender createAppender( - // @formatter:off - final String fileName, // - final String append, // - final String name, // - final String immediateFlush, // - final String regionLengthStr, // - final String ignore, // - final Layout layout, // - final Filter filter, // - final String advertise, // - final String advertiseURI, // - final Configuration config) { - // @formatter:on - - final boolean isAppend = Booleans.parseBoolean(append, true); - final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, false); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - final boolean isAdvertise = Boolean.parseBoolean(advertise); - final int regionLength = Integers.parseInt(regionLengthStr, MemoryMappedFileManager.DEFAULT_REGION_LENGTH); - - // @formatter:off - return MemoryMappedFileAppender.newBuilder() - .setAdvertise(isAdvertise) - .setAdvertiseURI(advertiseURI) - .setAppend(isAppend) - .setConfiguration(config) - .setFileName(fileName) - .withFilter(filter) - .withIgnoreExceptions(ignoreExceptions) - .withImmediateFlush(isImmediateFlush) - .withLayout(layout) - .withName(name) - .setRegionLength(regionLength) - .build(); - // @formatter:on - } - - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java index 7d5fd8d7604..587ffed469c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManager.java @@ -16,6 +16,12 @@ */ package org.apache.logging.log4j.core.appender; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.core.util.FileUtils; +import org.apache.logging.log4j.core.util.NullOutputStream; +import org.apache.logging.log4j.util.ReflectionUtil; + import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -33,11 +39,6 @@ import java.util.Map; import java.util.Objects; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.core.util.FileUtils; -import org.apache.logging.log4j.core.util.NullOutputStream; - //Lines too long... //CHECKSTYLE:OFF /** @@ -69,7 +70,6 @@ public class MemoryMappedFileManager extends OutputStreamManager { private final int regionLength; private final String advertiseURI; private final RandomAccessFile randomAccessFile; - private final ThreadLocal isEndOfBatch = new ThreadLocal<>(); private MappedByteBuffer mappedBuffer; private long mappingOffset; @@ -81,7 +81,6 @@ protected MemoryMappedFileManager(final RandomAccessFile file, final String file this.randomAccessFile = Objects.requireNonNull(file, "RandomAccessFile"); this.regionLength = regionLength; this.advertiseURI = advertiseURI; - this.isEndOfBatch.set(Boolean.FALSE); this.mappedBuffer = mmap(randomAccessFile.getChannel(), getFileName(), position, regionLength); this.byteBuffer = mappedBuffer; this.mappingOffset = position; @@ -105,12 +104,23 @@ public static MemoryMappedFileManager getFileManager(final String fileName, fina regionLength, advertiseURI, layout), FACTORY)); } + /** + * No longer used, the {@link org.apache.logging.log4j.core.LogEvent#isEndOfBatch()} attribute is used instead. + * @return {@link Boolean#FALSE}. + * @deprecated end-of-batch on the event is used instead. + */ + @Deprecated public Boolean isEndOfBatch() { - return isEndOfBatch.get(); + return Boolean.FALSE; } - public void setEndOfBatch(final boolean endOfBatch) { - this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); + /** + * No longer used, the {@link org.apache.logging.log4j.core.LogEvent#isEndOfBatch()} attribute is used instead. + * This method is a no-op. + * @deprecated end-of-batch on the event is used instead. + */ + @Deprecated + public void setEndOfBatch(@SuppressWarnings("unused") final boolean endOfBatch) { } @Override @@ -213,16 +223,12 @@ public static MappedByteBuffer mmap(final FileChannel fileChannel, final String private static void unsafeUnmap(final MappedByteBuffer mbb) throws PrivilegedActionException { LOGGER.debug("MMapAppender unmapping old buffer..."); final long startNanos = System.nanoTime(); - AccessController.doPrivileged(new PrivilegedExceptionAction() { - @Override - public Object run() throws Exception { - final Method getCleanerMethod = mbb.getClass().getMethod("cleaner"); - getCleanerMethod.setAccessible(true); - final Object cleaner = getCleanerMethod.invoke(mbb); // sun.misc.Cleaner instance - final Method cleanMethod = cleaner.getClass().getMethod("clean"); - cleanMethod.invoke(cleaner); - return null; - } + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + final Class unsafeClass = Class.forName("sun.misc.Unsafe"); + final Object unsafe = ReflectionUtil.getStaticFieldValue(unsafeClass.getDeclaredField("theUnsafe")); + final Method invokeCleaner = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class); + invokeCleaner.invoke(unsafe, mbb); + return null; }); final float millis = (float) ((System.nanoTime() - startNanos) / NANOS_PER_MILLISEC); LOGGER.debug("MMapAppender unmapped buffer OK in {} millis", millis); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/NullAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/NullAppender.java index 3978f058d31..dddf6cde2af 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/NullAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/NullAppender.java @@ -17,27 +17,31 @@ package org.apache.logging.log4j.core.appender; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; /** - * An Appender that ignores log events. Use for compatibility with version 1.2. + * An Appender that ignores log events. Use for compatibility with version 1.2 and handy for composing a + * {@link ScriptAppenderSelector}. */ -@Plugin(name = NullAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin(NullAppender.PLUGIN_NAME) public class NullAppender extends AbstractAppender { public static final String PLUGIN_NAME = "Null"; @PluginFactory - public static NullAppender createAppender(@PluginAttribute("name") final String name) { + public static NullAppender createAppender( + @PluginAttribute(defaultString = "null") final String name) { return new NullAppender(name); } private NullAppender(final String name) { - super(name, null, null); + super(name, null, null, true, Property.EMPTY_ARRAY); // Do nothing } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java index ddca626fde8..86b36f77994 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java @@ -1,191 +1,173 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.appender; - -import java.io.OutputStream; -import java.io.Serializable; - -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.core.util.CloseShieldOutputStream; - -/** - * Appends log events to a given output stream using a layout. - *

- * Character encoding is handled within the Layout. - *

- */ -@Plugin(name = "OutputStream", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) -public final class OutputStreamAppender extends AbstractOutputStreamAppender { - - /** - * Builds OutputStreamAppender instances. - */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { - - private Filter filter; - - private boolean follow = false; - - private boolean ignoreExceptions = true; - - private Layout layout = PatternLayout.createDefaultLayout(); - - private String name; - - private OutputStream target; - - @Override - public OutputStreamAppender build() { - return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions); - } - - public Builder setFilter(final Filter aFilter) { - this.filter = aFilter; - return this; - } - - public Builder setFollow(final boolean shouldFollow) { - this.follow = shouldFollow; - return this; - } - - public Builder setIgnoreExceptions(final boolean shouldIgnoreExceptions) { - this.ignoreExceptions = shouldIgnoreExceptions; - return this; - } - - public Builder setLayout(final Layout aLayout) { - this.layout = aLayout; - return this; - } - - public Builder setName(final String aName) { - this.name = aName; - return this; - } - - public Builder setTarget(final OutputStream aTarget) { - this.target = aTarget; - return this; - } - } - /** - * Holds data to pass to factory method. - */ - private static class FactoryData { - private final Layout layout; - private final String name; - private final OutputStream os; - - /** - * Builds instances. - * - * @param os - * The OutputStream. - * @param type - * The name of the target. - * @param layout - * A Serializable layout - */ - public FactoryData(final OutputStream os, final String type, final Layout layout) { - this.os = os; - this.name = type; - this.layout = layout; - } - } - - /** - * Creates the manager. - */ - private static class OutputStreamManagerFactory implements ManagerFactory { - - /** - * Creates an OutputStreamManager. - * - * @param name - * The name of the entity to manage. - * @param data - * The data required to create the entity. - * @return The OutputStreamManager - */ - @Override - public OutputStreamManager createManager(final String name, final FactoryData data) { - return new OutputStreamManager(data.os, data.name, data.layout, true); - } - } - - private static OutputStreamManagerFactory factory = new OutputStreamManagerFactory(); - - /** - * Creates an OutputStream Appender. - * - * @param layout - * The layout to use or null to get the default layout. - * @param filter - * The Filter or null. - * @param target - * an output stream. - * @param follow - * If true will follow changes to the underlying output stream. - * Use false as the default. - * @param name - * The name of the Appender (required). - * @param ignore - * If {@code "true"} (default) exceptions encountered when - * appending events are logged; otherwise they are propagated to - * the caller. Use true as the default. - * @return The ConsoleAppender. - */ - @PluginFactory - public static OutputStreamAppender createAppender(Layout layout, final Filter filter, - final OutputStream target, final String name, final boolean follow, final boolean ignore) { - if (name == null) { - LOGGER.error("No name provided for OutputStreamAppender"); - return null; - } - if (layout == null) { - layout = PatternLayout.createDefaultLayout(); - } - return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignore); - } - - private static OutputStreamManager getManager(final OutputStream target, final boolean follow, - final Layout layout) { - final OutputStream os = new CloseShieldOutputStream(target); - final String managerName = target.getClass().getName() + "@" + Integer.toHexString(target.hashCode()) + '.' - + follow; - return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); - } - - @PluginBuilderFactory - public static Builder newBuilder() { - return new Builder(); - } - - private OutputStreamAppender(final String name, final Layout layout, final Filter filter, - final OutputStreamManager manager, final boolean ignoreExceptions) { - super(name, layout, filter, ignoreExceptions, true, manager); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.util.CloseShieldOutputStream; +import org.apache.logging.log4j.core.util.NullOutputStream; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.io.OutputStream; +import java.io.Serializable; + +/** + * Appends log events to a given output stream using a layout. + *

+ * Character encoding is handled within the Layout. + *

+ */ +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("OutputStream") +public final class OutputStreamAppender extends AbstractOutputStreamAppender { + + /** + * Builds OutputStreamAppender instances. + * + * @param + * The type to build. + */ + public static class Builder> extends AbstractOutputStreamAppender.Builder + implements org.apache.logging.log4j.plugins.util.Builder { + + private boolean follow = false; + + private final Layout layout = PatternLayout.createDefaultLayout(); + + private OutputStream target; + + @Override + public OutputStreamAppender build() { + + return new OutputStreamAppender(getName(), layout, getFilter(), getManager(target, follow, layout), isIgnoreExceptions(), getPropertyArray()); + } + + public B setFollow(final boolean shouldFollow) { + this.follow = shouldFollow; + return asBuilder(); + } + + public B setTarget(final OutputStream aTarget) { + this.target = aTarget; + return asBuilder(); + } + } + /** + * Holds data to pass to factory method. + */ + private static class FactoryData { + private final Layout layout; + private final String name; + private final OutputStream os; + + /** + * Builds instances. + * + * @param os + * The OutputStream. + * @param type + * The name of the target. + * @param layout + * A Serializable layout + */ + public FactoryData(final OutputStream os, final String type, final Layout layout) { + this.os = os; + this.name = type; + this.layout = layout; + } + } + + /** + * Creates the manager. + */ + private static class OutputStreamManagerFactory implements ManagerFactory { + + /** + * Creates an OutputStreamManager. + * + * @param name + * The name of the entity to manage. + * @param data + * The data required to create the entity. + * @return The OutputStreamManager + */ + @Override + public OutputStreamManager createManager(final String name, final FactoryData data) { + return new OutputStreamManager(data.os, data.name, data.layout, true); + } + } + + private static final OutputStreamManagerFactory factory = new OutputStreamManagerFactory(); + + /** + * Creates an OutputStream Appender. + * + * @param layout + * The layout to use or null to get the default layout. + * @param filter + * The Filter or null. + * @param target + * an output stream. + * @param follow + * If true will follow changes to the underlying output stream. + * Use false as the default. + * @param name + * The name of the Appender (required). + * @param ignore + * If {@code "true"} (default) exceptions encountered when + * appending events are logged; otherwise they are propagated to + * the caller. Use true as the default. + * @return The ConsoleAppender. + */ + @PluginFactory + public static OutputStreamAppender createAppender(Layout layout, final Filter filter, + final OutputStream target, final String name, final boolean follow, final boolean ignore) { + if (name == null) { + LOGGER.error("No name provided for OutputStreamAppender"); + return null; + } + if (layout == null) { + layout = PatternLayout.createDefaultLayout(); + } + return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignore, null); + } + + private static OutputStreamManager getManager(final OutputStream target, final boolean follow, + final Layout layout) { + final OutputStream os = target == null ? NullOutputStream.getInstance() : new CloseShieldOutputStream(target); + final OutputStream targetRef = target == null ? os : target; + final String managerName = targetRef.getClass().getName() + "@" + Integer.toHexString(targetRef.hashCode()) + + '.' + follow; + return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory); + } + + @PluginFactory + public static > B newBuilder() { + return new Builder().asBuilder(); + } + + private OutputStreamAppender(final String name, final Layout layout, final Filter filter, + final OutputStreamManager manager, final boolean ignoreExceptions, final Property[] properties) { + super(name, layout, filter, ignoreExceptions, true, null, manager); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java index 0873822feec..34fdc1579c9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java @@ -42,13 +42,11 @@ public class OutputStreamManager extends AbstractManager implements ByteBufferDe protected OutputStreamManager(final OutputStream os, final String streamName, final Layout layout, final boolean writeHeader) { - // Can't use new ctor because it throws an exception this(os, streamName, layout, writeHeader, Constants.ENCODER_BYTE_BUFFER_SIZE); } protected OutputStreamManager(final OutputStream os, final String streamName, final Layout layout, final boolean writeHeader, final int bufferSize) { - // Can't use new ctor because it throws an exception this(os, streamName, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); } @@ -62,17 +60,10 @@ protected OutputStreamManager(final OutputStream os, final String streamName, fi super(null, streamName); this.outputStream = os; this.layout = layout; - if (writeHeader && layout != null) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - getOutputStream().write(header, 0, header.length); - } catch (final IOException e) { - logError("Unable to write header", e); - } - } - } this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer"); + if (writeHeader) { + writeHeader(os); + } } /** @@ -90,15 +81,8 @@ protected OutputStreamManager(final LoggerContext loggerContext, final OutputStr this.layout = layout; this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer"); this.outputStream = os; - if (writeHeader && layout != null) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - getOutputStream().write(header, 0, header.length); - } catch (final IOException e) { - logError("Unable to write header for " + streamName, e); - } - } + if (writeHeader) { + writeHeader(os); } } @@ -138,6 +122,19 @@ public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { return closeOutputStream(); } + protected void writeHeader(final OutputStream os) { + if (layout != null && os != null) { + final byte[] header = layout.getHeader(); + if (header != null) { + try { + os.write(header, 0, header.length); + } catch (final IOException e) { + logError("Unable to write header", e); + } + } + } + } + /** * Writes the footer. */ @@ -171,17 +168,7 @@ protected OutputStream getOutputStream() throws IOException { } protected void setOutputStream(final OutputStream os) { - final byte[] header = layout.getHeader(); - if (header != null) { - try { - os.write(header, 0, header.length); - this.outputStream = os; // only update field if os.write() succeeded - } catch (final IOException ioe) { - logError("Unable to write header", ioe); - } - } else { - this.outputStream = os; - } + this.outputStream = os; } /** @@ -291,10 +278,13 @@ protected synchronized void flushDestination() { */ protected synchronized void flushBuffer(final ByteBuffer buf) { ((Buffer) buf).flip(); - if (buf.remaining() > 0) { - writeToDestination(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); + try { + if (buf.remaining() > 0) { + writeToDestination(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); + } + } finally { + buf.clear(); } - buf.clear(); } /** @@ -313,6 +303,7 @@ protected synchronized boolean closeOutputStream() { } try { stream.close(); + LOGGER.debug("OutputStream closed"); } catch (final IOException ex) { logError("Unable to close stream", ex); return false; @@ -343,7 +334,7 @@ public ByteBuffer getByteBuffer() { * {@link #flushBuffer(ByteBuffer)} directly instead. *

* - * @param buf the buffer whose contents to write the the destination + * @param buf the buffer whose contents to write the destination * @return the specified buffer * @since 2.6 */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppender.java index d7ec9206bd9..2cac501a6ad 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppender.java @@ -16,38 +16,35 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.net.Advertiser; -import org.apache.logging.log4j.core.util.Booleans; -import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * File Appender. */ -@Plugin(name = "RandomAccessFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("RandomAccessFile") public final class RandomAccessFileAppender extends AbstractOutputStreamAppender { /** * Builds RandomAccessFileAppender instances. - * + * * @param * The type to build */ public static class Builder> extends AbstractOutputStreamAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute("fileName") private String fileName; @@ -61,16 +58,20 @@ public static class Builder> extends AbstractOutputStreamAp @PluginBuilderAttribute("advertiseURI") private String advertiseURI; + public Builder() { + setBufferSize(RandomAccessFileManager.DEFAULT_BUFFER_SIZE); + } + @Override public RandomAccessFileAppender build() { final String name = getName(); if (name == null) { - LOGGER.error("No name provided for FileAppender"); + LOGGER.error("No name provided for RandomAccessFileAppender"); return null; } if (fileName == null) { - LOGGER.error("No filename provided for FileAppender with name " + name); + LOGGER.error("No filename provided for RandomAccessFileAppender with name {}", name); return null; } final Layout layout = getOrCreateLayout(); @@ -106,7 +107,7 @@ public B setAdvertiseURI(final String advertiseURI) { } } - + private final String fileName; private Object advertisement; private final Advertiser advertiser; @@ -115,7 +116,7 @@ private RandomAccessFileAppender(final String name, final Layout configuration = new HashMap<>( layout.getContentFormat()); @@ -139,26 +140,6 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { return true; } - /** - * Write the log entry rolling over the file when required. - * - * @param event The LogEvent. - */ - @Override - public void append(final LogEvent event) { - - // Leverage the nice batching behaviour of async Loggers/Appenders: - // we can signal the file manager that it needs to flush the buffer - // to disk at the end of a batch. - // From a user's point of view, this means that all log events are - // _always_ available in the log file, without incurring the overhead - // of immediateFlush=true. - getManager().setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted - - // LOG4J2-1292 utilize gc-free Layout.encode() method: taken care of in superclass - super.append(event); - } - /** * Returns the file name this appender is associated with. * @@ -176,71 +157,11 @@ public int getBufferSize() { return getManager().getBufferSize(); } - // difference from standard File Appender: - // locking is not supported and buffering cannot be switched off - /** - * Create a File Appender. - * - * @param fileName The name and path of the file. - * @param append "True" if the file should be appended to, "false" if it - * should be overwritten. The default is "true". - * @param name The name of the Appender. - * @param immediateFlush "true" if the contents should be flushed on every - * write, "false" otherwise. The default is "true". - * @param bufferSizeStr The buffer size, defaults to {@value RandomAccessFileManager#DEFAULT_BUFFER_SIZE}. - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param layout The layout to use to format the event. If no layout is - * provided the default PatternLayout will be used. - * @param filter The filter, if any, to use. - * @param advertise "true" if the appender configuration should be - * advertised, "false" otherwise. - * @param advertiseURI The advertised URI which can be used to retrieve the - * file contents. - * @param configuration The Configuration. - * @return The FileAppender. - * @deprecated Use {@link #newBuilder()}. - */ - @Deprecated - public static > RandomAccessFileAppender createAppender( - final String fileName, - final String append, - final String name, - final String immediateFlush, - final String bufferSizeStr, - final String ignore, - final Layout layout, - final Filter filter, - final String advertise, - final String advertiseURI, - final Configuration configuration) { - - final boolean isAppend = Booleans.parseBoolean(append, true); - final boolean isFlush = Booleans.parseBoolean(immediateFlush, true); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - final boolean isAdvertise = Boolean.parseBoolean(advertise); - final int bufferSize = Integers.parseInt(bufferSizeStr, RandomAccessFileManager.DEFAULT_BUFFER_SIZE); - - return RandomAccessFileAppender.newBuilder() - .setAdvertise(isAdvertise) - .setAdvertiseURI(advertiseURI) - .setAppend(isAppend) - .withBufferSize(bufferSize) - .setConfiguration(configuration) - .setFileName(fileName) - .withFilter(filter) - .withIgnoreExceptions(ignoreExceptions) - .withImmediateFlush(isFlush) - .withLayout(layout) - .withName(name) - .build(); - } - /** * Creates a builder for a RandomAccessFileAppender. * @return a builder for a RandomAccessFileAppender. */ - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java index 59f0df33b6d..ba2c52b4128 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileManager.java @@ -43,7 +43,6 @@ public class RandomAccessFileManager extends OutputStreamManager { private final String advertiseURI; private final RandomAccessFile randomAccessFile; - private final ThreadLocal isEndOfBatch = new ThreadLocal<>(); protected RandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile file, final String fileName, final OutputStream os, final int bufferSize, final String advertiseURI, @@ -51,7 +50,6 @@ protected RandomAccessFileManager(final LoggerContext loggerContext, final Rando super(loggerContext, os, fileName, false, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); this.randomAccessFile = file; this.advertiseURI = advertiseURI; - this.isEndOfBatch.set(Boolean.FALSE); } /** @@ -60,7 +58,7 @@ protected RandomAccessFileManager(final LoggerContext loggerContext, final Rando * @param fileName The name of the file to manage. * @param append true if the file should be appended to, false if it should * be overwritten. - * @param isFlush true if the contents should be flushed to disk on every + * @param immediateFlush true if the contents should be flushed to disk on every * write * @param bufferSize The buffer size. * @param advertiseURI the URI to use when advertising the file @@ -69,18 +67,29 @@ protected RandomAccessFileManager(final LoggerContext loggerContext, final Rando * @return A RandomAccessFileManager for the File. */ public static RandomAccessFileManager getFileManager(final String fileName, final boolean append, - final boolean isFlush, final int bufferSize, final String advertiseURI, + final boolean immediateFlush, final int bufferSize, final String advertiseURI, final Layout layout, final Configuration configuration) { - return narrow(RandomAccessFileManager.class, getManager(fileName, new FactoryData(append, - isFlush, bufferSize, advertiseURI, layout, configuration), FACTORY)); + return narrow(RandomAccessFileManager.class, getManager(fileName, + new FactoryData(append, immediateFlush, bufferSize, advertiseURI, layout, configuration), FACTORY)); } + /** + * No longer used, the {@link org.apache.logging.log4j.core.LogEvent#isEndOfBatch()} attribute is used instead. + * @return {@link Boolean#FALSE}. + * @deprecated end-of-batch on the event is used instead. + */ + @Deprecated public Boolean isEndOfBatch() { - return isEndOfBatch.get(); + return Boolean.FALSE; } - public void setEndOfBatch(final boolean endOfBatch) { - this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); + /** + * No longer used, the {@link org.apache.logging.log4j.core.LogEvent#isEndOfBatch()} attribute is used instead. + * This method is a no-op. + * @deprecated end-of-batch on the event is used instead. + */ + @Deprecated + public void setEndOfBatch(@SuppressWarnings("unused") final boolean endOfBatch) { } @Override @@ -193,7 +202,7 @@ public RandomAccessFileManager createManager(final String name, final FactoryDat final boolean writeHeader = !data.append || !file.exists(); final OutputStream os = NullOutputStream.getInstance(); - RandomAccessFile raf; + final RandomAccessFile raf; try { FileUtils.makeParentDirs(file); raf = new RandomAccessFile(name, "rw"); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java index def63a4d335..92d1b2153aa 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java @@ -16,14 +16,7 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.zip.Deflater; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -33,33 +26,38 @@ import org.apache.logging.log4j.core.appender.rolling.RollingFileManager; import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.net.Advertiser; -import org.apache.logging.log4j.core.util.Booleans; -import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.zip.Deflater; /** * An appender that writes to files and can roll over at intervals. */ -@Plugin(name = RollingFileAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin(RollingFileAppender.PLUGIN_NAME) public final class RollingFileAppender extends AbstractOutputStreamAppender { public static final String PLUGIN_NAME = "RollingFile"; /** * Builds FileAppender instances. - * + * * @param * The type to build * @since 2.7 */ public static class Builder> extends AbstractOutputStreamAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute private String fileName; @@ -74,11 +72,11 @@ public static class Builder> extends AbstractOutputStreamAp @PluginBuilderAttribute private boolean locking; - @PluginElement("Policy") + @PluginElement("Policy") @Required private TriggeringPolicy policy; - - @PluginElement("Strategy") + + @PluginElement("Strategy") private RolloverStrategy strategy; @PluginBuilderAttribute @@ -127,17 +125,17 @@ public RollingFileAppender build() { if (strategy == null) { if (fileName != null) { strategy = DefaultRolloverStrategy.newBuilder() - .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) - .withConfig(getConfiguration()) + .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) + .setConfig(getConfiguration()) .build(); } else { strategy = DirectWriteRolloverStrategy.newBuilder() - .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) - .withConfig(getConfiguration()) + .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) + .setConfig(getConfiguration()) .build(); } } else if (fileName == null && !(strategy instanceof DirectFileRolloverStrategy)) { - LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFilenameRolloverStrategy must be configured"); + LOGGER.error("RollingFileAppender '{}': When no file name is provided a {} must be configured", getName(), DirectFileRolloverStrategy.class.getSimpleName()); return null; } @@ -152,7 +150,7 @@ isBufferedIo, policy, strategy, advertiseUri, layout, bufferSize, isImmediateFlu manager.initialize(); return new RollingFileAppender(getName(), layout, getFilter(), manager, fileName, filePattern, - isIgnoreExceptions(), isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null); + isIgnoreExceptions(), !isBufferedIo || isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null); } public String getAdvertiseUri() { @@ -191,32 +189,32 @@ public String getFileGroup() { return fileGroup; } - public B withAdvertise(final boolean advertise) { + public B setAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } - public B withAdvertiseUri(final String advertiseUri) { + public B setAdvertiseUri(final String advertiseUri) { this.advertiseUri = advertiseUri; return asBuilder(); } - public B withAppend(final boolean append) { + public B setAppend(final boolean append) { this.append = append; return asBuilder(); } - public B withFileName(final String fileName) { + public B setFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } - public B withCreateOnDemand(final boolean createOnDemand) { + public B setCreateOnDemand(final boolean createOnDemand) { this.createOnDemand = createOnDemand; return asBuilder(); } - public B withLocking(final boolean locking) { + public B setLocking(final boolean locking) { this.locking = locking; return asBuilder(); } @@ -233,39 +231,37 @@ public RolloverStrategy getStrategy() { return strategy; } - public B withFilePattern(final String filePattern) { + public B setFilePattern(final String filePattern) { this.filePattern = filePattern; return asBuilder(); } - public B withPolicy(final TriggeringPolicy policy) { + public B setPolicy(final TriggeringPolicy policy) { this.policy = policy; return asBuilder(); } - public B withStrategy(final RolloverStrategy strategy) { + public B setStrategy(final RolloverStrategy strategy) { this.strategy = strategy; return asBuilder(); } - public B withFilePermissions(final String filePermissions) { + public B setFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } - public B withFileOwner(final String fileOwner) { + public B setFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } - public B withFileGroup(final String fileGroup) { + public B setFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return asBuilder(); } } - - private static final int DEFAULT_BUFFER_SIZE = 8192; private final String fileName; private final String filePattern; @@ -275,7 +271,7 @@ public B withFileGroup(final String fileGroup) { private RollingFileAppender(final String name, final Layout layout, final Filter filter, final RollingFileManager manager, final String fileName, final String filePattern, final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) { - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + super(name, layout, filter, ignoreExceptions, immediateFlush, null, manager); if (advertiser != null) { final Map configuration = new HashMap<>(layout.getContentFormat()); configuration.put("contentType", layout.getContentType()); @@ -334,78 +330,13 @@ public T getTriggeringPolicy() { return getManager().getTriggeringPolicy(); } - /** - * Creates a RollingFileAppender. - * @param fileName The name of the file that is actively written to. (required). - * @param filePattern The pattern of the file name to use on rollover. (required). - * @param append If true, events are appended to the file. If false, the file - * is overwritten when opened. Defaults to "true" - * @param name The name of the Appender (required). - * @param bufferedIO When true, I/O will be buffered. Defaults to "true". - * @param bufferSizeStr buffer size for buffered IO (default is 8192). - * @param immediateFlush When true, events are immediately flushed. Defaults to "true". - * @param policy The triggering policy. (required). - * @param strategy The rollover strategy. Defaults to DefaultRolloverStrategy. - * @param layout The layout to use (defaults to the default PatternLayout). - * @param filter The Filter or null. - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param advertise "true" if the appender configuration should be advertised, "false" otherwise. - * @param advertiseUri The advertised URI which can be used to retrieve the file contents. - * @param config The Configuration. - * @return A RollingFileAppender. - * @deprecated Use {@link #newBuilder()}. - */ - @Deprecated - public static > RollingFileAppender createAppender( - // @formatter:off - final String fileName, - final String filePattern, - final String append, - final String name, - final String bufferedIO, - final String bufferSizeStr, - final String immediateFlush, - final TriggeringPolicy policy, - final RolloverStrategy strategy, - final Layout layout, - final Filter filter, - final String ignore, - final String advertise, - final String advertiseUri, - final Configuration config) { - // @formatter:on - final int bufferSize = Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE); - // @formatter:off - return RollingFileAppender.newBuilder() - .withAdvertise(Boolean.parseBoolean(advertise)) - .withAdvertiseUri(advertiseUri) - .withAppend(Booleans.parseBoolean(append, true)) - .withBufferedIo(Booleans.parseBoolean(bufferedIO, true)) - .withBufferSize(bufferSize) - .setConfiguration(config) - .withFileName(fileName) - .withFilePattern(filePattern) - .withFilter(filter) - .withIgnoreExceptions(Booleans.parseBoolean(ignore, true)) - .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true)) - .withLayout(layout) - .withCreateOnDemand(false) - .withLocking(false) - .withName(name) - .withPolicy(policy) - .withStrategy(strategy) - .build(); - // @formatter:on - } - /** * Creates a new Builder. - * + * * @return a new Builder. * @since 2.7 */ - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java index f43ccd0499e..bd88dfcbab3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java @@ -16,14 +16,7 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.zip.Deflater; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -33,30 +26,35 @@ import org.apache.logging.log4j.core.appender.rolling.RollingRandomAccessFileManager; import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.net.Advertiser; -import org.apache.logging.log4j.core.util.Booleans; -import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.zip.Deflater; /** * An appender that writes to random access files and can roll over at * intervals. */ -@Plugin(name = "RollingRandomAccessFile", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("RollingRandomAccessFile") public final class RollingRandomAccessFileAppender extends AbstractOutputStreamAppender { public static class Builder> extends AbstractOutputStreamAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { public Builder() { super(); - withBufferSize(RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE); - withIgnoreExceptions(true); - withImmediateFlush(true); + setBufferSize(RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE); + setIgnoreExceptions(true); + setImmediateFlush(true); } @PluginBuilderAttribute("fileName") @@ -100,17 +98,17 @@ public RollingRandomAccessFileAppender build() { if (strategy == null) { if (fileName != null) { strategy = DefaultRolloverStrategy.newBuilder() - .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) - .withConfig(getConfiguration()) + .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) + .setConfig(getConfiguration()) .build(); } else { strategy = DirectWriteRolloverStrategy.newBuilder() - .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) - .withConfig(getConfiguration()) + .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION)) + .setConfig(getConfiguration()) .build(); } } else if (fileName == null && !(strategy instanceof DirectFileRolloverStrategy)) { - LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFilenameRolloverStrategy must be configured"); + LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFileRolloverStrategy must be configured", name); return null; } @@ -142,58 +140,58 @@ public RollingRandomAccessFileAppender build() { isIgnoreExceptions(), immediateFlush, bufferSize, advertise ? getConfiguration().getAdvertiser() : null); } - public B withFileName(final String fileName) { + public B setFileName(final String fileName) { this.fileName = fileName; return asBuilder(); } - public B withFilePattern(final String filePattern) { + public B setFilePattern(final String filePattern) { this.filePattern = filePattern; return asBuilder(); } - public B withAppend(final boolean append) { + public B setAppend(final boolean append) { this.append = append; return asBuilder(); } - public B withPolicy(final TriggeringPolicy policy) { + public B setPolicy(final TriggeringPolicy policy) { this.policy = policy; return asBuilder(); } - public B withStrategy(final RolloverStrategy strategy) { + public B setStrategy(final RolloverStrategy strategy) { this.strategy = strategy; return asBuilder(); } - public B withAdvertise(final boolean advertise) { + public B setAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } - public B withAdvertiseURI(final String advertiseURI) { + public B setAdvertiseURI(final String advertiseURI) { this.advertiseURI = advertiseURI; return asBuilder(); } - public B withFilePermissions(final String filePermissions) { + public B setFilePermissions(final String filePermissions) { this.filePermissions = filePermissions; return asBuilder(); } - public B withFileOwner(final String fileOwner) { + public B setFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return asBuilder(); } - public B withFileGroup(final String fileGroup) { + public B setFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return asBuilder(); } } - + private final String fileName; private final String filePattern; private final Object advertisement; @@ -203,7 +201,7 @@ private RollingRandomAccessFileAppender(final String name, final Layout configuration = new HashMap<>(layout.getContentFormat()); configuration.put("contentType", layout.getContentType()); @@ -238,14 +236,6 @@ public void append(final LogEvent event) { final RollingRandomAccessFileManager manager = getManager(); manager.checkRollover(event); - // Leverage the nice batching behaviour of async Loggers/Appenders: - // we can signal the file manager that it needs to flush the buffer - // to disk at the end of a batch. - // From a user's point of view, this means that all log events are - // _always_ available in the log file, without incurring the overhead - // of immediateFlush=true. - manager.setEndOfBatch(event.isEndOfBatch()); // FIXME manager's EndOfBatch threadlocal can be deleted - // LOG4J2-1292 utilize gc-free Layout.encode() method: taken care of in superclass super.append(event); } @@ -276,76 +266,7 @@ public int getBufferSize() { return getManager().getBufferSize(); } - /** - * Create a RollingRandomAccessFileAppender. - * - * @param fileName The name of the file that is actively written to. - * (required). - * @param filePattern The pattern of the file name to use on rollover. - * (required). - * @param append If true, events are appended to the file. If false, the - * file is overwritten when opened. Defaults to "true" - * @param name The name of the Appender (required). - * @param immediateFlush When true, events are immediately flushed. Defaults - * to "true". - * @param bufferSizeStr The buffer size, defaults to {@value RollingRandomAccessFileManager#DEFAULT_BUFFER_SIZE}. - * @param policy The triggering policy. (required). - * @param strategy The rollover strategy. Defaults to - * DefaultRolloverStrategy. - * @param layout The layout to use (defaults to the default PatternLayout). - * @param filter The Filter or null. - * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param advertise "true" if the appender configuration should be - * advertised, "false" otherwise. - * @param advertiseURI The advertised URI which can be used to retrieve the - * file contents. - * @param configuration The Configuration. - * @return A RollingRandomAccessFileAppender. - * @deprecated Use {@link #newBuilder()}. - */ - @Deprecated - public static > RollingRandomAccessFileAppender createAppender( - final String fileName, - final String filePattern, - final String append, - final String name, - final String immediateFlush, - final String bufferSizeStr, - final TriggeringPolicy policy, - final RolloverStrategy strategy, - final Layout layout, - final Filter filter, - final String ignoreExceptions, - final String advertise, - final String advertiseURI, - final Configuration configuration) { - - final boolean isAppend = Booleans.parseBoolean(append, true); - final boolean isIgnoreExceptions = Booleans.parseBoolean(ignoreExceptions, true); - final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, true); - final boolean isAdvertise = Boolean.parseBoolean(advertise); - final int bufferSize = Integers.parseInt(bufferSizeStr, RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE); - - return RollingRandomAccessFileAppender.newBuilder() - .withAdvertise(isAdvertise) - .withAdvertiseURI(advertiseURI) - .withAppend(isAppend) - .withBufferSize(bufferSize) - .setConfiguration(configuration) - .withFileName(fileName) - .withFilePattern(filePattern) - .withFilter(filter) - .withIgnoreExceptions(isIgnoreExceptions) - .withImmediateFlush(isImmediateFlush) - .withLayout(layout) - .withName(name) - .withPolicy(policy) - .withStrategy(strategy) - .build(); - } - - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelector.java deleted file mode 100644 index ab9dfb48f20..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelector.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.appender; - -import java.io.Serializable; -import java.util.Objects; - -import javax.script.Bindings; - -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.script.AbstractScript; -import org.apache.logging.log4j.core.script.ScriptManager; - -@Plugin(name = "ScriptAppenderSelector", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) -public class ScriptAppenderSelector extends AbstractAppender { - - /** - * Builds an appender. - */ - public static final class Builder implements org.apache.logging.log4j.core.util.Builder { - - @PluginElement("AppenderSet") - @Required - private AppenderSet appenderSet; - - @PluginConfiguration - @Required - private Configuration configuration; - - @PluginBuilderAttribute - @Required - private String name; - - @PluginElement("Script") - @Required - private AbstractScript script; - - @Override - public Appender build() { - if (name == null) { - LOGGER.error("Name missing."); - return null; - } - if (script == null) { - LOGGER.error("Script missing for ScriptAppenderSelector appender {}", name); - return null; - } - if (appenderSet == null) { - LOGGER.error("AppenderSet missing for ScriptAppenderSelector appender {}", name); - return null; - } - if (configuration == null) { - LOGGER.error("Configuration missing for ScriptAppenderSelector appender {}", name); - return null; - } - final ScriptManager scriptManager = configuration.getScriptManager(); - scriptManager.addScript(script); - final Bindings bindings = scriptManager.createBindings(script); - final Object object = scriptManager.execute(script.getName(), bindings); - final String appenderName = Objects.toString(object, null); - final Appender appender = appenderSet.createAppender(appenderName, name); - return appender; - } - - public AppenderSet getAppenderSet() { - return appenderSet; - } - - public Configuration getConfiguration() { - return configuration; - } - - public String getName() { - return name; - } - - public AbstractScript getScript() { - return script; - } - - public Builder withAppenderNodeSet(@SuppressWarnings("hiding") final AppenderSet appenderSet) { - this.appenderSet = appenderSet; - return this; - } - - public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { - this.configuration = configuration; - return this; - } - - public Builder withName(@SuppressWarnings("hiding") final String name) { - this.name = name; - return this; - } - - public Builder withScript(@SuppressWarnings("hiding") final AbstractScript script) { - this.script = script; - return this; - } - - } - - @PluginBuilderFactory - public static Builder newBuilder() { - return new Builder(); - } - - private ScriptAppenderSelector(final String name, final Filter filter, final Layout layout) { - super(name, filter, layout); - } - - @Override - public void append(final LogEvent event) { - // Do nothing: This appender is only used to discover and build another appender - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SmtpAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SmtpAppender.java deleted file mode 100644 index e3aac4dbdd4..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SmtpAppender.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender; - -import java.io.Serializable; - -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort; -import org.apache.logging.log4j.core.filter.ThresholdFilter; -import org.apache.logging.log4j.core.layout.HtmlLayout; -import org.apache.logging.log4j.core.net.SmtpManager; -import org.apache.logging.log4j.core.util.Booleans; - -/** - * Send an e-mail when a specific logging event occurs, typically on errors or - * fatal errors. - * - *

- * The number of logging events delivered in this e-mail depend on the value of - * BufferSize option. The SmtpAppender keeps only the last - * BufferSize logging events in its cyclic buffer. This keeps - * memory requirements at a reasonable level while still delivering useful - * application context. - * - * By default, an email message will formatted as HTML. This can be modified by - * setting a layout for the appender. - * - * By default, an email message will be sent when an ERROR or higher severity - * message is appended. This can be modified by setting a filter for the - * appender. - */ -@Plugin(name = "SMTP", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) -public final class SmtpAppender extends AbstractAppender { - - private static final int DEFAULT_BUFFER_SIZE = 512; - - /** The SMTP Manager */ - private final SmtpManager manager; - - private SmtpAppender(final String name, final Filter filter, final Layout layout, final SmtpManager manager, - final boolean ignoreExceptions) { - super(name, filter, layout, ignoreExceptions); - this.manager = manager; - } - - /** - * Create a SmtpAppender. - * - * @param name - * The name of the Appender. - * @param to - * The comma-separated list of recipient email addresses. - * @param cc - * The comma-separated list of CC email addresses. - * @param bcc - * The comma-separated list of BCC email addresses. - * @param from - * The email address of the sender. - * @param replyTo - * The comma-separated list of reply-to email addresses. - * @param subject The subject of the email message. - * @param smtpProtocol The SMTP transport protocol (such as "smtps", defaults to "smtp"). - * @param smtpHost - * The SMTP hostname to send to. - * @param smtpPortStr - * The SMTP port to send to. - * @param smtpUsername - * The username required to authenticate against the SMTP server. - * @param smtpPassword - * The password required to authenticate against the SMTP server. - * @param smtpDebug - * Enable mail session debuging on STDOUT. - * @param bufferSizeStr - * How many log events should be buffered for inclusion in the - * message? - * @param layout - * The layout to use (defaults to HtmlLayout). - * @param filter - * The Filter or null (defaults to ThresholdFilter, level of - * ERROR). - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @return The SmtpAppender. - */ - @PluginFactory - public static SmtpAppender createAppender( - @PluginConfiguration final Configuration config, - @PluginAttribute("name") @Required final String name, - @PluginAttribute("to") final String to, - @PluginAttribute("cc") final String cc, - @PluginAttribute("bcc") final String bcc, - @PluginAttribute("from") final String from, - @PluginAttribute("replyTo") final String replyTo, - @PluginAttribute("subject") final String subject, - @PluginAttribute("smtpProtocol") final String smtpProtocol, - @PluginAttribute("smtpHost") final String smtpHost, - @PluginAttribute(value = "smtpPort", defaultString = "0") @ValidPort final String smtpPortStr, - @PluginAttribute("smtpUsername") final String smtpUsername, - @PluginAttribute(value = "smtpPassword", sensitive = true) final String smtpPassword, - @PluginAttribute("smtpDebug") final String smtpDebug, - @PluginAttribute("bufferSize") final String bufferSizeStr, - @PluginElement("Layout") Layout layout, - @PluginElement("Filter") Filter filter, - @PluginAttribute("ignoreExceptions") final String ignore) { - if (name == null) { - LOGGER.error("No name provided for SmtpAppender"); - return null; - } - - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - final int smtpPort = AbstractAppender.parseInt(smtpPortStr, 0); - final boolean isSmtpDebug = Boolean.parseBoolean(smtpDebug); - final int bufferSize = bufferSizeStr == null ? DEFAULT_BUFFER_SIZE : Integer.parseInt(bufferSizeStr); - - if (layout == null) { - layout = HtmlLayout.createDefaultLayout(); - } - if (filter == null) { - filter = ThresholdFilter.createFilter(null, null, null); - } - final Configuration configuration = config != null ? config : new DefaultConfiguration(); - - final SmtpManager manager = SmtpManager.getSmtpManager(configuration, to, cc, bcc, from, replyTo, subject, smtpProtocol, - smtpHost, smtpPort, smtpUsername, smtpPassword, isSmtpDebug, filter.toString(), bufferSize); - if (manager == null) { - return null; - } - - return new SmtpAppender(name, filter, layout, manager, ignoreExceptions); - } - - /** - * Capture all events in CyclicBuffer. - * @param event The Log event. - * @return true if the event should be filtered. - */ - @Override - public boolean isFiltered(final LogEvent event) { - final boolean filtered = super.isFiltered(event); - if (filtered) { - manager.add(event); - } - return filtered; - } - - /** - * Perform SmtpAppender specific appending actions, mainly adding the event - * to a cyclic buffer and checking if the event triggers an e-mail to be - * sent. - * @param event The Log event. - */ - @Override - public void append(final LogEvent event) { - manager.sendEvents(getLayout(), event); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SocketAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SocketAppender.java index b1d167c018f..907e0ecf227 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SocketAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SocketAppender.java @@ -16,26 +16,11 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort; import org.apache.logging.log4j.core.net.AbstractSocketManager; import org.apache.logging.log4j.core.net.Advertiser; import org.apache.logging.log4j.core.net.DatagramSocketManager; @@ -44,12 +29,25 @@ import org.apache.logging.log4j.core.net.SslSocketManager; import org.apache.logging.log4j.core.net.TcpSocketManager; import org.apache.logging.log4j.core.net.ssl.SslConfiguration; -import org.apache.logging.log4j.core.util.Booleans; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.ValidHost; +import org.apache.logging.log4j.plugins.validation.constraints.ValidPort; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * An Appender that delivers events over socket connections. Supports both TCP and UDP. */ -@Plugin(name = "Socket", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Socket") public class SocketAppender extends AbstractOutputStreamAppender { /** @@ -64,7 +62,7 @@ public class SocketAppender extends AbstractOutputStreamAppenderRemoved deprecated "delayMillis", use "reconnectionDelayMillis". *

  • Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".
  • * - * + * * @param * The type to build. */ @@ -93,10 +91,10 @@ public static abstract class AbstractBuilder> exten @PluginBuilderAttribute @PluginAliases({ "reconnectDelay", "reconnectionDelay", "delayMillis", "reconnectionDelayMillis" }) private int reconnectDelayMillis; - + @PluginElement("SocketOptions") private SocketOptions socketOptions; - + @PluginElement("SslConfiguration") @PluginAliases({ "SslConfig" }) private SslConfiguration sslConfiguration; @@ -129,47 +127,47 @@ public boolean getImmediateFail() { return immediateFail; } - public B withAdvertise(final boolean advertise) { + public B setAdvertise(final boolean advertise) { this.advertise = advertise; return asBuilder(); } - public B withConnectTimeoutMillis(final int connectTimeoutMillis) { + public B setConnectTimeoutMillis(final int connectTimeoutMillis) { this.connectTimeoutMillis = connectTimeoutMillis; return asBuilder(); } - public B withHost(final String host) { + public B setHost(final String host) { this.host = host; return asBuilder(); } - public B withImmediateFail(final boolean immediateFail) { + public B setImmediateFail(final boolean immediateFail) { this.immediateFail = immediateFail; return asBuilder(); } - public B withPort(final int port) { + public B setPort(final int port) { this.port = port; return asBuilder(); } - public B withProtocol(final Protocol protocol) { + public B setProtocol(final Protocol protocol) { this.protocol = protocol; return asBuilder(); } - public B withReconnectDelayMillis(final int reconnectDelayMillis) { + public B setReconnectDelayMillis(final int reconnectDelayMillis) { this.reconnectDelayMillis = reconnectDelayMillis; return asBuilder(); } - public B withSocketOptions(final SocketOptions socketOptions) { + public B setSocketOptions(final SocketOptions socketOptions) { this.socketOptions = socketOptions; return asBuilder(); } - public B withSslConfiguration(final SslConfiguration sslConfiguration) { + public B setSslConfiguration(final SslConfiguration sslConfiguration) { this.sslConfiguration = sslConfiguration; return asBuilder(); } @@ -183,16 +181,16 @@ public SocketOptions getSocketOptions() { } } - + /** * Builds a SocketAppender. - *
      + *
        *
      • Removed deprecated "delayMillis", use "reconnectionDelayMillis".
      • *
      • Removed deprecated "reconnectionDelay", use "reconnectionDelayMillis".
      • - *
      + *
    */ public static class Builder extends AbstractBuilder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @SuppressWarnings("resource") @Override @@ -224,8 +222,8 @@ public SocketAppender build() { !bufferedIo || immediateFlush, getAdvertise() ? getConfiguration().getAdvertiser() : null); } } - - @PluginBuilderFactory + + @PluginFactory public static Builder newBuilder() { return new Builder(); } @@ -236,7 +234,7 @@ public static Builder newBuilder() { protected SocketAppender(final String name, final Layout layout, final Filter filter, final AbstractSocketManager manager, final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) { - super(name, layout, filter, ignoreExceptions, immediateFlush, manager); + super(name, layout, filter, ignoreExceptions, immediateFlush, null, manager); if (advertiser != null) { final Map configuration = new HashMap<>(layout.getContentFormat()); configuration.putAll(manager.getContentFormat()); @@ -260,159 +258,6 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { return true; } - /** - * Creates a socket appender. - * - * @param host - * The name of the host to connect to. - * @param port - * The port to connect to on the target host. - * @param protocol - * The Protocol to use. - * @param sslConfig - * The SSL configuration file for TCP/SSL, ignored for UPD. - * @param connectTimeoutMillis - * the connect timeout in milliseconds. - * @param reconnectDelayMillis - * The interval in which failed writes should be retried. - * @param immediateFail - * True if the write should fail if no socket is immediately available. - * @param name - * The name of the Appender. - * @param immediateFlush - * "true" if data should be flushed on each write. - * @param ignoreExceptions - * If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they - * are propagated to the caller. - * @param layout - * The layout to use. Required, there is no default. - * @param filter - * The Filter or null. - * @param advertise - * "true" if the appender configuration should be advertised, "false" otherwise. - * @param configuration - * The Configuration - * @return A SocketAppender. - * @deprecated Deprecated in 2.7; use {@link #newBuilder()} - */ - @Deprecated - @PluginFactory - public static SocketAppender createAppender( - // @formatter:off - final String host, - final int port, - final Protocol protocol, - final SslConfiguration sslConfig, - final int connectTimeoutMillis, - final int reconnectDelayMillis, - final boolean immediateFail, - final String name, - final boolean immediateFlush, - final boolean ignoreExceptions, - final Layout layout, - final Filter filter, - final boolean advertise, - final Configuration configuration) { - // @formatter:on - - // @formatter:off - return newBuilder() - .withAdvertise(advertise) - .setConfiguration(configuration) - .withConnectTimeoutMillis(connectTimeoutMillis) - .withFilter(filter) - .withHost(host) - .withIgnoreExceptions(ignoreExceptions) - .withImmediateFail(immediateFail) - .withLayout(layout) - .withName(name) - .withPort(port) - .withProtocol(protocol) - .withReconnectDelayMillis(reconnectDelayMillis) - .withSslConfiguration(sslConfig) - .build(); - // @formatter:on - } - - /** - * Creates a socket appender. - * - * @param host - * The name of the host to connect to. - * @param portNum - * The port to connect to on the target host. - * @param protocolIn - * The Protocol to use. - * @param sslConfig - * The SSL configuration file for TCP/SSL, ignored for UPD. - * @param connectTimeoutMillis - * the connect timeout in milliseconds. - * @param delayMillis - * The interval in which failed writes should be retried. - * @param immediateFail - * True if the write should fail if no socket is immediately available. - * @param name - * The name of the Appender. - * @param immediateFlush - * "true" if data should be flushed on each write. - * @param ignore - * If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they - * are propagated to the caller. - * @param layout - * The layout to use. Required, there is no default. - * @param filter - * The Filter or null. - * @param advertise - * "true" if the appender configuration should be advertised, "false" otherwise. - * @param config - * The Configuration - * @return A SocketAppender. - * @deprecated Deprecated in 2.5; use {@link #newBuilder()} - */ - @Deprecated - public static SocketAppender createAppender( - // @formatter:off - final String host, - final String portNum, - final String protocolIn, - final SslConfiguration sslConfig, - final int connectTimeoutMillis, - // deprecated - final String delayMillis, - final String immediateFail, - final String name, - final String immediateFlush, - final String ignore, - final Layout layout, - final Filter filter, - final String advertise, - final Configuration config) { - // @formatter:on - final boolean isFlush = Booleans.parseBoolean(immediateFlush, true); - final boolean isAdvertise = Boolean.parseBoolean(advertise); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - final boolean fail = Booleans.parseBoolean(immediateFail, true); - final int reconnectDelayMillis = AbstractAppender.parseInt(delayMillis, 0); - final int port = AbstractAppender.parseInt(portNum, 0); - final Protocol p = protocolIn == null ? Protocol.UDP : Protocol.valueOf(protocolIn); - return createAppender(host, port, p, sslConfig, connectTimeoutMillis, reconnectDelayMillis, fail, name, isFlush, - ignoreExceptions, layout, filter, isAdvertise, config); - } - - /** - * Creates an AbstractSocketManager for TCP, UDP, and SSL. - * - * @throws IllegalArgumentException - * if the protocol cannot be handled. - * @deprecated Use {@link #createSocketManager(String, Protocol, String, int, int, SslConfiguration, int, boolean, Layout, int, SocketOptions)}. - */ - @Deprecated - protected static AbstractSocketManager createSocketManager(final String name, final Protocol protocol, final String host, - final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig, final int reconnectDelayMillis, - final boolean immediateFail, final Layout layout, final int bufferSize) { - return createSocketManager(name, protocol, host, port, connectTimeoutMillis, sslConfig, reconnectDelayMillis, immediateFail, layout, bufferSize, null); - } - /** * Creates an AbstractSocketManager for TCP, UDP, and SSL. * diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java index 26b435fe964..03525b31470 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SyslogAppender.java @@ -21,14 +21,9 @@ import java.nio.charset.StandardCharsets; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.layout.LoggerFields; import org.apache.logging.log4j.core.layout.Rfc5424Layout; import org.apache.logging.log4j.core.layout.SyslogLayout; @@ -38,72 +33,77 @@ import org.apache.logging.log4j.core.net.Protocol; import org.apache.logging.log4j.core.net.ssl.SslConfiguration; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.util.EnglishEnums; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; /** * The Syslog Appender. */ -@Plugin(name = "Syslog", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Syslog") public class SyslogAppender extends SocketAppender { public static class Builder> extends AbstractBuilder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute(value = "facility") private Facility facility = Facility.LOCAL0; @PluginBuilderAttribute("id") private String id; - + @PluginBuilderAttribute(value = "enterpriseNumber") - private int enterpriseNumber = Rfc5424Layout.DEFAULT_ENTERPRISE_NUMBER; - + private String enterpriseNumber = String.valueOf(Rfc5424Layout.DEFAULT_ENTERPRISE_NUMBER); + @PluginBuilderAttribute(value = "includeMdc") private boolean includeMdc = true; - + @PluginBuilderAttribute("mdcId") private String mdcId; - + @PluginBuilderAttribute("mdcPrefix") private String mdcPrefix; - + @PluginBuilderAttribute("eventPrefix") private String eventPrefix; - + @PluginBuilderAttribute(value = "newLine") private boolean newLine; - + @PluginBuilderAttribute("newLineEscape") private String escapeNL; - + @PluginBuilderAttribute("appName") private String appName; - + @PluginBuilderAttribute("messageId") private String msgId; - + @PluginBuilderAttribute("mdcExcludes") private String excludes; - + @PluginBuilderAttribute("mdcIncludes") private String includes; - + @PluginBuilderAttribute("mdcRequired") private String required; - + @PluginBuilderAttribute("format") private String format; - + @PluginBuilderAttribute("charset") private Charset charsetName = StandardCharsets.UTF_8; - + @PluginBuilderAttribute("exceptionPattern") private String exceptionPattern; - + @PluginElement("LoggerFields") private LoggerFields[] loggerFields; - @SuppressWarnings({"resource", "unchecked"}) + @SuppressWarnings({"resource"}) @Override public SyslogAppender build() { final Protocol protocol = getProtocol(); @@ -113,10 +113,26 @@ public SyslogAppender build() { Layout layout = getLayout(); if (layout == null) { layout = RFC5424.equalsIgnoreCase(format) - ? Rfc5424Layout.createLayout(facility, id, enterpriseNumber, includeMdc, mdcId, mdcPrefix, - eventPrefix, newLine, escapeNL, appName, msgId, excludes, includes, required, - exceptionPattern, useTlsMessageFormat, loggerFields, configuration) - : + ? new Rfc5424Layout.Rfc5424LayoutBuilder() + .setFacility(facility) + .setId(id) + .setEin(enterpriseNumber) + .setIncludeMDC(includeMdc) + .setMdcId(mdcId) + .setMdcPrefix(mdcPrefix) + .setEventPrefix(eventPrefix) + .setIncludeNL(newLine) + .setEscapeNL(escapeNL) + .setAppName(appName) + .setMessageId(msgId) + .setExcludes(excludes) + .setIncludes(includes) + .setRequired(required) + .setExceptionPattern(exceptionPattern) + .setUseTLSMessageFormat(useTlsMessageFormat) + .setLoggerFields(loggerFields) + .setConfig(configuration) + .build() : // @formatter:off SyslogLayout.newBuilder() .setFacility(facility) @@ -146,7 +162,7 @@ public String getId() { return id; } - public int getEnterpriseNumber() { + public String getEnterpriseNumber() { return enterpriseNumber; } @@ -220,11 +236,19 @@ public B setId(final String id) { return asBuilder(); } - public B setEnterpriseNumber(final int enterpriseNumber) { + public B setEnterpriseNumber(final String enterpriseNumber) { this.enterpriseNumber = enterpriseNumber; return asBuilder(); } + /** + * @deprecated Use {@link #setEnterpriseNumber(String)} instead + */ + public B setEnterpriseNumber(final int enterpriseNumber) { + this.enterpriseNumber = String.valueOf(enterpriseNumber); + return asBuilder(); + } + public B setIncludeMdc(final boolean includeMdc) { this.includeMdc = includeMdc; return asBuilder(); @@ -300,7 +324,7 @@ public B setLoggerFields(final LoggerFields[] loggerFields) { return asBuilder(); } } - + protected static final String RFC5424 = "RFC5424"; protected SyslogAppender(final String name, final Layout layout, final Filter filter, @@ -310,119 +334,8 @@ protected SyslogAppender(final String name, final Layout } - /** - * Creates a SyslogAppender. - * @param host The name of the host to connect to. - * @param port The port to connect to on the target host. - * @param protocolStr The Protocol to use. - * @param sslConfiguration TODO - * @param connectTimeoutMillis the connect timeout in milliseconds. - * @param reconnectDelayMillis The interval in which failed writes should be retried. - * @param immediateFail True if the write should fail if no socket is immediately available. - * @param name The name of the Appender. - * @param immediateFlush "true" if data should be flushed on each write. - * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; - * otherwise they are propagated to the caller. - * @param facility The Facility is used to try to classify the message. - * @param id The default structured data id to use when formatting according to RFC 5424. - * @param enterpriseNumber The IANA enterprise number. - * @param includeMdc Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog - * record. Defaults to "true:. - * @param mdcId The id to use for the MDC Structured Data Element. - * @param mdcPrefix The prefix to add to MDC key names. - * @param eventPrefix The prefix to add to event key names. - * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false. - * @param escapeNL String that should be used to replace newlines within the message text. - * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record. - * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records. - * @param excludes A comma separated list of mdc keys that should be excluded from the LogEvent. - * @param includes A comma separated list of mdc keys that should be included in the FlumeEvent. - * @param required A comma separated list of mdc keys that must be present in the MDC. - * @param format If set to "RFC5424" the data will be formatted in accordance with RFC 5424. Otherwise, - * it will be formatted as a BSD Syslog record. - * @param filter A Filter to determine if the event should be handled by this Appender. - * @param configuration The Configuration. - * @param charset The character set to use when converting the syslog String to a byte array. - * @param exceptionPattern The converter pattern to use for formatting exceptions. - * @param loggerFields The logger fields - * @param advertise Whether to advertise - * @return A SyslogAppender. - * @deprecated Use {@link #newSyslogAppenderBuilder()}. - */ - @Deprecated - public static > SyslogAppender createAppender( - // @formatter:off - final String host, - final int port, - final String protocolStr, - final SslConfiguration sslConfiguration, - final int connectTimeoutMillis, - final int reconnectDelayMillis, - final boolean immediateFail, - final String name, - final boolean immediateFlush, - final boolean ignoreExceptions, - final Facility facility, - final String id, - final int enterpriseNumber, - final boolean includeMdc, - final String mdcId, - final String mdcPrefix, - final String eventPrefix, - final boolean newLine, - final String escapeNL, - final String appName, - final String msgId, - final String excludes, - final String includes, - final String required, - final String format, - final Filter filter, - final Configuration configuration, - final Charset charset, - final String exceptionPattern, - final LoggerFields[] loggerFields, - final boolean advertise) { - // @formatter:on - - // @formatter:off - return SyslogAppender.newSyslogAppenderBuilder() - .withHost(host) - .withPort(port) - .withProtocol(EnglishEnums.valueOf(Protocol.class, protocolStr)) - .withSslConfiguration(sslConfiguration) - .withConnectTimeoutMillis(connectTimeoutMillis) - .withReconnectDelayMillis(reconnectDelayMillis) - .withImmediateFail(immediateFail) - .withName(appName) - .withImmediateFlush(immediateFlush) - .withIgnoreExceptions(ignoreExceptions) - .withFilter(filter) - .setConfiguration(configuration) - .withAdvertise(advertise) - .setFacility(facility) - .setId(id) - .setEnterpriseNumber(enterpriseNumber) - .setIncludeMdc(includeMdc) - .setMdcId(mdcId) - .setMdcPrefix(mdcPrefix) - .setEventPrefix(eventPrefix) - .setNewLine(newLine) - .setAppName(appName) - .setMsgId(msgId) - .setExcludes(excludes) - .setIncludeMdc(includeMdc) - .setRequired(required) - .setFormat(format) - .setCharsetName(charset) - .setExceptionPattern(exceptionPattern) - .setLoggerFields(loggerFields) - .build(); - // @formatter:on - } - // Calling this method newBuilder() does not compile - @PluginBuilderFactory + @PluginFactory public static > B newSyslogAppenderBuilder() { return new Builder().asBuilder(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/TlsSyslogFrame.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/TlsSyslogFrame.java index 1b1a61aa6bf..27e90cdfd45 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/TlsSyslogFrame.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/TlsSyslogFrame.java @@ -22,7 +22,7 @@ /** * Wraps messages that are formatted according to RFC 5425. - * + * * @see RFC 5425 */ public class TlsSyslogFrame { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterAppender.java index dc8f4d24ff1..55f21bdbf37 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterAppender.java @@ -16,74 +16,51 @@ */ package org.apache.logging.log4j.core.appender; -import java.io.Writer; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.StringLayout; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.util.CloseShieldWriter; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.io.Writer; /** * Appends log events to a {@link Writer}. */ -@Plugin(name = "Writer", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Writer") public final class WriterAppender extends AbstractWriterAppender { /** * Builds WriterAppender instances. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { - - private Filter filter; + public static class Builder> extends AbstractAppender.Builder + implements org.apache.logging.log4j.plugins.util.Builder { private boolean follow = false; - private boolean ignoreExceptions = true; - - private StringLayout layout = PatternLayout.createDefaultLayout(); - - private String name; - private Writer target; @Override public WriterAppender build() { - return new WriterAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions); + final StringLayout layout = (StringLayout) getLayout(); + final StringLayout actualLayout = layout != null ? layout : PatternLayout.createDefaultLayout(); + return new WriterAppender(getName(), actualLayout, getFilter(), getManager(target, follow, actualLayout), + isIgnoreExceptions(), getPropertyArray()); } - public Builder setFilter(final Filter aFilter) { - this.filter = aFilter; - return this; - } - - public Builder setFollow(final boolean shouldFollow) { + public B setFollow(final boolean shouldFollow) { this.follow = shouldFollow; - return this; - } - - public Builder setIgnoreExceptions(final boolean shouldIgnoreExceptions) { - this.ignoreExceptions = shouldIgnoreExceptions; - return this; - } - - public Builder setLayout(final StringLayout aLayout) { - this.layout = aLayout; - return this; - } - - public Builder setName(final String aName) { - this.name = aName; - return this; + return asBuilder(); } - public Builder setTarget(final Writer aTarget) { + public B setTarget(final Writer aTarget) { this.target = aTarget; - return this; + return asBuilder(); } } /** @@ -96,7 +73,7 @@ private static class FactoryData { /** * Builds instances. - * + * * @param writer * The OutputStream. * @param type @@ -115,7 +92,7 @@ private static class WriterManagerFactory implements ManagerFactory> B newBuilder() { + return new Builder().asBuilder(); } private WriterAppender(final String name, final StringLayout layout, final Filter filter, - final WriterManager manager, final boolean ignoreExceptions) { - super(name, layout, filter, ignoreExceptions, true, manager); + final WriterManager manager, final boolean ignoreExceptions, final Property[] properties) { + super(name, layout, filter, ignoreExceptions, true, properties, manager); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java index 09798fdabce..62bd192b195 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java @@ -28,6 +28,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.config.Property; /** * An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders @@ -39,27 +40,17 @@ */ public abstract class AbstractDatabaseAppender extends AbstractAppender { + public static class Builder> extends AbstractAppender.Builder { + // empty for now. + } + + public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); - private T manager; - /** - * Instantiates the base appender. - * - * @param name The appender name. - * @param filter The filter, if any, to use. - * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param manager The matching {@link AbstractDatabaseManager} implementation. - */ - protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions, - final T manager) { - super(name, filter, null, ignoreExceptions); - this.manager = manager; - } - /** * Instantiates the base appender. * @@ -68,14 +59,33 @@ protected AbstractDatabaseAppender(final String name, final Filter filter, final * @param layout The layout to use to format the event. * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise * they are propagated to the caller. + * @param properties TODO * @param manager The matching {@link AbstractDatabaseManager} implementation. */ protected AbstractDatabaseAppender(final String name, final Filter filter, - final Layout layout, final boolean ignoreExceptions, final T manager) { - super(name, filter, layout, ignoreExceptions); + final Layout layout, final boolean ignoreExceptions, final Property[] properties, final T manager) { + super(name, filter, layout, ignoreExceptions, properties); this.manager = manager; } + @Override + public final void append(final LogEvent event) { + this.readLock.lock(); + try { + this.getManager().write(event, toSerializable(event)); + } catch (final LoggingException e) { + LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), + this.getName(), e); + throw e; + } catch (final Exception e) { + LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), + this.getName(), e); + throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e); + } finally { + this.readLock.unlock(); + } + } + /** * This always returns {@code null}, as database appenders do not use a single layout. The JPA and NoSQL appenders * do not use a layout at all. The JDBC appender has a layout-per-column pattern. @@ -96,6 +106,27 @@ public final T getManager() { return this.manager; } + /** + * Replaces the underlying manager in use within this appender. This can be useful for manually changing the way log + * events are written to the database without losing buffered or in-progress events. The existing manager is + * released only after the new manager has been installed. This method is thread-safe. + * + * @param manager The new manager to install. + */ + protected final void replaceManager(final T manager) { + this.writeLock.lock(); + try { + final T old = this.getManager(); + if (!manager.isRunning()) { + manager.startup(); + } + this.manager = manager; + old.close(); + } finally { + this.writeLock.unlock(); + } + } + @Override public final void start() { if (this.getManager() == null) { @@ -117,43 +148,4 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { setStopped(); return stopped; } - - @Override - public final void append(final LogEvent event) { - this.readLock.lock(); - try { - this.getManager().write(event, toSerializable(event)); - } catch (final LoggingException e) { - LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), - this.getName(), e); - throw e; - } catch (final Exception e) { - LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(), - this.getName(), e); - throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e); - } finally { - this.readLock.unlock(); - } - } - - /** - * Replaces the underlying manager in use within this appender. This can be useful for manually changing the way log - * events are written to the database without losing buffered or in-progress events. The existing manager is - * released only after the new manager has been installed. This method is thread-safe. - * - * @param manager The new manager to install. - */ - protected final void replaceManager(final T manager) { - this.writeLock.lock(); - try { - final T old = this.getManager(); - if (!manager.isRunning()) { - manager.startup(); - } - this.manager = manager; - old.close(); - } finally { - this.writeLock.unlock(); - } - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java index 7624c5e82c8..870f614d1c3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java @@ -31,12 +31,68 @@ * Manager that allows database appenders to have their configuration reloaded without losing events. */ public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable { + + /** + * Implementations should extend this class for passing data between the getManager method and the manager factory + * class. + */ + protected abstract static class AbstractFactoryData { + private final int bufferSize; + private final Layout layout; + + /** + * Constructs the base factory data. + * + * @param bufferSize The size of the buffer. + * @param layout The appender-level layout + */ + protected AbstractFactoryData(final int bufferSize, final Layout layout) { + this.bufferSize = bufferSize; + this.layout = layout; + } + + /** + * Gets the buffer size. + * + * @return the buffer size. + */ + public int getBufferSize() { + return bufferSize; + } + + /** + * Gets the layout. + * + * @return the layout. + */ + public Layout getLayout() { + return layout; + } + } + + /** + * Implementations should define their own getManager method and call this method from that to create or get + * existing managers. + * + * @param name The manager name, which should include any configuration details that one might want to be able to + * reconfigure at runtime, such as database name, username, (hashed) password, etc. + * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager. + * @param factory A factory instance for creating the appropriate manager. + * @param The concrete manager type. + * @param The concrete {@link AbstractFactoryData} type. + * @return a new or existing manager of the specified type and name. + */ + protected static M getManager( + final String name, final T data, final ManagerFactory factory + ) { + return AbstractManager.getManager(name, factory, data); + } + private final ArrayList buffer; + private final int bufferSize; private final Layout layout; - - private boolean running = false; - + private boolean running; /** * Instantiates the base manager. * @@ -63,37 +119,67 @@ protected AbstractDatabaseManager(final String name, final int bufferSize, final this.layout = layout; } + protected void buffer(final LogEvent event) { + this.buffer.add(event.toImmutable()); + if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { + this.flush(); + } + } + /** - * Implementations should implement this method to perform any proprietary startup operations. This method will - * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method - * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same - * connection for hours. + * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the + * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call + * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of + * {@link #writeInternal}. + * @return true if all resources were closed normally, false otherwise. */ - protected abstract void startupInternal() throws Exception; + protected abstract boolean commitAndClose(); /** - * This method is called within the appender when the appender is started. If it has not already been called, it - * calls {@link #startupInternal()} and catches any exceptions it might throw. + * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when + * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is + * called immediately before every invocation of {@link #writeInternal}. */ - public final synchronized void startup() { - if (!this.isRunning()) { + protected abstract void connectAndStart(); + + /** + * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to + * {@link #shutdown()}. It can also be called manually to flush events to the database. + */ + @Override + public final synchronized void flush() { + if (this.isRunning() && isBuffered()) { + this.connectAndStart(); try { - this.startupInternal(); - this.running = true; - } catch (final Exception e) { - logError("Could not perform database startup operations", e); + for (final LogEvent event : this.buffer) { + this.writeInternal(event, layout != null ? layout.toSerializable(event) : null); + } + } finally { + this.commitAndClose(); + // not sure if this should be done when writing the events failed + this.buffer.clear(); } } } + protected boolean isBuffered() { + return this.bufferSize > 0; + } + /** - * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This - * method will never be called twice on the same instance, and it will only be called after - * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not - * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}. - * @return true if all resources were closed normally, false otherwise. + * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()} + * has not been called). + * + * @return {@code true} if the manager is connected. */ - protected abstract boolean shutdownInternal() throws Exception; + public final boolean isRunning() { + return this.running; + } + + @Override + public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) { + return this.shutdown(); + } /** * This method is called from the {@link #close()} method when the appender is stopped or the appender's manager @@ -118,78 +204,40 @@ public final synchronized boolean shutdown() { } /** - * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()} - * has not been called). - * - * @return {@code true} if the manager is connected. - */ - public final boolean isRunning() { - return this.running; - } - - /** - * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when - * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is - * called immediately before every invocation of {@link #writeInternal}. - */ - protected abstract void connectAndStart(); - - /** - * Performs the actual writing of the event in an implementation-specific way. This method is called immediately - * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. - * - * @param event The event to write to the database. - * @deprecated Use {@link #writeInternal(LogEvent, Serializable)}. - */ - @Deprecated - protected abstract void writeInternal(LogEvent event); - - /** - * Performs the actual writing of the event in an implementation-specific way. This method is called immediately - * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. - * - * @param event The event to write to the database. - */ - protected abstract void writeInternal(LogEvent event, Serializable serializable); - - /** - * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the - * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call - * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of - * {@link #writeInternal}. + * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This + * method will never be called twice on the same instance, and it will only be called after + * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not + * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}. * @return true if all resources were closed normally, false otherwise. */ - protected abstract boolean commitAndClose(); + protected abstract boolean shutdownInternal() throws Exception; /** - * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to - * {@link #shutdown()}. It can also be called manually to flush events to the database. + * This method is called within the appender when the appender is started. If it has not already been called, it + * calls {@link #startupInternal()} and catches any exceptions it might throw. */ - @Override - public final synchronized void flush() { - if (this.isRunning() && this.buffer.size() > 0) { - this.connectAndStart(); + public final synchronized void startup() { + if (!this.isRunning()) { try { - for (final LogEvent event : this.buffer) { - this.writeInternal(event, layout != null ? layout.toSerializable(event) : null); - } - } finally { - this.commitAndClose(); - // not sure if this should be done when writing the events failed - this.buffer.clear(); + this.startupInternal(); + this.running = true; + } catch (final Exception e) { + logError("Could not perform database startup operations", e); } } } /** - * This method manages buffering and writing of events. - * - * @param event The event to write to the database. - * @deprecated since 2.10.1 Use {@link #write(LogEvent, Serializable)}. + * Implementations should implement this method to perform any proprietary startup operations. This method will + * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method + * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same + * connection for hours. */ - @Deprecated - public final synchronized void write(final LogEvent event) { - write(event, null); + protected abstract void startupInternal() throws Exception; + + @Override + public final String toString() { + return this.getName(); } /** @@ -199,84 +247,27 @@ public final synchronized void write(final LogEvent event) { * @param serializable Serializable event */ public final synchronized void write(final LogEvent event, final Serializable serializable) { - if (this.bufferSize > 0) { - this.buffer.add(event.toImmutable()); - if (this.buffer.size() >= this.bufferSize || event.isEndOfBatch()) { - this.flush(); - } + if (isBuffered()) { + buffer(event); } else { - this.connectAndStart(); - try { - this.writeInternal(event, serializable); - } finally { - this.commitAndClose(); - } + writeThrough(event, serializable); } } - @Override - public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - return this.shutdown(); - } - - @Override - public final String toString() { - return this.getName(); - } - /** - * Implementations should define their own getManager method and call this method from that to create or get - * existing managers. + * Performs the actual writing of the event in an implementation-specific way. This method is called immediately + * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit. * - * @param name The manager name, which should include any configuration details that one might want to be able to - * reconfigure at runtime, such as database name, username, (hashed) password, etc. - * @param data The concrete instance of {@link AbstractFactoryData} appropriate for the given manager. - * @param factory A factory instance for creating the appropriate manager. - * @param The concrete manager type. - * @param The concrete {@link AbstractFactoryData} type. - * @return a new or existing manager of the specified type and name. - */ - protected static M getManager( - final String name, final T data, final ManagerFactory factory - ) { - return AbstractManager.getManager(name, factory, data); - } - - /** - * Implementations should extend this class for passing data between the getManager method and the manager factory - * class. + * @param event The event to write to the database. */ - protected abstract static class AbstractFactoryData { - private final int bufferSize; - private final Layout layout; - - /** - * Constructs the base factory data. - * - * @param bufferSize The size of the buffer. - * @param bufferSize The appender-level layout - */ - protected AbstractFactoryData(final int bufferSize, final Layout layout) { - this.bufferSize = bufferSize; - this.layout = layout; - } - - /** - * Gets the buffer size. - * - * @return the buffer size. - */ - public int getBufferSize() { - return bufferSize; - } + protected abstract void writeInternal(LogEvent event, Serializable serializable); - /** - * Gets the layout. - * - * @return the layout. - */ - public Layout getLayout() { - return layout; + protected void writeThrough(final LogEvent event, final Serializable serializable) { + this.connectAndStart(); + try { + this.writeInternal(event, serializable); + } finally { + this.commitAndClose(); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java index 470c6a7e26e..d75a2bce678 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java @@ -16,36 +16,42 @@ */ package org.apache.logging.log4j.core.appender.db; -import java.util.Date; - import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.convert.TypeConverter; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.spi.ThreadContextMap; import org.apache.logging.log4j.spi.ThreadContextStack; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.ReadOnlyStringMap; +import java.util.Date; +import java.util.Locale; +import java.util.function.Supplier; + /** * A configuration element for specifying a database column name mapping. * * @since 2.8 */ -@Plugin(name = "ColumnMapping", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public class ColumnMapping { /** * Builder for {@link ColumnMapping}. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginConfiguration private Configuration configuration; @@ -73,12 +79,15 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde @Required(message = "No conversion type provided") private Class type = String.class; + private Injector injector; + @Override public ColumnMapping build() { if (pattern != null) { layout = PatternLayout.newBuilder() - .withPattern(pattern) - .withConfiguration(configuration) + .setPattern(pattern) + .setConfiguration(configuration) + .setAlwaysWriteExceptions(false) .build(); } if (!(layout == null @@ -94,7 +103,7 @@ public ColumnMapping build() { LOGGER.error("Only one of 'literal' or 'parameter' can be set on the column mapping {}", this); return null; } - return new ColumnMapping(name, source, layout, literal, parameter, type); + return new ColumnMapping(name, source, layout, literal, parameter, type, () -> injector.getTypeConverter(type)); } public Builder setConfiguration(final Configuration configuration) { @@ -105,8 +114,8 @@ public Builder setConfiguration(final Configuration configuration) { /** * Layout of value to write to database (before type conversion). Not applicable if {@link #setType(Class)} is * a {@link ReadOnlyStringMap}, {@link ThreadContextMap}, or {@link ThreadContextStack}. - * - * @return this. + * + * @return this. */ public Builder setLayout(final StringLayout layout) { this.layout = layout; @@ -116,8 +125,8 @@ public Builder setLayout(final StringLayout layout) { /** * Literal value to use for populating a column. This is generally useful for functions, stored procedures, * etc. No escaping will be done on this value. - * - * @return this. + * + * @return this. */ public Builder setLiteral(final String literal) { this.literal = literal; @@ -126,8 +135,8 @@ public Builder setLiteral(final String literal) { /** * Column name. - * - * @return this. + * + * @return this. */ public Builder setName(final String name) { this.name = name; @@ -137,8 +146,8 @@ public Builder setName(final String name) { /** * Parameter value to use for populating a column, MUST contain a single parameter marker '?'. This is generally useful for functions, stored procedures, * etc. No escaping will be done on this value. - * - * @return this. + * + * @return this. */ public Builder setParameter(final String parameter) { this.parameter= parameter; @@ -148,8 +157,8 @@ public Builder setParameter(final String parameter) { /** * Pattern to use as a {@link PatternLayout}. Convenient shorthand for {@link #setLayout(StringLayout)} with a * PatternLayout. - * - * @return this. + * + * @return this. */ public Builder setPattern(final String pattern) { this.pattern = pattern; @@ -159,7 +168,7 @@ public Builder setPattern(final String pattern) { /** * Source name. Useful when combined with a {@link org.apache.logging.log4j.message.MapMessage} depending on the * appender. - * + * * @return this. */ public Builder setSource(final String source) { @@ -171,14 +180,20 @@ public Builder setSource(final String source) { * Class to convert value to before storing in database. If the type is compatible with {@link ThreadContextMap} or * {@link ReadOnlyStringMap}, then the MDC will be used. If the type is compatible with {@link ThreadContextStack}, * then the NDC will be used. If the type is compatible with {@link Date}, then the event timestamp will be used. - * - * @return this. + * + * @return this. */ public Builder setType(final Class type) { this.type = type; return this; } + @Inject + public Builder setInjector(final Injector injector) { + this.injector = injector; + return this; + } + @Override public String toString() { return "Builder [name=" + name + ", source=" + source + ", literal=" + literal + ", parameter=" + parameter @@ -187,25 +202,36 @@ public String toString() { } private static final Logger LOGGER = StatusLogger.getLogger(); - @PluginBuilderFactory + + @PluginFactory public static Builder newBuilder() { return new Builder(); } - + + public static String toKey(final String name) { + return name.toUpperCase(Locale.ROOT); + } + private final StringLayout layout; private final String literalValue; private final String name; + private final String nameKey; private final String parameter; private final String source; private final Class type; + private final Supplier> typeConverter; - private ColumnMapping(final String name, final String source, final StringLayout layout, final String literalValue, final String parameter, final Class type) { + private ColumnMapping( + final String name, final String source, final StringLayout layout, final String literalValue, + final String parameter, final Class type, final Supplier> typeConverter) { this.name = name; + this.nameKey = toKey(name); this.source = source; this.layout = layout; this.literalValue = literalValue; this.parameter = parameter; this.type = type; + this.typeConverter = typeConverter; } public StringLayout getLayout() { @@ -220,6 +246,10 @@ public String getName() { return name; } + public String getNameKey() { + return nameKey; + } + public String getParameter() { return parameter; } @@ -232,6 +262,10 @@ public Class getType() { return type; } + public TypeConverter getTypeConverter() { + return typeConverter.get(); + } + @Override public String toString() { return "ColumnMapping [name=" + name + ", source=" + source + ", literalValue=" + literalValue + ", parameter=" diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/DbAppenderLoggingException.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/DbAppenderLoggingException.java new file mode 100644 index 00000000000..993f7770a5e --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/DbAppenderLoggingException.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.db; + +import org.apache.logging.log4j.core.appender.AppenderLoggingException; + +/** + * Wraps a database exception like a JDBC SQLException. Use this class to distinguish exceptions specifically caught + * from database layers like JDBC. + */ +public class DbAppenderLoggingException extends AppenderLoggingException { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an exception with a message. + * + * @param format The reason format for the exception, see {@link String#format(String, Object...)}. + * @param args The reason arguments for the exception, see {@link String#format(String, Object...)}. + * @since 2.12.1 + */ + public DbAppenderLoggingException(final String format, final Object... args) { + super(format, args); + } + + /** + * Constructs an exception with a message and underlying cause. + * + * @param message The reason for the exception + * @param cause The underlying cause of the exception + */ + public DbAppenderLoggingException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs an exception with a message. + * + * @param cause The underlying cause of the exception + * @param format The reason format for the exception, see {@link String#format(String, Object...)}. + * @param args The reason arguments for the exception, see {@link String#format(String, Object...)}. + * @since 2.12.1 + */ + public DbAppenderLoggingException(final Throwable cause, final String format, final Object... args) { + super(cause, format, args); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSource.java deleted file mode 100644 index a791df98978..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/DataSourceConnectionSource.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.appender.db.jdbc; - -import java.sql.Connection; -import java.sql.SQLException; - -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.sql.DataSource; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Strings; - -/** - * A {@link JdbcAppender} connection source that uses a {@link DataSource} to connect to the database. - */ -@Plugin(name = "DataSource", category = Core.CATEGORY_NAME, elementType = "connectionSource", printObject = true) -public final class DataSourceConnectionSource extends AbstractConnectionSource { - private static final Logger LOGGER = StatusLogger.getLogger(); - - private final DataSource dataSource; - private final String description; - - private DataSourceConnectionSource(final String dataSourceName, final DataSource dataSource) { - this.dataSource = dataSource; - this.description = "dataSource{ name=" + dataSourceName + ", value=" + dataSource + " }"; - } - - @Override - public Connection getConnection() throws SQLException { - return this.dataSource.getConnection(); - } - - @Override - public String toString() { - return this.description; - } - - /** - * Factory method for creating a connection source within the plugin manager. - * - * @param jndiName The full JNDI path where the data source is bound. Should start with java:/comp/env or - * environment-equivalent. - * @return the created connection source. - */ - @PluginFactory - public static DataSourceConnectionSource createConnectionSource(@PluginAttribute("jndiName") final String jndiName) { - if (Strings.isEmpty(jndiName)) { - LOGGER.error("No JNDI name provided."); - return null; - } - - try { - final InitialContext context = new InitialContext(); - final DataSource dataSource = (DataSource) context.lookup(jndiName); - if (dataSource == null) { - LOGGER.error("No data source found with JNDI name [" + jndiName + "]."); - return null; - } - - return new DataSourceConnectionSource(jndiName, dataSource); - } catch (final NamingException e) { - LOGGER.error(e.getMessage(), e); - return null; - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java deleted file mode 100644 index 974d87ed16c..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.appender.db.jdbc; - -import java.io.Serializable; -import java.sql.PreparedStatement; -import java.util.Arrays; -import java.util.Objects; - -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.appender.AbstractAppender; -import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; -import org.apache.logging.log4j.core.appender.db.ColumnMapping; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.util.Assert; -import org.apache.logging.log4j.core.util.Booleans; - -/** - * This Appender writes logging events to a relational database using standard JDBC mechanisms. It takes a list of - * {@link ColumnConfig}s and/or {@link ColumnMapping}s with which it determines how to save the event data into the - * appropriate columns in the table. ColumnMapping is new as of Log4j 2.8 and supports - * {@linkplain TypeConverter type conversion} and persistence using {@link PreparedStatement#setObject(int, Object)}. - * A {@link ConnectionSource} plugin instance instructs the appender (and {@link JdbcDatabaseManager}) how to connect to - * the database. This appender can be reconfigured at run time. - * - * @see ColumnConfig - * @see ColumnMapping - * @see ConnectionSource - */ -@Plugin(name = "JDBC", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) -public final class JdbcAppender extends AbstractDatabaseAppender { - - private final String description; - - private JdbcAppender(final String name, final Filter filter, final Layout layout, - final boolean ignoreExceptions, final JdbcDatabaseManager manager) { - super(name, filter, layout, ignoreExceptions, manager); - this.description = this.getName() + "{ manager=" + this.getManager() + " }"; - } - - @Override - public String toString() { - return this.description; - } - - /** - * Factory method for creating a JDBC appender within the plugin manager. - * - * @see Builder - * @deprecated use {@link #newBuilder()} - */ - @Deprecated - public static > JdbcAppender createAppender(final String name, final String ignore, - final Filter filter, - final ConnectionSource connectionSource, - final String bufferSize, final String tableName, - final ColumnConfig[] columnConfigs) { - Assert.requireNonEmpty(name, "Name cannot be empty"); - Objects.requireNonNull(connectionSource, "ConnectionSource cannot be null"); - Assert.requireNonEmpty(tableName, "Table name cannot be empty"); - Assert.requireNonEmpty(columnConfigs, "ColumnConfigs cannot be empty"); - - final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - - return JdbcAppender.newBuilder() - .setBufferSize(bufferSizeInt) - .setColumnConfigs(columnConfigs) - .setConnectionSource(connectionSource) - .setTableName(tableName) - .withName(name) - .withIgnoreExceptions(ignoreExceptions) - .withFilter(filter) - .build(); - } - - @PluginBuilderFactory - public static > B newBuilder() { - return new Builder().asBuilder(); - } - - public static class Builder> extends AbstractAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { - - @PluginElement("ConnectionSource") - @Required(message = "No ConnectionSource provided") - private ConnectionSource connectionSource; - - @PluginBuilderAttribute - private int bufferSize; - - @PluginBuilderAttribute - @Required(message = "No table name provided") - private String tableName; - - @PluginElement("ColumnConfigs") - private ColumnConfig[] columnConfigs; - - @PluginElement("ColumnMappings") - private ColumnMapping[] columnMappings; - - /** - * The connections source from which database connections should be retrieved. - * - * @return this - */ - public B setConnectionSource(final ConnectionSource connectionSource) { - this.connectionSource = connectionSource; - return asBuilder(); - } - - /** - * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the buffer - * reaches this size. - * - * @return this - */ - public B setBufferSize(final int bufferSize) { - this.bufferSize = bufferSize; - return asBuilder(); - } - - /** - * The name of the database table to insert log events into. - * - * @return this - */ - public B setTableName(final String tableName) { - this.tableName = tableName; - return asBuilder(); - } - - /** - * Information about the columns that log event data should be inserted into and how to insert that data. - * - * @return this - */ - public B setColumnConfigs(final ColumnConfig... columnConfigs) { - this.columnConfigs = columnConfigs; - return asBuilder(); - } - - public B setColumnMappings(final ColumnMapping... columnMappings) { - this.columnMappings = columnMappings; - return asBuilder(); - } - - @Override - public JdbcAppender build() { - if (Assert.isEmpty(columnConfigs) && Assert.isEmpty(columnMappings)) { - LOGGER.error("Cannot create JdbcAppender without any columns."); - return null; - } - final String managerName = "JdbcManager{name=" + getName() + ", bufferSize=" + bufferSize + ", tableName=" - + tableName + ", columnConfigs=" + Arrays.toString(columnConfigs) + ", columnMappings=" - + Arrays.toString(columnMappings) + '}'; - final JdbcDatabaseManager manager = JdbcDatabaseManager.getManager(managerName, bufferSize, getLayout(), - connectionSource, tableName, columnConfigs, columnMappings); - if (manager == null) { - return null; - } - return new JdbcAppender(getName(), getFilter(), getLayout(), isIgnoreExceptions(), manager); - } - - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java deleted file mode 100644 index 2952288d347..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/JdbcDatabaseManager.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.appender.db.jdbc; - -import java.io.Serializable; -import java.io.StringReader; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.NClob; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Objects; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.StringLayout; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.core.appender.ManagerFactory; -import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager; -import org.apache.logging.log4j.core.appender.db.ColumnMapping; -import org.apache.logging.log4j.core.config.plugins.convert.DateTypeConverter; -import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; -import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.message.MapMessage; -import org.apache.logging.log4j.spi.ThreadContextMap; -import org.apache.logging.log4j.spi.ThreadContextStack; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.Strings; - -/** - * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JDBC. - */ -public final class JdbcDatabaseManager extends AbstractDatabaseManager { - - private static StatusLogger logger() { - return StatusLogger.getLogger(); - } - - private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory(); - - // NOTE: prepared statements are prepared in this order: column mappings, then column configs - private final List columnMappings; - private final List columnConfigs; - private final ConnectionSource connectionSource; - private final String sqlStatement; - - private Connection connection; - private PreparedStatement statement; - private boolean isBatchSupported; - - private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource, - final String sqlStatement, final List columnConfigs, - final List columnMappings) { - super(name, bufferSize); - this.connectionSource = connectionSource; - this.sqlStatement = sqlStatement; - this.columnConfigs = columnConfigs; - this.columnMappings = columnMappings; - } - - @Override - protected void startupInternal() throws Exception { - this.connection = this.connectionSource.getConnection(); - final DatabaseMetaData metaData = this.connection.getMetaData(); - this.isBatchSupported = metaData.supportsBatchUpdates(); - logger().debug("Closing Connection {}", this.connection); - Closer.closeSilently(this.connection); - } - - @Override - protected boolean shutdownInternal() { - if (this.connection != null || this.statement != null) { - return this.commitAndClose(); - } - if (connectionSource != null) { - connectionSource.stop(); - } - return true; - } - - @Override - protected void connectAndStart() { - try { - this.connection = this.connectionSource.getConnection(); - this.connection.setAutoCommit(false); - logger().debug("Preparing SQL: {}", this.sqlStatement); - this.statement = this.connection.prepareStatement(this.sqlStatement); - } catch (final SQLException e) { - throw new AppenderLoggingException( - "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e); - } - } - - @Deprecated - @Override - protected void writeInternal(final LogEvent event) { - writeInternal(event, null); - } - - private void setFields(final MapMessage mapMessage) throws SQLException { - final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap(); - final String simpleName = statement.getClass().getName(); - int i = 1; // JDBC indices start at 1 - for (final ColumnMapping mapping : this.columnMappings) { - final String source = mapping.getSource(); - final String key = Strings.isEmpty(source) ? mapping.getName() : source; - final Object value = map.getValue(key); - if (logger().isTraceEnabled()) { - final String valueStr = value instanceof String ? "\"" + value + "\"" : Objects.toString(value, null); - logger().trace("{} setObject({}, {}) for key '{}' and mapping '{}'", simpleName, i, valueStr, key, - mapping.getName()); - } - statement.setObject(i++, value); - } - } - - @Override - protected void writeInternal(final LogEvent event, final Serializable serializable) { - StringReader reader = null; - try { - if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null - || this.statement.isClosed()) { - throw new AppenderLoggingException( - "Cannot write logging event; JDBC manager not connected to the database."); - } - - if (serializable instanceof MapMessage) { - setFields((MapMessage) serializable); - } - int i = 1; // JDBC indices start at 1 - for (final ColumnMapping mapping : this.columnMappings) { - if (ThreadContextMap.class.isAssignableFrom(mapping.getType()) - || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) { - this.statement.setObject(i++, event.getContextData().toMap()); - } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) { - this.statement.setObject(i++, event.getContextStack().asList()); - } else if (Date.class.isAssignableFrom(mapping.getType())) { - this.statement.setObject(i++, DateTypeConverter.fromMillis(event.getTimeMillis(), - mapping.getType().asSubclass(Date.class))); - } else { - StringLayout layout = mapping.getLayout(); - if (layout != null) { - if (Clob.class.isAssignableFrom(mapping.getType())) { - this.statement.setClob(i++, new StringReader(layout.toSerializable(event))); - } else if (NClob.class.isAssignableFrom(mapping.getType())) { - this.statement.setNClob(i++, new StringReader(layout.toSerializable(event))); - } else { - final Object value = TypeConverters.convert(layout.toSerializable(event), mapping.getType(), - null); - if (value == null) { - this.statement.setNull(i++, Types.NULL); - } else { - this.statement.setObject(i++, value); - } - } - } - } - } - for (final ColumnConfig column : this.columnConfigs) { - if (column.isEventTimestamp()) { - this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis())); - } else if (column.isClob()) { - reader = new StringReader(column.getLayout().toSerializable(event)); - if (column.isUnicode()) { - this.statement.setNClob(i++, reader); - } else { - this.statement.setClob(i++, reader); - } - } else if (column.isUnicode()) { - this.statement.setNString(i++, column.getLayout().toSerializable(event)); - } else { - this.statement.setString(i++, column.getLayout().toSerializable(event)); - } - } - - if (this.isBatchSupported) { - this.statement.addBatch(); - } else if (this.statement.executeUpdate() == 0) { - throw new AppenderLoggingException( - "No records inserted in database table for log event in JDBC manager."); - } - } catch (final SQLException e) { - throw new AppenderLoggingException("Failed to insert record for log event in JDBC manager: " + - e.getMessage(), e); - } finally { - Closer.closeSilently(reader); - } - } - - @Override - protected boolean commitAndClose() { - boolean closed = true; - try { - if (this.connection != null && !this.connection.isClosed()) { - if (this.isBatchSupported) { - logger().debug("Executing batch PreparedStatement {}", this.statement); - this.statement.executeBatch(); - } - logger().debug("Committing Connection {}", this.connection); - this.connection.commit(); - } - } catch (final SQLException e) { - throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e); - } finally { - try { - logger().debug("Closing PreparedStatement {}", this.statement); - Closer.close(this.statement); - } catch (final Exception e) { - logWarn("Failed to close SQL statement logging event or flushing buffer", e); - closed = false; - } finally { - this.statement = null; - } - - try { - logger().debug("Closing Connection {}", this.connection); - Closer.close(this.connection); - } catch (final Exception e) { - logWarn("Failed to close database connection logging event or flushing buffer", e); - closed = false; - } finally { - this.connection = null; - } - } - return closed; - } - - /** - * Creates a JDBC manager for use within the {@link JdbcAppender}, or returns a suitable one if it already exists. - * - * @param name The name of the manager, which should include connection details and hashed passwords where possible. - * @param bufferSize The size of the log event buffer. - * @param connectionSource The source for connections to the database. - * @param tableName The name of the database table to insert log events into. - * @param columnConfigs Configuration information about the log table columns. - * @return a new or existing JDBC manager as applicable. - * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])} - */ - @Deprecated - public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize, - final ConnectionSource connectionSource, - final String tableName, - final ColumnConfig[] columnConfigs) { - - return getManager(name, - new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, new ColumnMapping[0]), - getFactory()); - } - - /** - * Creates a JDBC manager for use within the {@link JdbcAppender}, or returns a suitable one if it already exists. - * - * @param name The name of the manager, which should include connection details and hashed passwords where possible. - * @param bufferSize The size of the log event buffer. - * @param connectionSource The source for connections to the database. - * @param tableName The name of the database table to insert log events into. - * @param columnConfigs Configuration information about the log table columns. - * @param columnMappings column mapping configuration (including type conversion). - * @return a new or existing JDBC manager as applicable. - * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])} - */ - @Deprecated - public static JdbcDatabaseManager getManager(final String name, - final int bufferSize, - final ConnectionSource connectionSource, - final String tableName, - final ColumnConfig[] columnConfigs, - final ColumnMapping[] columnMappings) { - return getManager(name, new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, columnMappings), - getFactory()); - } - - /** - * Creates a JDBC manager for use within the {@link JdbcAppender}, or returns a suitable one if it already exists. - * - * @param name The name of the manager, which should include connection details and hashed passwords where possible. - * @param bufferSize The size of the log event buffer. - * @param layout The Appender-level layout - * @param connectionSource The source for connections to the database. - * @param tableName The name of the database table to insert log events into. - * @param columnConfigs Configuration information about the log table columns. - * @param columnMappings column mapping configuration (including type conversion). - * @return a new or existing JDBC manager as applicable. - */ - public static JdbcDatabaseManager getManager(final String name, - final int bufferSize, - final Layout layout, - final ConnectionSource connectionSource, - final String tableName, - final ColumnConfig[] columnConfigs, - final ColumnMapping[] columnMappings) { - return getManager(name, new FactoryData(bufferSize, layout, connectionSource, tableName, columnConfigs, columnMappings), - getFactory()); - } - - private static JdbcDatabaseManagerFactory getFactory() { - return INSTANCE; - } - - /** - * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers. - */ - private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData { - private final ConnectionSource connectionSource; - private final String tableName; - private final ColumnConfig[] columnConfigs; - private final ColumnMapping[] columnMappings; - - protected FactoryData(final int bufferSize, final Layout layout, - final ConnectionSource connectionSource, final String tableName, final ColumnConfig[] columnConfigs, - final ColumnMapping[] columnMappings) { - super(bufferSize, layout); - this.connectionSource = connectionSource; - this.tableName = tableName; - this.columnConfigs = columnConfigs; - this.columnMappings = columnMappings; - } - } - - /** - * Creates managers. - */ - private static final class JdbcDatabaseManagerFactory implements ManagerFactory { - - private static final char PARAMETER_MARKER = '?'; - - @Override - public JdbcDatabaseManager createManager(final String name, final FactoryData data) { - final StringBuilder sb = new StringBuilder("INSERT INTO ").append(data.tableName).append(" ("); - // so this gets a little more complicated now that there are two ways to configure column mappings, but - // both mappings follow the same exact pattern for the prepared statement - int i = 1; - for (final ColumnMapping mapping : data.columnMappings) { - final String mappingName = mapping.getName(); - logger().trace("Adding INSERT ColumnMapping[{}]: {}={} ", i++, mappingName, mapping); - sb.append(mappingName).append(','); - } - for (final ColumnConfig config : data.columnConfigs) { - sb.append(config.getColumnName()).append(','); - } - // at least one of those arrays is guaranteed to be non-empty - sb.setCharAt(sb.length() - 1, ')'); - sb.append(" VALUES ("); - i = 1; - final List columnMappings = new ArrayList<>(data.columnMappings.length); - for (final ColumnMapping mapping : data.columnMappings) { - final String mappingName = mapping.getName(); - if (Strings.isNotEmpty(mapping.getLiteralValue())) { - logger().trace("Adding INSERT VALUES literal for ColumnMapping[{}]: {}={} ", i, mappingName, mapping.getLiteralValue()); - sb.append(mapping.getLiteralValue()); - } - if (Strings.isNotEmpty(mapping.getParameter())) { - logger().trace("Adding INSERT VALUES parameter for ColumnMapping[{}]: {}={} ", i, mappingName, mapping.getParameter()); - sb.append(mapping.getParameter()); - columnMappings.add(mapping); - } else { - logger().trace("Adding INSERT VALUES parameter marker for ColumnMapping[{}]: {}={} ", i, mappingName, PARAMETER_MARKER); - sb.append(PARAMETER_MARKER); - columnMappings.add(mapping); - } - sb.append(','); - i++; - } - final List columnConfigs = new ArrayList<>(data.columnConfigs.length); - for (final ColumnConfig config : data.columnConfigs) { - if (Strings.isNotEmpty(config.getLiteralValue())) { - sb.append(config.getLiteralValue()); - } else { - sb.append(PARAMETER_MARKER); - columnConfigs.add(config); - } - sb.append(','); - } - // at least one of those arrays is guaranteed to be non-empty - sb.setCharAt(sb.length() - 1, ')'); - final String sqlStatement = sb.toString(); - - return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement, - columnConfigs, columnMappings); - } - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/package-info.java deleted file mode 100644 index 1dbd663c4e3..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/jdbc/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -/** - * The JDBC Appender supports writing log events to a relational database using standard JDBC connections. You will need - * a JDBC driver on your classpath for the database you wish to log to. - */ -package org.apache.logging.log4j.core.appender.db.jdbc; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java deleted file mode 100644 index 17040186d82..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/JmsAppender.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.mom; - -import java.io.Serializable; -import java.util.Properties; -import java.util.concurrent.TimeUnit; - -import javax.jms.JMSException; - -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AbstractAppender; -import org.apache.logging.log4j.core.appender.AbstractManager; -import org.apache.logging.log4j.core.appender.mom.JmsManager.JmsManagerConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.net.JndiManager; - -/** - * Generic JMS Appender plugin for both queues and topics. This Appender replaces the previous split ones. However, - * configurations set up for the 2.0 version of the JMS appenders will still work. - */ -@Plugin(name = "JMS", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) -@PluginAliases({ "JMSQueue", "JMSTopic" }) -public class JmsAppender extends AbstractAppender { - - public static class Builder implements org.apache.logging.log4j.core.util.Builder { - - public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000; - - @PluginBuilderAttribute - @Required(message = "A name for the JmsAppender must be specified") - private String name; - - @PluginBuilderAttribute - private String factoryName; - - @PluginBuilderAttribute - private String providerUrl; - - @PluginBuilderAttribute - private String urlPkgPrefixes; - - @PluginBuilderAttribute - private String securityPrincipalName; - - @PluginBuilderAttribute(sensitive = true) - private String securityCredentials; - - @PluginBuilderAttribute - @Required(message = "A javax.jms.ConnectionFactory JNDI name must be specified") - private String factoryBindingName; - - @PluginBuilderAttribute - @PluginAliases({ "queueBindingName", "topicBindingName" }) - @Required(message = "A javax.jms.Destination JNDI name must be specified") - private String destinationBindingName; - - @PluginBuilderAttribute - private String userName; - - @PluginBuilderAttribute(sensitive = true) - private char[] password; - - @PluginElement("Layout") - private Layout layout; - - @PluginElement("Filter") - private Filter filter; - - private long reconnectIntervalMillis = DEFAULT_RECONNECT_INTERVAL_MILLIS; - - @PluginBuilderAttribute - private boolean ignoreExceptions = true; - - @PluginBuilderAttribute - private boolean immediateFail; - - // Programmatic access only for now. - private JmsManager jmsManager; - - private Builder() { - } - - @SuppressWarnings("resource") // actualJmsManager and jndiManager are managed by the JmsAppender - @Override - public JmsAppender build() { - JmsManager actualJmsManager = jmsManager; - JmsManagerConfiguration configuration = null; - if (actualJmsManager == null) { - final Properties jndiProperties = JndiManager.createProperties(factoryName, providerUrl, urlPkgPrefixes, - securityPrincipalName, securityCredentials, null); - configuration = new JmsManagerConfiguration(jndiProperties, factoryBindingName, destinationBindingName, - userName, password, false, reconnectIntervalMillis); - actualJmsManager = AbstractManager.getManager(name, JmsManager.FACTORY, configuration); - } - if (actualJmsManager == null) { - // JmsManagerFactory has already logged an ERROR. - return null; - } - if (layout == null) { - LOGGER.error("No layout provided for JmsAppender"); - return null; - } - try { - return new JmsAppender(name, filter, layout, ignoreExceptions, actualJmsManager); - } catch (final JMSException e) { - // Never happens since the ctor no longer actually throws a JMSException. - throw new IllegalStateException(e); - } - } - - public Builder setDestinationBindingName(final String destinationBindingName) { - this.destinationBindingName = destinationBindingName; - return this; - } - - public Builder setFactoryBindingName(final String factoryBindingName) { - this.factoryBindingName = factoryBindingName; - return this; - } - - public Builder setFactoryName(final String factoryName) { - this.factoryName = factoryName; - return this; - } - - public Builder setFilter(final Filter filter) { - this.filter = filter; - return this; - } - - public Builder setIgnoreExceptions(final boolean ignoreExceptions) { - this.ignoreExceptions = ignoreExceptions; - return this; - } - - public Builder setImmediateFail(final boolean immediateFail) { - this.immediateFail = immediateFail; - return this; - } - - public Builder setJmsManager(final JmsManager jmsManager) { - this.jmsManager = jmsManager; - return this; - } - - public Builder setLayout(final Layout layout) { - this.layout = layout; - return this; - } - - public Builder setName(final String name) { - this.name = name; - return this; - } - - public Builder setPassword(final char[] password) { - this.password = password; - return this; - } - - /** - * @deprecated Use setPassword(char[]) - */ - @Deprecated - public Builder setPassword(final String password) { - this.password = password == null ? null : password.toCharArray(); - return this; - } - - public Builder setProviderUrl(final String providerUrl) { - this.providerUrl = providerUrl; - return this; - } - - public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) { - this.reconnectIntervalMillis = reconnectIntervalMillis; - return this; - } - - public Builder setSecurityCredentials(final String securityCredentials) { - this.securityCredentials = securityCredentials; - return this; - } - - public Builder setSecurityPrincipalName(final String securityPrincipalName) { - this.securityPrincipalName = securityPrincipalName; - return this; - } - - public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) { - this.urlPkgPrefixes = urlPkgPrefixes; - return this; - } - - /** - * @deprecated Use {@link #setUserName(String)}. - */ - @Deprecated - public Builder setUsername(final String username) { - this.userName = username; - return this; - } - - public Builder setUserName(final String userName) { - this.userName = userName; - return this; - } - - /** - * Does not include the password. - */ - @Override - public String toString() { - return "Builder [name=" + name + ", factoryName=" + factoryName + ", providerUrl=" + providerUrl - + ", urlPkgPrefixes=" + urlPkgPrefixes + ", securityPrincipalName=" + securityPrincipalName - + ", securityCredentials=" + securityCredentials + ", factoryBindingName=" + factoryBindingName - + ", destinationBindingName=" + destinationBindingName + ", username=" + userName + ", layout=" - + layout + ", filter=" + filter + ", ignoreExceptions=" + ignoreExceptions + ", jmsManager=" - + jmsManager + "]"; - } - - } - - @PluginBuilderFactory - public static Builder newBuilder() { - return new Builder(); - } - - private volatile JmsManager manager; - - /** - * - * @throws JMSException - * not thrown as of 2.9 but retained in the signature for compatibility, will be removed in 3.0 - */ - protected JmsAppender(final String name, final Filter filter, final Layout layout, - final boolean ignoreExceptions, final JmsManager manager) throws JMSException { - super(name, filter, layout, ignoreExceptions); - this.manager = manager; - } - - @Override - public void append(final LogEvent event) { - this.manager.send(event, toSerializable(event)); - } - - public JmsManager getManager() { - return manager; - } - - @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { - setStopping(); - boolean stopped = super.stop(timeout, timeUnit, false); - stopped &= this.manager.stop(timeout, timeUnit); - setStopped(); - return stopped; - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqAppender.java deleted file mode 100644 index aa78fad87d8..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/JeroMqAppender.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.mom.jeromq; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AbstractAppender; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.util.Strings; - -/** - * Sends log events to one or more ZeroMQ (JeroMQ) endpoints. - *

    - * Requires the JeroMQ jar (LGPL as of 0.3.5) - *

    - */ -// TODO -// Some methods are synchronized because a ZMQ.Socket is not thread-safe -// Using a ThreadLocal for the publisher hangs tests on shutdown. There must be -// some issue on threads owning certain resources as opposed to others. -@Plugin(name = "JeroMQ", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) -public final class JeroMqAppender extends AbstractAppender { - - private static final int DEFAULT_BACKLOG = 100; - - private static final int DEFAULT_IVL = 100; - - private static final int DEFAULT_RCV_HWM = 1000; - - private static final int DEFAULT_SND_HWM = 1000; - - private final JeroMqManager manager; - private final List endpoints; - private int sendRcFalse; - private int sendRcTrue; - - private JeroMqAppender(final String name, final Filter filter, final Layout layout, - final boolean ignoreExceptions, final List endpoints, final long affinity, final long backlog, - final boolean delayAttachOnConnect, final byte[] identity, final boolean ipv4Only, final long linger, - final long maxMsgSize, final long rcvHwm, final long receiveBufferSize, final int receiveTimeOut, - final long reconnectIVL, final long reconnectIVLMax, final long sendBufferSize, final int sendTimeOut, - final long sndHWM, final int tcpKeepAlive, final long tcpKeepAliveCount, final long tcpKeepAliveIdle, - final long tcpKeepAliveInterval, final boolean xpubVerbose) { - super(name, filter, layout, ignoreExceptions); - this.manager = JeroMqManager.getJeroMqManager(name, affinity, backlog, delayAttachOnConnect, identity, ipv4Only, - linger, maxMsgSize, rcvHwm, receiveBufferSize, receiveTimeOut, reconnectIVL, reconnectIVLMax, - sendBufferSize, sendTimeOut, sndHWM, tcpKeepAlive, tcpKeepAliveCount, tcpKeepAliveIdle, - tcpKeepAliveInterval, xpubVerbose, endpoints); - this.endpoints = endpoints; - } - - // The ZMQ.Socket class has other set methods that we do not cover because - // they throw unsupported operation exceptions. - @PluginFactory - public static JeroMqAppender createAppender( - // @formatter:off - @Required(message = "No name provided for JeroMqAppender") @PluginAttribute("name") final String name, - @PluginElement("Layout") Layout layout, - @PluginElement("Filter") final Filter filter, - @PluginElement("Properties") final Property[] properties, - // Super attributes - @PluginAttribute("ignoreExceptions") final boolean ignoreExceptions, - // ZMQ attributes; defaults picked from zmq.Options. - @PluginAttribute(value = "affinity", defaultLong = 0) final long affinity, - @PluginAttribute(value = "backlog", defaultLong = DEFAULT_BACKLOG) final long backlog, - @PluginAttribute(value = "delayAttachOnConnect") final boolean delayAttachOnConnect, - @PluginAttribute(value = "identity") final byte[] identity, - @PluginAttribute(value = "ipv4Only", defaultBoolean = true) final boolean ipv4Only, - @PluginAttribute(value = "linger", defaultLong = -1) final long linger, - @PluginAttribute(value = "maxMsgSize", defaultLong = -1) final long maxMsgSize, - @PluginAttribute(value = "rcvHwm", defaultLong = DEFAULT_RCV_HWM) final long rcvHwm, - @PluginAttribute(value = "receiveBufferSize", defaultLong = 0) final long receiveBufferSize, - @PluginAttribute(value = "receiveTimeOut", defaultLong = -1) final int receiveTimeOut, - @PluginAttribute(value = "reconnectIVL", defaultLong = DEFAULT_IVL) final long reconnectIVL, - @PluginAttribute(value = "reconnectIVLMax", defaultLong = 0) final long reconnectIVLMax, - @PluginAttribute(value = "sendBufferSize", defaultLong = 0) final long sendBufferSize, - @PluginAttribute(value = "sendTimeOut", defaultLong = -1) final int sendTimeOut, - @PluginAttribute(value = "sndHwm", defaultLong = DEFAULT_SND_HWM) final long sndHwm, - @PluginAttribute(value = "tcpKeepAlive", defaultInt = -1) final int tcpKeepAlive, - @PluginAttribute(value = "tcpKeepAliveCount", defaultLong = -1) final long tcpKeepAliveCount, - @PluginAttribute(value = "tcpKeepAliveIdle", defaultLong = -1) final long tcpKeepAliveIdle, - @PluginAttribute(value = "tcpKeepAliveInterval", defaultLong = -1) final long tcpKeepAliveInterval, - @PluginAttribute(value = "xpubVerbose") final boolean xpubVerbose - // @formatter:on - ) { - if (layout == null) { - layout = PatternLayout.createDefaultLayout(); - } - List endpoints; - if (properties == null) { - endpoints = new ArrayList<>(0); - } else { - endpoints = new ArrayList<>(properties.length); - for (final Property property : properties) { - if ("endpoint".equalsIgnoreCase(property.getName())) { - final String value = property.getValue(); - if (Strings.isNotEmpty(value)) { - endpoints.add(value); - } - } - } - } - LOGGER.debug("Creating JeroMqAppender with name={}, filter={}, layout={}, ignoreExceptions={}, endpoints={}", - name, filter, layout, ignoreExceptions, endpoints); - return new JeroMqAppender(name, filter, layout, ignoreExceptions, endpoints, affinity, backlog, - delayAttachOnConnect, identity, ipv4Only, linger, maxMsgSize, rcvHwm, receiveBufferSize, - receiveTimeOut, reconnectIVL, reconnectIVLMax, sendBufferSize, sendTimeOut, sndHwm, tcpKeepAlive, - tcpKeepAliveCount, tcpKeepAliveIdle, tcpKeepAliveInterval, xpubVerbose); - } - - @Override - public synchronized void append(final LogEvent event) { - final Layout layout = getLayout(); - final byte[] formattedMessage = layout.toByteArray(event); - if (manager.send(getLayout().toByteArray(event))) { - sendRcTrue++; - } else { - sendRcFalse++; - LOGGER.error("Appender {} could not send message {} to JeroMQ {}", getName(), sendRcFalse, formattedMessage); - } - } - - @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { - setStopping(); - boolean stopped = super.stop(timeout, timeUnit, false); - stopped &= manager.stop(timeout, timeUnit); - setStopped(); - return stopped; - } - - // not public, handy for testing - int getSendRcFalse() { - return sendRcFalse; - } - - // not public, handy for testing - int getSendRcTrue() { - return sendRcTrue; - } - - // not public, handy for testing - void resetSendRcs() { - sendRcTrue = sendRcFalse = 0; - } - - @Override - public String toString() { - return "JeroMqAppender{" + - "name=" + getName() + - ", state=" + getState() + - ", manager=" + manager + - ", endpoints=" + endpoints + - '}'; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/package-info.java deleted file mode 100644 index b1df1e7724d..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/jeromq/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * Classes and interfaces for ZeroMQ/JeroMQ support. - * - * @since 2.4 - */ -package org.apache.logging.log4j.core.appender.mom.jeromq; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppender.java deleted file mode 100644 index f7d50af4b8a..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaAppender.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.mom.kafka; - -import java.io.Serializable; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.apache.logging.log4j.core.AbstractLifeCycle; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AbstractAppender; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.layout.SerializedLayout; - -/** - * Sends log events to an Apache Kafka topic. - */ -@Plugin(name = "Kafka", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true) -public final class KafkaAppender extends AbstractAppender { - - /** - * Builds KafkaAppender instances. - * @param The type to build - */ - public static class Builder> extends AbstractAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { - - @PluginAttribute("topic") - private String topic; - - @PluginAttribute("key") - private String key; - - @PluginAttribute(value = "syncSend", defaultBoolean = true) - private boolean syncSend; - - @PluginElement("Properties") - private Property[] properties; - - @SuppressWarnings("resource") - @Override - public KafkaAppender build() { - final Layout layout = getLayout(); - if (layout == null) { - AbstractLifeCycle.LOGGER.error("No layout provided for KafkaAppender"); - return null; - } - final KafkaManager kafkaManager = - new KafkaManager(getConfiguration().getLoggerContext(), getName(), topic, syncSend, properties, key); - return new KafkaAppender(getName(), layout, getFilter(), isIgnoreExceptions(), kafkaManager); - } - - public String getTopic() { - return topic; - } - - public boolean isSyncSend() { - return syncSend; - } - - public Property[] getProperties() { - return properties; - } - - public B setTopic(final String topic) { - this.topic = topic; - return asBuilder(); - } - - public B setSyncSend(final boolean syncSend) { - this.syncSend = syncSend; - return asBuilder(); - } - - public B setProperties(final Property[] properties) { - this.properties = properties; - return asBuilder(); - } - } - - @Deprecated - public static KafkaAppender createAppender( - final Layout layout, - final Filter filter, - final String name, - final boolean ignoreExceptions, - final String topic, - final Property[] properties, - final Configuration configuration, - final String key) { - - if (layout == null) { - AbstractLifeCycle.LOGGER.error("No layout provided for KafkaAppender"); - return null; - } - final KafkaManager kafkaManager = - new KafkaManager(configuration.getLoggerContext(), name, topic, true, properties, key); - return new KafkaAppender(name, layout, filter, ignoreExceptions, kafkaManager); - } - - /** - * Creates a builder for a KafkaAppender. - * @return a builder for a KafkaAppender. - */ - @PluginBuilderFactory - public static > B newBuilder() { - return new Builder().asBuilder(); - } - - private final KafkaManager manager; - - private KafkaAppender(final String name, final Layout layout, final Filter filter, - final boolean ignoreExceptions, final KafkaManager manager) { - super(name, filter, layout, ignoreExceptions); - this.manager = Objects.requireNonNull(manager, "manager"); - } - - @Override - public void append(final LogEvent event) { - if (event.getLoggerName() != null && event.getLoggerName().startsWith("org.apache.kafka")) { - LOGGER.warn("Recursive logging from [{}] for appender [{}].", event.getLoggerName(), getName()); - } else { - try { - tryAppend(event); - } catch (final Exception e) { - error("Unable to write to Kafka in appender [" + getName() + "]", event, e); - } - } - } - - private void tryAppend(final LogEvent event) throws ExecutionException, InterruptedException, TimeoutException { - final Layout layout = getLayout(); - byte[] data; - if (layout instanceof SerializedLayout) { - final byte[] header = layout.getHeader(); - final byte[] body = layout.toByteArray(event); - data = new byte[header.length + body.length]; - System.arraycopy(header, 0, data, 0, header.length); - System.arraycopy(body, 0, data, header.length, body.length); - } else { - data = layout.toByteArray(event); - } - manager.send(data); - } - - @Override - public void start() { - super.start(); - manager.startup(); - } - - @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { - setStopping(); - boolean stopped = super.stop(timeout, timeUnit, false); - stopped &= manager.stop(timeout, timeUnit); - setStopped(); - return stopped; - } - - @Override - public String toString() { - return "KafkaAppender{" + - "name=" + getName() + - ", state=" + getState() + - ", topic=" + manager.getTopic() + - '}'; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaManager.java deleted file mode 100644 index 9af11cd6931..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/KafkaManager.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.mom.kafka; - -import java.nio.charset.StandardCharsets; -import java.util.Objects; -import java.util.Properties; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.apache.kafka.clients.producer.Callback; -import org.apache.kafka.clients.producer.Producer; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.clients.producer.RecordMetadata; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.appender.AbstractManager; -import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.util.Log4jThread; - -public class KafkaManager extends AbstractManager { - - public static final String DEFAULT_TIMEOUT_MILLIS = "30000"; - - /** - * package-private access for testing. - */ - static KafkaProducerFactory producerFactory = new DefaultKafkaProducerFactory(); - - private final Properties config = new Properties(); - private Producer producer; - private final int timeoutMillis; - - private final String topic; - private final String key; - private final boolean syncSend; - - public KafkaManager(final LoggerContext loggerContext, final String name, final String topic, final boolean syncSend, - final Property[] properties, final String key) { - super(loggerContext, name); - this.topic = Objects.requireNonNull(topic, "topic"); - this.syncSend = syncSend; - config.setProperty("key.serializer", "org.apache.kafka.common.serialization.ByteArraySerializer"); - config.setProperty("value.serializer", "org.apache.kafka.common.serialization.ByteArraySerializer"); - config.setProperty("batch.size", "0"); - for (final Property property : properties) { - config.setProperty(property.getName(), property.getValue()); - } - - this.key = key; - - this.timeoutMillis = Integer.parseInt(config.getProperty("timeout.ms", DEFAULT_TIMEOUT_MILLIS)); - } - - @Override - public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - if (timeout > 0) { - closeProducer(timeout, timeUnit); - } else { - closeProducer(timeoutMillis, TimeUnit.MILLISECONDS); - } - return true; - } - - private void closeProducer(final long timeout, final TimeUnit timeUnit) { - if (producer != null) { - // This thread is a workaround for this Kafka issue: https://issues.apache.org/jira/browse/KAFKA-1660 - final Thread closeThread = new Log4jThread(new Runnable() { - @Override - public void run() { - if (producer != null) { - producer.close(); - } - } - }, "KafkaManager-CloseThread"); - closeThread.setDaemon(true); // avoid blocking JVM shutdown - closeThread.start(); - try { - closeThread.join(timeUnit.toMillis(timeout)); - } catch (final InterruptedException ignore) { - Thread.currentThread().interrupt(); - // ignore - } - } - } - - public void send(final byte[] msg) throws ExecutionException, InterruptedException, TimeoutException { - if (producer != null) { - byte[] newKey = null; - - if(key != null && key.contains("${")) { - newKey = getLoggerContext().getConfiguration().getStrSubstitutor().replace(key).getBytes(StandardCharsets.UTF_8); - } else if (key != null) { - newKey = key.getBytes(StandardCharsets.UTF_8); - } - - final ProducerRecord newRecord = new ProducerRecord<>(topic, newKey, msg); - if (syncSend) { - final Future response = producer.send(newRecord); - response.get(timeoutMillis, TimeUnit.MILLISECONDS); - } else { - producer.send(newRecord, new Callback() { - @Override - public void onCompletion(final RecordMetadata metadata, final Exception e) { - if (e != null) { - LOGGER.error("Unable to write to Kafka in appender [" + getName() + "]", e); - } - } - }); - } - } - } - - public void startup() { - producer = producerFactory.newKafkaProducer(config); - } - - public String getTopic() { - return topic; - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/package-info.java deleted file mode 100644 index 60f4dcf09e7..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/kafka/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * Classes and interfaces for Kafka appender support. - * - * @since 2.4 - */ -package org.apache.logging.log4j.core.appender.mom.kafka; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java deleted file mode 100644 index 866b689c777..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/mom/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * Message oriented middleware appenders. - * - * @since 2.1 - */ -package org.apache.logging.log4j.core.appender.mom; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java index 67145c621b0..15d8d2f31d0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/AbstractNoSqlConnection.java @@ -29,7 +29,7 @@ */ public abstract class AbstractNoSqlConnection> implements NoSqlConnection { - private final AtomicBoolean closed = new AtomicBoolean(false); + private final AtomicBoolean closed = new AtomicBoolean(); @Override public void close() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java index aae28ffaaf4..212c2e37fbc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java @@ -16,18 +16,19 @@ */ package org.apache.logging.log4j.core.appender.nosql; -import java.io.Serializable; - import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.util.Booleans; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.io.Serializable; /** * This Appender writes logging events to a NoSQL database using a configured NoSQL provider. It requires @@ -37,22 +38,23 @@ * For examples on how to write your own NoSQL provider, see the simple source code for the MongoDB and CouchDB * providers. *

    - * + * * @see NoSqlObject * @see NoSqlConnection * @see NoSqlProvider */ -@Plugin(name = "NoSql", category = "Core", elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("NoSql") public final class NoSqlAppender extends AbstractDatabaseAppender> { /** * Builds ConsoleAppender instances. - * + * * @param * The type to build */ public static class Builder> extends AbstractAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute("bufferSize") private int bufferSize; @@ -78,93 +80,45 @@ public NoSqlAppender build() { return null; } - return new NoSqlAppender(name, getFilter(), getLayout(), isIgnoreExceptions(), manager); + return new NoSqlAppender(name, getFilter(), getLayout(), isIgnoreExceptions(), getPropertyArray(), manager); } /** * Sets the buffer size. - * + * * @param bufferSize * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the * buffer reaches this size. * @return this */ - public B setBufferSize(int bufferSize) { + public B setBufferSize(final int bufferSize) { this.bufferSize = bufferSize; return asBuilder(); } /** * Sets the provider. - * + * * @param provider * The NoSQL provider that provides connections to the chosen NoSQL database. * @return this */ - public B setProvider(NoSqlProvider provider) { + public B setProvider(final NoSqlProvider provider) { this.provider = provider; return asBuilder(); } } - /** - * Factory method for creating a NoSQL appender within the plugin manager. - * - * @param name - * The name of the appender. - * @param ignore - * If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they - * are propagated to the caller. - * @param filter - * The filter, if any, to use. - * @param bufferSize - * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the - * buffer reaches this size. - * @param provider - * The NoSQL provider that provides connections to the chosen NoSQL database. - * @return a new NoSQL appender. - * @deprecated since 2.10.1; use {@link Builder}. - */ - @SuppressWarnings("resource") - @Deprecated - public static NoSqlAppender createAppender( - // @formatter:off - final String name, - final String ignore, - final Filter filter, - final String bufferSize, - final NoSqlProvider provider) { - // @formatter:on - if (provider == null) { - LOGGER.error("NoSQL provider not specified for appender [{}].", name); - return null; - } - - final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0); - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - - final String managerName = "noSqlManager{ description=" + name + ", bufferSize=" + bufferSizeInt + ", provider=" - + provider + " }"; - - final NoSqlDatabaseManager manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, bufferSizeInt, - provider); - if (manager == null) { - return null; - } - - return new NoSqlAppender(name, filter, null, ignoreExceptions, manager); - } - - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } private final String description; - private NoSqlAppender(final String name, final Filter filter, Layout layout, - final boolean ignoreExceptions, final NoSqlDatabaseManager manager) { - super(name, filter, layout, ignoreExceptions, manager); + private NoSqlAppender(final String name, final Filter filter, final Layout layout, + final boolean ignoreExceptions, final Property[] properties, final NoSqlDatabaseManager manager) { + super(name, filter, layout, ignoreExceptions, properties, manager); this.description = this.getName() + "{ manager=" + this.getManager() + " }"; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java index 80acae7c5f1..3f1782ceebe 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java @@ -18,8 +18,6 @@ import java.io.Serializable; -import javax.jms.JMSException; - import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; @@ -69,12 +67,6 @@ protected void connectAndStart() { } } - @Deprecated - @Override - protected void writeInternal(final LogEvent event) { - writeInternal(event, null); - } - @Override protected void writeInternal(final LogEvent event, final Serializable serializable) { if (!this.isRunning() || this.connection == null || this.connection.isClosed()) { @@ -94,12 +86,7 @@ protected void writeInternal(final LogEvent event, final Serializable serializab private void setFields(final MapMessage mapMessage, final NoSqlObject noSqlObject) { // Map without calling org.apache.logging.log4j.message.MapMessage#getData() which makes a copy of the map. - mapMessage.forEach(new BiConsumer() { - @Override - public void accept(final String key, final Object value) { - noSqlObject.set(key, value); - } - }); + mapMessage.forEach((key, value) -> noSqlObject.set(key, value)); } private void setFields(final LogEvent event, final NoSqlObject entity) { @@ -155,12 +142,7 @@ private void setFields(final LogEvent event, final NoSqlObject entity) { entity.set("contextMap", (Object) null); } else { final NoSqlObject contextMapEntity = this.connection.createObject(); - contextMap.forEach(new BiConsumer() { - @Override - public void accept(final String key, final String val) { - contextMapEntity.set(key, val); - } - }); + contextMap.forEach((BiConsumer) (key, val) -> contextMapEntity.set(key, val)); entity.set("contextMap", contextMapEntity); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java index 39c4d60c1a4..27267958300 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlProvider.java @@ -23,7 +23,7 @@ * @param Specifies which implementation of {@link NoSqlConnection} this provider provides. */ public interface NoSqlProvider>> { - + /** * Obtains a connection from this provider. The concept of a connection in this case is not strictly an active * duplex UDP or TCP connection to the underlying database. It can be thought of more as a gateway, a path for @@ -32,7 +32,7 @@ public interface NoSqlProvider - * + * * @return a connection that can be used to create and persist objects to this database. * @see NoSqlConnection */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicy.java index a1ef2ef2318..4c171482f9b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/LoggerNameLevelRewritePolicy.java @@ -16,31 +16,32 @@ */ package org.apache.logging.log4j.core.appender.rewrite; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; /** * Rewrites log event levels for a given logger name. - * + * * @since 2.4 */ -@Plugin(name = "LoggerNameLevelRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = true) +@Configurable(elementType = "rewritePolicy", printObject = true) +@Plugin("LoggerNameLevelRewritePolicy") public class LoggerNameLevelRewritePolicy implements RewritePolicy { /** * Creates a policy to rewrite levels for a given logger name. - * + * * @param loggerNamePrefix * The logger name prefix for events to rewrite; all event logger names that start with this string will be * rewritten. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicy.java index 5302a07b203..32fd1fccf9a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicy.java @@ -16,28 +16,29 @@ */ package org.apache.logging.log4j.core.appender.rewrite; -import java.util.HashMap; -import java.util.Map; - import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; +import java.util.HashMap; +import java.util.Map; + /** * This policy modifies events by replacing or possibly adding keys and values to the MapMessage. */ -@Plugin(name = "MapRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = true) +@Configurable(elementType = "rewritePolicy", printObject = true) +@Plugin public final class MapRewritePolicy implements RewritePolicy { - + /** * Allow subclasses access to the status logger without creating another instance. */ @@ -90,12 +91,12 @@ public LogEvent rewrite(final LogEvent source) { * keys should be updated. */ public enum Mode { - + /** * Keys should be added. */ Add, - + /** * Keys should be updated. */ @@ -127,7 +128,7 @@ public String toString() { */ @PluginFactory public static MapRewritePolicy createPolicy( - @PluginAttribute("mode") final String mode, + @PluginAttribute final String mode, @PluginElement("KeyValuePair") final KeyValuePair[] pairs) { Mode op = mode == null ? op = Mode.Add : Mode.valueOf(mode); if (pairs == null || pairs.length == 0) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/PropertiesRewritePolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/PropertiesRewritePolicy.java index 88a574dc5bf..112b2ee625e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/PropertiesRewritePolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/PropertiesRewritePolicy.java @@ -16,31 +16,32 @@ */ package org.apache.logging.log4j.core.appender.rewrite; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.StringMap; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * This policy modifies events by replacing or possibly adding keys and values to the MapMessage. */ -@Plugin(name = "PropertiesRewritePolicy", category = Core.CATEGORY_NAME, elementType = "rewritePolicy", printObject = true) +@Configurable(elementType = "rewritePolicy", printObject = true) +@Plugin public final class PropertiesRewritePolicy implements RewritePolicy { - + /** * Allows subclasses access to the status logger without creating another instance. */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppender.java index 64a0abdf99d..50923288eab 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppender.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppender.java @@ -16,28 +16,30 @@ */ package org.apache.logging.log4j.core.appender.rewrite; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.AppenderControl; import org.apache.logging.log4j.core.config.AppenderRef; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.util.Booleans; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; /** * This Appender allows the logging event to be manipulated before it is processed by other Appenders. */ -@Plugin(name = "Rewrite", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Rewrite") public final class RewriteAppender extends AbstractAppender { private final Configuration config; @@ -47,8 +49,8 @@ public final class RewriteAppender extends AbstractAppender { private RewriteAppender(final String name, final Filter filter, final boolean ignoreExceptions, final AppenderRef[] appenderRefs, final RewritePolicy rewritePolicy, - final Configuration config) { - super(name, filter, null, ignoreExceptions); + final Configuration config, final Property[] properties) { + super(name, filter, null, ignoreExceptions, properties); this.config = config; this.rewritePolicy = rewritePolicy; this.appenderRefs = appenderRefs; @@ -97,22 +99,12 @@ public void append(LogEvent event) { */ @PluginFactory public static RewriteAppender createAppender( - @PluginAttribute("name") final String name, - @PluginAttribute("ignoreExceptions") final String ignore, - @PluginElement("AppenderRef") final AppenderRef[] appenderRefs, + @PluginAttribute @Required(message = "No name provided for RewriteAppender") final String name, + @PluginAttribute(defaultBoolean = true) final boolean ignoreExceptions, + @PluginElement @Required(message = "No appender references defined for RewriteAppender") final AppenderRef[] appenderRefs, @PluginConfiguration final Configuration config, - @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy, - @PluginElement("Filter") final Filter filter) { - - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - if (name == null) { - LOGGER.error("No name provided for RewriteAppender"); - return null; - } - if (appenderRefs == null) { - LOGGER.error("No appender references defined for RewriteAppender"); - return null; - } - return new RewriteAppender(name, filter, ignoreExceptions, appenderRefs, rewritePolicy, config); + @PluginElement final RewritePolicy rewritePolicy, + @PluginElement final Filter filter) { + return new RewriteAppender(name, filter, ignoreExceptions, appenderRefs, rewritePolicy, config, Property.EMPTY_ARRAY); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java index 741afb8b186..ce450ecb82b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java @@ -46,6 +46,8 @@ public abstract class AbstractRolloverStrategy implements RolloverStrategy { */ protected static final Logger LOGGER = StatusLogger.getLogger(); + public static final Pattern PATTERN_COUNTER= Pattern.compile(".*%((?0)?(?\\d+))?i.*"); + protected final StrSubstitutor strSubstitutor; protected AbstractRolloverStrategy(final StrSubstitutor strSubstitutor) { @@ -89,14 +91,16 @@ protected SortedMap getEligibleFiles(final RollingFileManager man final StringBuilder buf = new StringBuilder(); final String pattern = manager.getPatternProcessor().getPattern(); manager.getPatternProcessor().formatFileName(strSubstitutor, buf, NotANumber.NAN); - return getEligibleFiles(buf.toString(), pattern, isAscending); + final String fileName = manager.isDirectWrite() ? "" : manager.getFileName(); + return getEligibleFiles(fileName, buf.toString(), pattern, isAscending); } protected SortedMap getEligibleFiles(final String path, final String pattern) { - return getEligibleFiles(path, pattern, true); + return getEligibleFiles("", path, pattern, true); } - protected SortedMap getEligibleFiles(final String path, final String logfilePattern, final boolean isAscending) { + protected SortedMap getEligibleFiles(final String currentFile, final String path, + final String logfilePattern, final boolean isAscending) { final TreeMap eligibleFiles = new TreeMap<>(); final File file = new File(path); File parent = file.getParentFile(); @@ -105,24 +109,37 @@ protected SortedMap getEligibleFiles(final String path, final Str } else { parent.mkdirs(); } - if (!logfilePattern.contains("%i")) { + if (!PATTERN_COUNTER.matcher(logfilePattern).matches()) { return eligibleFiles; } final Path dir = parent.toPath(); String fileName = file.getName(); final int suffixLength = suffixLength(fileName); + // use Pattern.quote to treat all initial parts of the fileName as literal + // this fixes issues with filenames containing 'magic' regex characters if (suffixLength > 0) { - fileName = fileName.substring(0, fileName.length() - suffixLength) + ".*"; + fileName = Pattern.quote(fileName.substring(0, fileName.length() - suffixLength)) + ".*"; + } else { + fileName = Pattern.quote(fileName); } - final String filePattern = fileName.replace(NotANumber.VALUE, "(\\d+)"); + // since we insert a pattern inside a regex escaped string, + // surround it with quote characters so that (\d) is treated as a pattern and not a literal + final String filePattern = fileName.replaceFirst("0*\\u0000", "\\\\E(0?\\\\d+)\\\\Q"); final Pattern pattern = Pattern.compile(filePattern); + final Path current = currentFile.length() > 0 ? new File(currentFile).toPath() : null; + LOGGER.debug("Current file: {}", currentFile); - try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + try (final DirectoryStream stream = Files.newDirectoryStream(dir)) { for (final Path entry: stream) { final Matcher matcher = pattern.matcher(entry.toFile().getName()); - if (matcher.matches()) { - final Integer index = Integer.parseInt(matcher.group(1)); - eligibleFiles.put(index, entry); + if (matcher.matches() && !entry.equals(current)) { + try { + final Integer index = Integer.parseInt(matcher.group(1)); + eligibleFiles.put(index, entry); + } catch (final NumberFormatException ex) { + LOGGER.debug("Ignoring file {} which matches pattern but the index is invalid.", + entry.toFile().getName()); + } } } } catch (final IOException ioe) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CompositeTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CompositeTriggeringPolicy.java index ecb48ccf3da..f85c8169685 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CompositeTriggeringPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CompositeTriggeringPolicy.java @@ -16,21 +16,21 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import java.util.Arrays; -import java.util.concurrent.TimeUnit; - -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LifeCycle; -import org.apache.logging.log4j.core.LifeCycle2; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; /** * Triggering policy that wraps other triggering policies. */ -@Plugin(name = "Policies", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("Policies") public final class CompositeTriggeringPolicy extends AbstractTriggeringPolicy { private final TriggeringPolicy[] triggeringPolicies; @@ -50,6 +50,7 @@ public TriggeringPolicy[] getTriggeringPolicies() { @Override public void initialize(final RollingFileManager manager) { for (final TriggeringPolicy triggeringPolicy : triggeringPolicies) { + LOGGER.debug("Initializing triggering policy {}", triggeringPolicy.toString()); triggeringPolicy.initialize(manager); } } @@ -85,12 +86,7 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { setStopping(); boolean stopped = true; for (final TriggeringPolicy triggeringPolicy : triggeringPolicies) { - if (triggeringPolicy instanceof LifeCycle2) { - stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit); - } else if (triggeringPolicy instanceof LifeCycle) { - ((LifeCycle) triggeringPolicy).stop(); - stopped &= true; - } + stopped &= ((LifeCycle) triggeringPolicy).stop(timeout, timeUnit); } setStopped(); return stopped; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicy.java index 6eb3e505049..ec972f4c7e6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicy.java @@ -21,22 +21,23 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationScheduler; import org.apache.logging.log4j.core.config.CronScheduledFuture; import org.apache.logging.log4j.core.config.Scheduled; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.util.CronExpression; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; /** * Rolls a file over based on a cron schedule. */ -@Plugin(name = "CronTriggeringPolicy", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin @Scheduled public final class CronTriggeringPolicy extends AbstractTriggeringPolicy { @@ -57,7 +58,7 @@ private CronTriggeringPolicy(final CronExpression schedule, final boolean checkO /** * Initializes the policy. - * + * * @param aManager * The RollingFileManager. */ @@ -70,6 +71,7 @@ public void initialize(final RollingFileManager aManager) { aManager.getPatternProcessor().setCurrentFileTime(lastRegularRoll.getTime()); LOGGER.debug("LastRollForFile {}, LastRegularRole {}", lastRollForFile, lastRegularRoll); aManager.getPatternProcessor().setPrevFileTime(lastRegularRoll.getTime()); + aManager.getPatternProcessor().setTimeBased(true); if (checkOnStartup && lastRollForFile != null && lastRegularRoll != null && lastRollForFile.before(lastRegularRoll)) { lastRollDate = lastRollForFile; @@ -91,7 +93,7 @@ public void initialize(final RollingFileManager aManager) { /** * Determines whether a rollover should occur. - * + * * @param event * A reference to the currently event. * @return true if a rollover should occur. @@ -107,7 +109,7 @@ public CronExpression getCronExpression() { /** * Creates a ScheduledTriggeringPolicy. - * + * * @param configuration * the Configuration. * @param evaluateOnStartup @@ -118,8 +120,8 @@ public CronExpression getCronExpression() { */ @PluginFactory public static CronTriggeringPolicy createPolicy(@PluginConfiguration final Configuration configuration, - @PluginAttribute("evaluateOnStartup") final String evaluateOnStartup, - @PluginAttribute("schedule") final String schedule) { + @PluginAttribute final String evaluateOnStartup, + @PluginAttribute final String schedule) { CronExpression cronExpression; final boolean checkOnStartup = Boolean.parseBoolean(evaluateOnStartup); if (schedule == null) { @@ -145,10 +147,8 @@ private static CronExpression getSchedule(final String expression) { } private void rollover() { - manager.getPatternProcessor().setPrevFileTime(lastRollDate.getTime()); - final Date thisRoll = cronExpression.getPrevFireTime(new Date()); - manager.getPatternProcessor().setCurrentFileTime(thisRoll.getTime()); - manager.rollover(); + Date rollTime = future != null ? future.getFireTime() : new Date(); + manager.rollover(cronExpression.getPrevFireTime(rollTime).getTime(), lastRollDate.getTime()); if (future != null) { lastRollDate = future.getFireTime(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java index dfacd72eeb1..20674ff6f71 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java @@ -16,6 +16,21 @@ */ package org.apache.logging.log4j.core.appender.rolling; +import org.apache.logging.log4j.core.appender.rolling.action.Action; +import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction; +import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction; +import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; +import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -28,23 +43,6 @@ import java.util.concurrent.TimeUnit; import java.util.zip.Deflater; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.appender.rolling.action.Action; -import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction; -import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction; -import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; -import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.util.Integers; - /** * When rolling over, DefaultRolloverStrategy renames files according to an algorithm as described below. * @@ -78,7 +76,8 @@ * are discouraged. *

    */ -@Plugin(name = "DefaultRolloverStrategy", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public class DefaultRolloverStrategy extends AbstractRolloverStrategy { private static final int MIN_WINDOW_SIZE = 1; @@ -87,13 +86,13 @@ public class DefaultRolloverStrategy extends AbstractRolloverStrategy { /** * Builds DefaultRolloverStrategy instances. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute("max") private String max; - + @PluginBuilderAttribute("min") private String min; - + @PluginBuilderAttribute("fileIndex") private String fileIndex; @@ -116,17 +115,17 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde public DefaultRolloverStrategy build() { int minIndex; int maxIndex; - boolean useMax; + final boolean useMax; if (fileIndex != null && fileIndex.equalsIgnoreCase("nomax")) { minIndex = Integer.MIN_VALUE; maxIndex = Integer.MAX_VALUE; useMax = false; } else { - useMax = fileIndex == null ? true : fileIndex.equalsIgnoreCase("max"); + useMax = fileIndex == null || fileIndex.equalsIgnoreCase("max"); minIndex = MIN_WINDOW_SIZE; if (min != null) { - minIndex = Integer.parseInt(min); + minIndex = Integer.parseInt(min.trim()); if (minIndex < 1) { LOGGER.error("Minimum window size too small. Limited to " + MIN_WINDOW_SIZE); minIndex = MIN_WINDOW_SIZE; @@ -134,15 +133,18 @@ public DefaultRolloverStrategy build() { } maxIndex = DEFAULT_WINDOW_SIZE; if (max != null) { - maxIndex = Integer.parseInt(max); + maxIndex = Integer.parseInt(max.trim()); if (maxIndex < minIndex) { maxIndex = minIndex < DEFAULT_WINDOW_SIZE ? DEFAULT_WINDOW_SIZE : minIndex; LOGGER.error("Maximum window size must be greater than the minimum windows size. Set to " + maxIndex); } } } - final int compressionLevel = Integers.parseInt(compressionLevelStr, Deflater.DEFAULT_COMPRESSION); - return new DefaultRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, config.getStrSubstitutor(), + final String trimmedCompressionLevelStr = compressionLevelStr != null ? compressionLevelStr.trim() : compressionLevelStr; + final int compressionLevel = Integers.parseInt(trimmedCompressionLevelStr, Deflater.DEFAULT_COMPRESSION); + // The config object can be null when this object is built programmatically. + final StrSubstitutor nonNullStrSubstitutor = config != null ? config.getStrSubstitutor() : new StrSubstitutor(); + return new DefaultRolloverStrategy(minIndex, maxIndex, useMax, compressionLevel, nonNullStrSubstitutor, customActions, stopCustomActionsOnError, tempCompressedFilePattern); } @@ -156,7 +158,7 @@ public String getMax() { * @param max The maximum number of files to keep. * @return This builder for chaining convenience */ - public Builder withMax(final String max) { + public Builder setMax(final String max) { this.max = max; return this; } @@ -171,7 +173,7 @@ public String getMin() { * @param min The minimum number of files to keep. * @return This builder for chaining convenience */ - public Builder withMin(final String min) { + public Builder setMin(final String min) { this.min = min; return this; } @@ -187,7 +189,7 @@ public String getFileIndex() { * index. If set to "min", file renaming and the counter will follow the Fixed Window strategy. * @return This builder for chaining convenience */ - public Builder withFileIndex(final String fileIndex) { + public Builder setFileIndex(final String fileIndex) { this.fileIndex = fileIndex; return this; } @@ -202,7 +204,7 @@ public String getCompressionLevelStr() { * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files. * @return This builder for chaining convenience */ - public Builder withCompressionLevelStr(final String compressionLevelStr) { + public Builder setCompressionLevelStr(final String compressionLevelStr) { this.compressionLevelStr = compressionLevelStr; return this; } @@ -217,7 +219,7 @@ public Action[] getCustomActions() { * @param customActions custom actions to perform asynchronously after rollover * @return This builder for chaining convenience */ - public Builder withCustomActions(final Action[] customActions) { + public Builder setCustomActions(final Action... customActions) { this.customActions = customActions; return this; } @@ -232,7 +234,7 @@ public boolean isStopCustomActionsOnError() { * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs * @return This builder for chaining convenience */ - public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) { + public Builder setStopCustomActionsOnError(final boolean stopCustomActionsOnError) { this.stopCustomActionsOnError = stopCustomActionsOnError; return this; } @@ -247,7 +249,7 @@ public String getTempCompressedFilePattern() { * @param tempCompressedFilePattern File pattern of the working file pattern used during compression, if null no temporary file are used * @return This builder for chaining convenience */ - public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) { + public Builder setTempCompressedFilePattern(final String tempCompressedFilePattern) { this.tempCompressedFilePattern = tempCompressedFilePattern; return this; } @@ -258,59 +260,21 @@ public Configuration getConfig() { /** * Defines configuration. - * + * * @param config The Configuration. * @return This builder for chaining convenience */ - public Builder withConfig(final Configuration config) { + public Builder setConfig(final Configuration config) { this.config = config; return this; } } - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - /** - * Creates the DefaultRolloverStrategy. - * - * @param max The maximum number of files to keep. - * @param min The minimum number of files to keep. - * @param fileIndex If set to "max" (the default), files with a higher index will be newer than files with a smaller - * index. If set to "min", file renaming and the counter will follow the Fixed Window strategy. - * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files. - * @param customActions custom actions to perform asynchronously after rollover - * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs - * @param config The Configuration. - * @return A DefaultRolloverStrategy. - * @deprecated Since 2.9 Usage of Builder API is preferable - */ - @PluginFactory - @Deprecated - public static DefaultRolloverStrategy createStrategy( - // @formatter:off - @PluginAttribute("max") final String max, - @PluginAttribute("min") final String min, - @PluginAttribute("fileIndex") final String fileIndex, - @PluginAttribute("compressionLevel") final String compressionLevelStr, - @PluginElement("Actions") final Action[] customActions, - @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true) - final boolean stopCustomActionsOnError, - @PluginConfiguration final Configuration config) { - return DefaultRolloverStrategy.newBuilder() - .withMin(min) - .withMax(max) - .withFileIndex(fileIndex) - .withCompressionLevelStr(compressionLevelStr) - .withCustomActions(customActions) - .withStopCustomActionsOnError(stopCustomActionsOnError) - .withConfig(config) - .build(); - // @formatter:on - } - /** * Index for oldest retained log file. */ @@ -326,23 +290,6 @@ public static DefaultRolloverStrategy createStrategy( private final boolean stopCustomActionsOnError; private final PatternProcessor tempCompressedFilePattern; - /** - * Constructs a new instance. - * - * @param minIndex The minimum index. - * @param maxIndex The maximum index. - * @param customActions custom actions to perform asynchronously after rollover - * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs - * @deprecated Since 2.9 Added tempCompressedFilePatternString parameter - */ - @Deprecated - protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final boolean useMax, - final int compressionLevel, final StrSubstitutor strSubstitutor, final Action[] customActions, - final boolean stopCustomActionsOnError) { - this(minIndex, maxIndex, useMax, compressionLevel, - strSubstitutor, customActions, stopCustomActionsOnError, null); - } - /** * Constructs a new instance. * @@ -412,7 +359,8 @@ private int purgeAscending(final int lowIndex, final int highIndex, final Rollin final SortedMap eligibleFiles = getEligibleFiles(manager); final int maxFiles = highIndex - lowIndex + 1; - boolean renameFiles = false; + LOGGER.debug("Eligible files: {}", eligibleFiles); + boolean renameFiles = !eligibleFiles.isEmpty() && eligibleFiles.lastKey() >= maxIndex; while (eligibleFiles.size() >= maxFiles) { try { LOGGER.debug("Eligible files: {}", eligibleFiles); @@ -468,10 +416,12 @@ private int purgeDescending(final int lowIndex, final int highIndex, final Rolli // Retrieve the files in descending order, so the highest key will be first. final SortedMap eligibleFiles = getEligibleFiles(manager, false); final int maxFiles = highIndex - lowIndex + 1; + LOGGER.debug("Eligible files: {}", eligibleFiles); while (eligibleFiles.size() >= maxFiles) { try { final Integer key = eligibleFiles.firstKey(); + LOGGER.debug("Deleting {}", eligibleFiles.get(key).toFile().getAbsolutePath()); Files.delete(eligibleFiles.get(key)); eligibleFiles.remove(key); } catch (final IOException ioe) { @@ -514,10 +464,12 @@ private int purgeDescending(final int lowIndex, final int highIndex, final Rolli */ @Override public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException { - int fileIndex; + final int fileIndex; + final StringBuilder buf = new StringBuilder(255); if (minIndex == Integer.MIN_VALUE) { final SortedMap eligibleFiles = getEligibleFiles(manager); fileIndex = eligibleFiles.size() > 0 ? eligibleFiles.lastKey() + 1 : 1; + manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex); } else { if (maxIndex < 0) { return null; @@ -527,13 +479,13 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec if (fileIndex < 0) { return null; } + manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex); if (LOGGER.isTraceEnabled()) { final double durationMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos); LOGGER.trace("DefaultRolloverStrategy.purge() took {} milliseconds", durationMillis); } } - final StringBuilder buf = new StringBuilder(255); - manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex); + final String currentFileName = manager.getFileName(); String renameTo = buf.toString(); @@ -571,17 +523,17 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec } if (compressAction != null && manager.isAttributeViewEnabled()) { - // Propagate posix attribute view to compressed file + // Propagate POSIX attribute view to compressed file // @formatter:off final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder() - .withBasePath(compressedName) - .withFollowLinks(false) - .withMaxDepth(1) - .withPathConditions(new PathCondition[0]) - .withSubst(getStrSubstitutor()) - .withFilePermissions(manager.getFilePermissions()) - .withFileOwner(manager.getFileOwner()) - .withFileGroup(manager.getFileGroup()) + .setBasePath(compressedName) + .setFollowLinks(false) + .setMaxDepth(1) + .setPathConditions(PathCondition.EMPTY_ARRAY) + .setSubst(getStrSubstitutor()) + .setFilePermissions(manager.getFilePermissions()) + .setFileOwner(manager.getFileOwner()) + .setFileGroup(manager.getFileGroup()) .build(); // @formatter:on compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java index 4d27f8d569c..3daeee31203 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java @@ -22,4 +22,6 @@ public interface DirectFileRolloverStrategy { String getCurrentFileName(final RollingFileManager manager); + + void clearCurrentFileName(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java index b1ee506fc9e..cb8b7cbcff0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java @@ -16,6 +16,21 @@ */ package org.apache.logging.log4j.core.appender.rolling; +import org.apache.logging.log4j.core.appender.rolling.action.Action; +import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction; +import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction; +import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; +import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -27,23 +42,6 @@ import java.util.concurrent.TimeUnit; import java.util.zip.Deflater; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.appender.rolling.action.Action; -import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction; -import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction; -import org.apache.logging.log4j.core.appender.rolling.action.PathCondition; -import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.util.Integers; - /** * When rolling over, DirectWriteRolloverStrategy writes directly to the file as resolved by the file * pattern. Files will be renamed files according to an algorithm as described below. @@ -56,15 +54,16 @@ * * @since 2.8 */ -@Plugin(name = "DirectWriteRolloverStrategy", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public class DirectWriteRolloverStrategy extends AbstractRolloverStrategy implements DirectFileRolloverStrategy { private static final int DEFAULT_MAX_FILES = 7; - + /** * Builds DirectWriteRolloverStrategy instances. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute("maxFiles") private String maxFiles; @@ -110,7 +109,7 @@ public String getMaxFiles() { * @param maxFiles The maximum number of files that match the date portion of the pattern to keep. * @return This builder for chaining convenience */ - public Builder withMaxFiles(final String maxFiles) { + public Builder setMaxFiles(final String maxFiles) { this.maxFiles = maxFiles; return this; } @@ -125,7 +124,7 @@ public String getCompressionLevelStr() { * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files. * @return This builder for chaining convenience */ - public Builder withCompressionLevelStr(final String compressionLevelStr) { + public Builder setCompressionLevelStr(final String compressionLevelStr) { this.compressionLevelStr = compressionLevelStr; return this; } @@ -140,7 +139,7 @@ public Action[] getCustomActions() { * @param customActions custom actions to perform asynchronously after rollover * @return This builder for chaining convenience */ - public Builder withCustomActions(final Action[] customActions) { + public Builder setCustomActions(final Action... customActions) { this.customActions = customActions; return this; } @@ -155,7 +154,7 @@ public boolean isStopCustomActionsOnError() { * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs * @return This builder for chaining convenience */ - public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) { + public Builder setStopCustomActionsOnError(final boolean stopCustomActionsOnError) { this.stopCustomActionsOnError = stopCustomActionsOnError; return this; } @@ -170,7 +169,7 @@ public String getTempCompressedFilePattern() { * @param tempCompressedFilePattern File pattern of the working file pattern used during compression, if null no temporary file are used * @return This builder for chaining convenience */ - public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) { + public Builder setTempCompressedFilePattern(final String tempCompressedFilePattern) { this.tempCompressedFilePattern = tempCompressedFilePattern; return this; } @@ -181,51 +180,21 @@ public Configuration getConfig() { /** * Defines configuration. - * + * * @param config The Configuration. * @return This builder for chaining convenience */ - public Builder withConfig(final Configuration config) { + public Builder setConfig(final Configuration config) { this.config = config; return this; } } - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - /** - * Creates the DirectWriteRolloverStrategy. - * - * @param maxFiles The maximum number of files that match the date portion of the pattern to keep. - * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files. - * @param customActions custom actions to perform asynchronously after rollover - * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs - * @param config The Configuration. - * @return A DirectWriteRolloverStrategy. - * @deprecated Since 2.9 Usage of Builder API is preferable - */ - @Deprecated - @PluginFactory - public static DirectWriteRolloverStrategy createStrategy( - // @formatter:off - @PluginAttribute("maxFiles") final String maxFiles, - @PluginAttribute("compressionLevel") final String compressionLevelStr, - @PluginElement("Actions") final Action[] customActions, - @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true) - final boolean stopCustomActionsOnError, - @PluginConfiguration final Configuration config) { - return newBuilder().withMaxFiles(maxFiles) - .withCompressionLevelStr(compressionLevelStr) - .withCustomActions(customActions) - .withStopCustomActionsOnError(stopCustomActionsOnError) - .withConfig(config) - .build(); - // @formatter:on - } - /** * Index for most recent log file. */ @@ -237,21 +206,6 @@ public static DirectWriteRolloverStrategy createStrategy( private int nextIndex = -1; private final PatternProcessor tempCompressedFilePattern; - /** - * Constructs a new instance. - * - * @param maxFiles The maximum number of files that match the date portion of the pattern to keep. - * @param customActions custom actions to perform asynchronously after rollover - * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs - * @deprecated Since 2.9 Added tempCompressedFilePatternString parameter - */ - @Deprecated - protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionLevel, - final StrSubstitutor strSubstitutor, final Action[] customActions, - final boolean stopCustomActionsOnError) { - this(maxFiles, compressionLevel, strSubstitutor, customActions, stopCustomActionsOnError, null); - } - /** * Constructs a new instance. * @@ -313,8 +267,11 @@ private int purge(final RollingFileManager manager) { public String getCurrentFileName(final RollingFileManager manager) { if (currentFileName == null) { final SortedMap eligibleFiles = getEligibleFiles(manager); - final int fileIndex = eligibleFiles.size() > 0 ? (nextIndex > 0 ? nextIndex : eligibleFiles.size()) : 1; + final int fileIndex = eligibleFiles.size() > 0 ? (nextIndex > 0 ? nextIndex : + eligibleFiles.lastKey()) : 1; final StringBuilder buf = new StringBuilder(255); + // LOG4J2-3339 - Always use the current time for new direct write files. + manager.getPatternProcessor().setCurrentFileTime(System.currentTimeMillis()); manager.getPatternProcessor().formatFileName(strSubstitutor, buf, true, fileIndex); final int suffixLength = suffixLength(buf.toString()); final String name = suffixLength > 0 ? buf.substring(0, buf.length() - suffixLength) : buf.toString(); @@ -323,6 +280,11 @@ public String getCurrentFileName(final RollingFileManager manager) { return currentFileName; } + @Override + public void clearCurrentFileName() { + currentFileName = null; + } + /** * Performs the rollover. * @@ -349,7 +311,7 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec nextIndex = fileIndex + 1; final FileExtension fileExtension = manager.getFileExtension(); if (fileExtension != null) { - compressedName += fileExtension.getExtension(); + compressedName += fileExtension.getExtension(); if (tempCompressedFilePattern != null) { final StringBuilder buf = new StringBuilder(); tempCompressedFilePattern.formatFileName(strSubstitutor, buf, fileIndex); @@ -372,17 +334,17 @@ public RolloverDescription rollover(final RollingFileManager manager) throws Sec } if (compressAction != null && manager.isAttributeViewEnabled()) { - // Propagate posix attribute view to compressed file + // Propagate POSIX attribute view to compressed file // @formatter:off final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder() - .withBasePath(compressedName) - .withFollowLinks(false) - .withMaxDepth(1) - .withPathConditions(new PathCondition[0]) - .withSubst(getStrSubstitutor()) - .withFilePermissions(manager.getFilePermissions()) - .withFileOwner(manager.getFileOwner()) - .withFileGroup(manager.getFileGroup()) + .setBasePath(compressedName) + .setFollowLinks(false) + .setMaxDepth(1) + .setPathConditions(PathCondition.EMPTY_ARRAY) + .setSubst(getStrSubstitutor()) + .setFilePermissions(manager.getFilePermissions()) + .setFileOwner(manager.getFileOwner()) + .setFileGroup(manager.getFileGroup()) .build(); // @formatter:on compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java index 9d0016fbffa..e5d6be4b418 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java @@ -39,7 +39,7 @@ Action createCompressAction(final String renameTo, final String compressedName, @Override Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource, final int compressionLevel) { - return new GzCompressAction(source(renameTo), target(compressedName), deleteSource); + return new GzCompressAction(source(renameTo), target(compressedName), deleteSource, compressionLevel); } }, BZIP2(".bz2") { @@ -95,7 +95,7 @@ public static FileExtension lookupForFile(final String fileName) { private final String extension; - private FileExtension(final String extension) { + FileExtension(final String extension) { Objects.requireNonNull(extension, "extension"); this.extension = extension; } @@ -121,5 +121,5 @@ File source(final String fileName) { File target(final String fileName) { return new File(fileName); - } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileSize.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileSize.java index bd5dce8b314..fb0aaf66866 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileSize.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileSize.java @@ -17,33 +17,34 @@ package org.apache.logging.log4j.core.appender.rolling; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + import java.text.NumberFormat; import java.text.ParseException; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; - /** * FileSize utility class. */ public final class FileSize { + private static final Logger LOGGER = StatusLogger.getLogger(); private static final long KB = 1024; private static final long MB = KB * KB; private static final long GB = KB * MB; + private static final long TB = KB * GB; /** * Pattern for string parsing. */ private static final Pattern VALUE_PATTERN = - Pattern.compile("([0-9]+([\\.,][0-9]+)?)\\s*(|K|M|G)B?", Pattern.CASE_INSENSITIVE); + Pattern.compile("([0-9]+([.,][0-9]+)?)\\s*(|K|M|G|T)B?", Pattern.CASE_INSENSITIVE); - private FileSize() { - } + private FileSize() {} /** * Converts a string to a number of bytes. Strings consist of a floating point value followed by @@ -60,32 +61,43 @@ public static long parse(final String string, final long defaultValue) { // Valid input? if (matcher.matches()) { try { - // Get double precision value - final long value = NumberFormat.getNumberInstance(Locale.getDefault()).parse( - matcher.group(1)).longValue(); - - // Get units specified - final String units = matcher.group(3); - - if (units.isEmpty()) { - return value; - } else if (units.equalsIgnoreCase("K")) { - return value * KB; - } else if (units.equalsIgnoreCase("M")) { - return value * MB; - } else if (units.equalsIgnoreCase("G")) { - return value * GB; + + // Read the quantity. + final String quantityString = matcher.group(1); + final double quantity = NumberFormat + .getNumberInstance(Locale.ROOT) + .parse(quantityString) + .doubleValue(); + + // Read the unit. + final String unit = matcher.group(3); + + // Calculate the number of bytes. + if (unit == null || unit.isEmpty()) { + return (long) quantity; + } else if (unit.equalsIgnoreCase("K")) { + return (long) (quantity * KB); + } else if (unit.equalsIgnoreCase("M")) { + return (long) (quantity * MB); + } else if (unit.equalsIgnoreCase("G")) { + return (long) (quantity * GB); + } else if (unit.equalsIgnoreCase("T")) { + return (long) (quantity * TB); } else { LOGGER.error("FileSize units not recognized: " + string); return defaultValue; } - } catch (final ParseException e) { - LOGGER.error("FileSize unable to parse numeric part: " + string, e); + + } catch (final ParseException error) { + LOGGER.error("FileSize unable to parse numeric part: " + string, error); return defaultValue; } } + + // Invalid input, bail out. LOGGER.error("FileSize unable to parse bytes: " + string); return defaultValue; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/NoOpTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/NoOpTriggeringPolicy.java new file mode 100644 index 00000000000..cc9d00558f9 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/NoOpTriggeringPolicy.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; + +/* + * Never triggers and is handy for edge-cases in tests for example. + * + * @since 2.11.1 + */ +@Configurable(printObject = true) +@Plugin +public class NoOpTriggeringPolicy extends AbstractTriggeringPolicy { + + public static final NoOpTriggeringPolicy INSTANCE = new NoOpTriggeringPolicy(); + + @PluginFactory + public static NoOpTriggeringPolicy createPolicy() { + return INSTANCE; + } + + @Override + public void initialize(final RollingFileManager manager) { + // NoOp + } + + @Override + public boolean isTriggeringEvent(final LogEvent logEvent) { + // Never triggers. + return false; + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicy.java index 4c8ae1c0912..54fc285168f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicy.java @@ -16,20 +16,21 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import java.lang.reflect.Method; - -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; +import java.lang.reflect.Method; + /** * Triggers a rollover on every restart, but only if the file size is greater than zero. */ -@Plugin(name = "OnStartupTriggeringPolicy", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public class OnStartupTriggeringPolicy extends AbstractTriggeringPolicy { private static final long JVM_START_TIME = initStartTime(); @@ -58,9 +59,8 @@ private static long initStartTime() { final Class runtimeMXBeanClass = Loader.loadSystemClass("java.lang.management.RuntimeMXBean"); final Method getStartTime = runtimeMXBeanClass.getMethod("getStartTime"); - final Long result = (Long) getStartTime.invoke(runtimeMXBean); - return result; + return (Long) getStartTime.invoke(runtimeMXBean); } catch (final Throwable t) { StatusLogger.getLogger().error("Unable to call ManagementFactory.getRuntimeMXBean().getStartTime(), " + "using system time for OnStartupTriggeringPolicy", t); @@ -76,6 +76,7 @@ private static long initStartTime() { @Override public void initialize(final RollingFileManager manager) { if (manager.getFileTime() < JVM_START_TIME && manager.getFileSize() >= minSize) { + StatusLogger.getLogger().debug("Initiating rollover at startup"); if (minSize == 0) { manager.setRenameEmptyFiles(true); } @@ -102,7 +103,7 @@ public String toString() { @PluginFactory public static OnStartupTriggeringPolicy createPolicy( - @PluginAttribute(value = "minSize", defaultLong = 1) final long minSize) { + @PluginAttribute(defaultLong = 1) final long minSize) { return new OnStartupTriggeringPolicy(minSize); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java index b5a5aa3a7bd..e508ece4659 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java @@ -16,23 +16,24 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.List; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.pattern.ArrayPatternConverter; import org.apache.logging.log4j.core.pattern.DatePatternConverter; +import org.apache.logging.log4j.core.pattern.FileDatePatternConverter; import org.apache.logging.log4j.core.pattern.FormattingInfo; import org.apache.logging.log4j.core.pattern.PatternConverter; import org.apache.logging.log4j.core.pattern.PatternParser; import org.apache.logging.log4j.status.StatusLogger; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + /** * Parses the rollover pattern. */ @@ -52,11 +53,14 @@ public class PatternProcessor { private final ArrayPatternConverter[] patternConverters; private final FormattingInfo[] patternFields; + private final FileExtension fileExtension; private long prevFileTime = 0; private long nextFileTime = 0; private long currentFileTime = 0; + private boolean isTimeBased = false; + private RolloverFrequency frequency = null; private final String pattern; @@ -77,18 +81,22 @@ public String toString() { public PatternProcessor(final String pattern) { this.pattern = pattern; final PatternParser parser = createPatternParser(); + // FIXME: this seems to expect List in practice; types need to be fixed around this final List converters = new ArrayList<>(); final List fields = new ArrayList<>(); parser.parse(pattern, converters, fields, false, false, false); - final FormattingInfo[] infoArray = new FormattingInfo[fields.size()]; - patternFields = fields.toArray(infoArray); + patternFields = fields.toArray(FormattingInfo.EMPTY_ARRAY); final ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()]; patternConverters = converters.toArray(converterArray); + this.fileExtension = FileExtension.lookupForFile(pattern); for (final ArrayPatternConverter converter : patternConverters) { + // TODO: extract common interface if (converter instanceof DatePatternConverter) { final DatePatternConverter dateConverter = (DatePatternConverter) converter; frequency = calculateFrequency(dateConverter.getPattern()); + } else if (converter instanceof FileDatePatternConverter) { + frequency = calculateFrequency(((FileDatePatternConverter) converter).getPattern()); } } } @@ -106,6 +114,18 @@ public PatternProcessor(final String pattern, final PatternProcessor copy) { this.currentFileTime = copy.currentFileTime; } + public FormattingInfo[] getPatternFields() { + return patternFields; + } + + public ArrayPatternConverter[] getPatternConverters() { + return patternConverters; + } + + public void setTimeBased(final boolean isTimeBased) { + this.isTimeBased = isTimeBased; + } + public long getCurrentFileTime() { return currentFileTime; } @@ -123,6 +143,10 @@ public void setPrevFileTime(final long prevFileTime) { this.prevFileTime = prevFileTime; } + public FileExtension getFileExtension() { + return fileExtension; + } + /** * Returns the next potential rollover time. * @param currentMillis The current time. @@ -136,10 +160,10 @@ public long getNextTime(final long currentMillis, final int increment, final boo // Call setMinimalDaysInFirstWeek(7); // prevFileTime = nextFileTime; - long nextTime; + final long nextTime; if (frequency == null) { - throw new IllegalStateException("Pattern does not contain a date"); + throw new IllegalStateException("Pattern '" + pattern + "' does not contain a date"); } final Calendar currentCal = Calendar.getInstance(); currentCal.setTimeInMillis(currentMillis); @@ -213,7 +237,10 @@ public long getNextTime(final long currentMillis, final int increment, final boo } public void updateTime() { - prevFileTime = nextFileTime; + if (nextFileTime != 0 || !isTimeBased) { + prevFileTime = nextFileTime; + currentFileTime = 0; + } } private long debugGetNextTime(final long nextTime) { @@ -266,7 +293,9 @@ public final void formatFileName(final StrSubstitutor subst, final StringBuilder final Object obj) { // LOG4J2-628: we deliberately use System time, not the log4j.Clock time // for creating the file name of rolled-over files. - final long time = useCurrentTime && currentFileTime != 0 ? currentFileTime : + LOGGER.debug("Formatting file name. useCurrentTime={}, currentFileTime={}, prevFileTime={}, nextFileTime={}", + useCurrentTime, currentFileTime, prevFileTime, nextFileTime); + final long time = useCurrentTime ? currentFileTime != 0 ? currentFileTime : System.currentTimeMillis() : prevFileTime != 0 ? prevFileTime : System.currentTimeMillis(); formatFileName(buf, new Date(time), obj); final LogEvent event = new Log4jLogEvent.Builder().setTimeMillis(time).build(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java index 6ccfe7b7586..f7d9dcc07bc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java @@ -22,8 +22,13 @@ import java.io.OutputStream; import java.io.Serializable; import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; import java.util.Collection; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadPoolExecutor; @@ -32,7 +37,6 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LifeCycle; -import org.apache.logging.log4j.core.LifeCycle2; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.ConfigurationFactoryData; @@ -50,9 +54,10 @@ */ public class RollingFileManager extends FileManager { - private static RollingFileManagerFactory factory = new RollingFileManagerFactory(); + private static final RollingFileManagerFactory factory = new RollingFileManagerFactory(); private static final int MAX_TRIES = 3; private static final int MIN_DURATION = 100; + private static final FileTime EPOCH = FileTime.fromMillis(0); protected long size; private long initialTime; @@ -61,10 +66,11 @@ public class RollingFileManager extends FileManager { private final Log4jThreadFactory threadFactory = Log4jThreadFactory.createThreadFactory("RollingFileManager"); private volatile TriggeringPolicy triggeringPolicy; private volatile RolloverStrategy rolloverStrategy; - private volatile boolean renameEmptyFiles = false; - private volatile boolean initialized = false; + private volatile boolean renameEmptyFiles; + private volatile boolean initialized; private volatile String fileName; - private final FileExtension fileExtension; + private final boolean directWrite; + private final CopyOnWriteArrayList rolloverListeners = new CopyOnWriteArrayList<>(); /* This executor pool will create a new Thread for every work async action to be performed. Using it allows us to make sure all the Threads are completed when the Manager is stopped. */ @@ -80,66 +86,25 @@ public class RollingFileManager extends FileManager { private static final AtomicReferenceFieldUpdater patternProcessorUpdater = AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, PatternProcessor.class, "patternProcessor"); - @Deprecated - protected RollingFileManager(final String fileName, final String pattern, final OutputStream os, - final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy, - final RolloverStrategy rolloverStrategy, final String advertiseURI, - final Layout layout, final int bufferSize, final boolean writeHeader) { - this(fileName, pattern, os, append, size, time, triggeringPolicy, rolloverStrategy, advertiseURI, layout, - writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE])); - } - - @Deprecated - protected RollingFileManager(final String fileName, final String pattern, final OutputStream os, - final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy, - final RolloverStrategy rolloverStrategy, final String advertiseURI, - final Layout layout, final boolean writeHeader, final ByteBuffer buffer) { - super(fileName, os, append, false, advertiseURI, layout, writeHeader, buffer); - this.size = size; - this.initialTime = time; - this.triggeringPolicy = triggeringPolicy; - this.rolloverStrategy = rolloverStrategy; - this.patternProcessor = new PatternProcessor(pattern); - this.patternProcessor.setPrevFileTime(time); - this.fileName = fileName; - this.fileExtension = FileExtension.lookupForFile(pattern); - } - - @Deprecated - protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os, - final boolean append, final boolean createOnDemand, final long size, final long time, - final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy, - final String advertiseURI, final Layout layout, final boolean writeHeader, final ByteBuffer buffer) { - super(loggerContext, fileName, os, append, false, createOnDemand, advertiseURI, layout, writeHeader, buffer); - this.size = size; - this.initialTime = time; - this.triggeringPolicy = triggeringPolicy; - this.rolloverStrategy = rolloverStrategy; - this.patternProcessor = new PatternProcessor(pattern); - this.patternProcessor.setPrevFileTime(time); - this.fileName = fileName; - this.fileExtension = FileExtension.lookupForFile(pattern); - } - /** * @since 2.9 */ protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os, - final boolean append, final boolean createOnDemand, final long size, final long time, + final boolean append, final boolean createOnDemand, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy, final String advertiseURI, final Layout layout, final String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader, final ByteBuffer buffer) { - super(loggerContext, fileName, os, append, false, createOnDemand, advertiseURI, layout, - filePermissions, fileOwner, fileGroup, writeHeader, buffer); + super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand, + advertiseURI, layout, filePermissions, fileOwner, fileGroup, writeHeader, buffer); this.size = size; - this.initialTime = time; + this.initialTime = initialTime; this.triggeringPolicy = triggeringPolicy; this.rolloverStrategy = rolloverStrategy; this.patternProcessor = new PatternProcessor(pattern); - this.patternProcessor.setPrevFileTime(time); + this.patternProcessor.setPrevFileTime(initialTime); this.fileName = fileName; - this.fileExtension = FileExtension.lookupForFile(pattern); + this.directWrite = rolloverStrategy instanceof DirectFileRolloverStrategy; } public void initialize() { @@ -147,6 +112,16 @@ public void initialize() { if (!initialized) { LOGGER.debug("Initializing triggering policy {}", triggeringPolicy); initialized = true; + // LOG4J2-2981 - set the file size before initializing the triggering policy. + if (directWrite) { + // LOG4J2-2485: Initialize size from the most recently written file. + final File file = new File(getFileName()); + if (file.exists()) { + size = file.length(); + } else { + ((DirectFileRolloverStrategy) rolloverStrategy).clearCurrentFileName(); + } + } triggeringPolicy.initialize(this); if (triggeringPolicy instanceof LifeCycle) { ((LifeCycle) triggeringPolicy).start(); @@ -190,20 +165,47 @@ public static RollingFileManager getFileManager(final String fileName, final Str filePermissions, fileOwner, fileGroup, configuration), factory)); } + /** + * Add a RolloverListener. + * @param listener The RolloverListener. + */ + public void addRolloverListener(final RolloverListener listener) { + rolloverListeners.add(listener); + } + + /** + * Remove a RolloverListener. + * @param listener The RolloverListener. + */ + public void removeRolloverListener(final RolloverListener listener) { + rolloverListeners.remove(listener); + } + /** * Returns the name of the File being managed. * @return The name of the File being managed. */ @Override public String getFileName() { - if (rolloverStrategy instanceof DirectFileRolloverStrategy) { + if (directWrite) { fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this); } return fileName; } + @Override + protected void createParentDir(final File file) { + if (directWrite) { + file.getParentFile().mkdirs(); + } + } + + public boolean isDirectWrite() { + return directWrite; + } + public FileExtension getFileExtension() { - return fileExtension; + return patternProcessor.getFileExtension(); } // override to make visible for unit tests @@ -255,14 +257,9 @@ public synchronized void checkRollover(final LogEvent event) { @Override public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { - LOGGER.debug("Shutting down RollingFileManager {}" + getName()); + LOGGER.debug("Shutting down RollingFileManager {}", getName()); boolean stopped = true; - if (triggeringPolicy instanceof LifeCycle2) { - stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit); - } else if (triggeringPolicy instanceof LifeCycle) { - ((LifeCycle) triggeringPolicy).stop(); - stopped &= true; - } + stopped &= ((LifeCycle) triggeringPolicy).stop(timeout, timeUnit); final boolean status = super.releaseSub(timeout, timeUnit) && stopped; asyncExecutor.shutdown(); try { @@ -305,17 +302,57 @@ public boolean releaseSub(final long timeout, final TimeUnit timeUnit) { return status; } + public synchronized void rollover(final long prevFileTime, final long prevRollTime) { + LOGGER.debug("Rollover PrevFileTime: {}, PrevRollTime: {}", prevFileTime, prevRollTime); + getPatternProcessor().setPrevFileTime(prevFileTime); + getPatternProcessor().setCurrentFileTime(prevRollTime); + rollover(); + } + public synchronized void rollover() { - if (!hasOutputStream()) { + if (!hasOutputStream() && !isCreateOnDemand() && !isDirectWrite()) { return; } - if (rollover(rolloverStrategy)) { - try { - size = 0; - initialTime = System.currentTimeMillis(); - createFileAfterRollover(); - } catch (final IOException e) { - logError("Failed to create file after rollover", e); + final String currentFileName = fileName; + if (rolloverListeners.size() > 0) { + for (final RolloverListener listener : rolloverListeners) { + try { + listener.rolloverTriggered(currentFileName); + } catch (final Exception ex) { + LOGGER.warn("Rollover Listener {} failed with {}: {}", listener.getClass().getSimpleName(), + ex.getClass().getName(), ex.getMessage()); + } + } + } + + boolean interrupted = Thread.interrupted(); // clear interrupted state + try { + if (interrupted) { + LOGGER.warn("RollingFileManager cleared thread interrupted state, continue to rollover"); + } + + if (rollover(rolloverStrategy)) { + try { + size = 0; + initialTime = System.currentTimeMillis(); + createFileAfterRollover(); + } catch (final IOException e) { + logError("Failed to create file after rollover", e); + } + } + } finally { + if (interrupted) { // restore interrupted state + Thread.currentThread().interrupt(); + } + } + if (rolloverListeners.size() > 0) { + for (final RolloverListener listener : rolloverListeners) { + try { + listener.rolloverComplete(currentFileName); + } catch (final Exception ex) { + LOGGER.warn("Rollover Listener {} failed with {}: {}", listener.getClass().getSimpleName(), + ex.getClass().getName(), ex.getMessage()); + } } } } @@ -594,12 +631,18 @@ public String toString() { } } + /** + * Updates the RollingFileManager's data during a reconfiguration. This method should be considered private. + * It is not thread safe and calling it outside of a reconfiguration may lead to errors. This method may be + * made protected in a future release. + * @param data The data to update. + */ @Override public void updateData(final Object data) { final FactoryData factoryData = (FactoryData) data; setRolloverStrategy(factoryData.getRolloverStrategy()); - setTriggeringPolicy(factoryData.getTriggeringPolicy()); setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor())); + setTriggeringPolicy(factoryData.getTriggeringPolicy()); } /** @@ -616,16 +659,13 @@ private static class RollingFileManagerFactory implements ManagerFactory 0) { + return fileTime.toMillis(); + } + LOGGER.info("Unable to obtain file creation time for " + file.getAbsolutePath()); + } catch (final Exception ex) { + LOGGER.info("Unable to calculate file creation time for " + file.getAbsolutePath() + ": " + ex.getMessage()); + } + } + return file.lastModified(); + } + private static class EmptyQueue extends ArrayBlockingQueue { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java index 0d60483495a..9be7e44d9da 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java @@ -22,6 +22,7 @@ import java.io.RandomAccessFile; import java.io.Serializable; import java.nio.ByteBuffer; +import java.nio.file.Paths; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LoggerContext; @@ -45,40 +46,28 @@ public class RollingRandomAccessFileManager extends RollingFileManager { private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory(); private RandomAccessFile randomAccessFile; - private final ThreadLocal isEndOfBatch = new ThreadLocal<>(); - - @Deprecated - public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf, - final String fileName, final String pattern, final OutputStream os, final boolean append, - final boolean immediateFlush, final int bufferSize, final long size, final long time, - final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, - final Layout layout, final boolean writeHeader) { - this(loggerContext, raf, fileName, pattern, os, append, immediateFlush, bufferSize, size, time, policy, strategy, advertiseURI, - layout, null, null, null, writeHeader); - } /** * @since 2.8.3 */ public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf, final String fileName, final String pattern, final OutputStream os, final boolean append, - final boolean immediateFlush, final int bufferSize, final long size, final long time, + final boolean immediateFlush, final int bufferSize, final long initialTime, final long time, final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, final Layout layout, final String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader) { - super(loggerContext, fileName, pattern, os, append, false, size, time, policy, strategy, advertiseURI, layout, + super(loggerContext, fileName, pattern, os, append, false, initialTime, time, policy, strategy, advertiseURI, layout, filePermissions, fileOwner, fileGroup, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); this.randomAccessFile = raf; - isEndOfBatch.set(Boolean.FALSE); writeHeader(); } /** * Writes the layout's header to the file if it exists. */ - private void writeHeader() { + protected void writeHeader() { if (layout == null) { return; } @@ -87,7 +76,7 @@ private void writeHeader() { return; } try { - if (randomAccessFile.length() == 0) { + if (randomAccessFile != null && randomAccessFile.length() == 0) { // write to the file, not to the buffer: the buffer may not be empty randomAccessFile.write(header, 0, header.length); } @@ -111,12 +100,23 @@ public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(f filePermissions, fileOwner, fileGroup, configuration), FACTORY)); } + /** + * No longer used, the {@link org.apache.logging.log4j.core.LogEvent#isEndOfBatch()} attribute is used instead. + * @return {@link Boolean#FALSE}. + * @deprecated end-of-batch on the event is used instead. + */ + @Deprecated public Boolean isEndOfBatch() { - return isEndOfBatch.get(); + return Boolean.FALSE; } - public void setEndOfBatch(final boolean endOfBatch) { - this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); + /** + * No longer used, the {@link org.apache.logging.log4j.core.LogEvent#isEndOfBatch()} attribute is used instead. + * This method is a no-op. + * @deprecated end-of-batch on the event is used instead. + */ + @Deprecated + public void setEndOfBatch(@SuppressWarnings("unused") final boolean endOfBatch) { } // override to make visible for unit tests @@ -130,8 +130,8 @@ protected synchronized void write(final byte[] bytes, final int offset, final in protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { try { if (randomAccessFile == null) { - String fileName = getFileName(); - File file = new File(fileName); + final String fileName = getFileName(); + final File file = new File(fileName); FileUtils.makeParentDirs(file); createFileAfterRollover(fileName); } @@ -148,8 +148,11 @@ protected void createFileAfterRollover() throws IOException { createFileAfterRollover(getFileName()); } - private void createFileAfterRollover(String fileName) throws IOException { + private void createFileAfterRollover(final String fileName) throws IOException { this.randomAccessFile = new RandomAccessFile(fileName, "rw"); + if (isAttributeViewEnabled()) { + defineAttributeView(Paths.get(fileName)); + } if (isAppend()) { randomAccessFile.seek(randomAccessFile.length()); } @@ -164,13 +167,16 @@ public synchronized void flush() { @Override public synchronized boolean closeOutputStream() { flush(); - try { - randomAccessFile.close(); - return true; - } catch (final IOException e) { - logError("Unable to close RandomAccessFile", e); - return false; + if (randomAccessFile != null) { + try { + randomAccessFile.close(); + return true; + } catch (final IOException e) { + logError("Unable to close RandomAccessFile", e); + return false; + } } + return true; } /** @@ -200,7 +206,7 @@ private static class RollingRandomAccessFileManagerFactory implements public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { File file = null; long size = 0; - long time = System.currentTimeMillis(); + long initialTime = System.currentTimeMillis(); RandomAccessFile raf = null; if (data.fileName != null) { file = new File(name); @@ -210,7 +216,7 @@ public RollingRandomAccessFileManager createManager(final String name, final Fac } size = data.append ? file.length() : 0; if (file.exists()) { - time = file.lastModified(); + initialTime = file.lastModified(); } try { FileUtils.makeParentDirs(file); @@ -238,7 +244,7 @@ public RollingRandomAccessFileManager createManager(final String name, final Fac final boolean writeHeader = !data.append || file == null || !file.exists(); final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern, - NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, + NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, initialTime, data.policy, data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader); if (rrm.isAttributeViewEnabled()) { rrm.defineAttributeView(file.toPath()); @@ -301,21 +307,31 @@ public FactoryData(final String fileName, final String pattern, final boolean ap this.fileGroup = fileGroup; } - public TriggeringPolicy getTriggeringPolicy() - { + public String getPattern() { + return pattern; + } + + public TriggeringPolicy getTriggeringPolicy() { return this.policy; } - public RolloverStrategy getRolloverStrategy() - { + public RolloverStrategy getRolloverStrategy() { return this.strategy; } + } + /** + * Updates the RollingFileManager's data during a reconfiguration. This method should be considered private. + * It is not thread safe and calling it outside of a reconfiguration may lead to errors. This method may be + * made protected in a future release. + * @param data The data to update. + */ @Override public void updateData(final Object data) { final FactoryData factoryData = (FactoryData) data; setRolloverStrategy(factoryData.getRolloverStrategy()); + setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor())); setTriggeringPolicy(factoryData.getTriggeringPolicy()); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverListener.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverListener.java new file mode 100644 index 00000000000..de3a2987f3e --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverListener.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.appender.rolling; + +/** + * Implementations of this interface that are registered with the RollingFileManager will be notified before and + * after a rollover occurs. This is a synchronous call so Listeners should exit the methods as fast as possible. + * It is recommended that they simply notify some other already active thread. + */ +public interface RolloverListener { + + /** + * Called before rollover. + * @param fileName The name of the file rolling over. + */ + void rolloverTriggered(String fileName); + + /** + * Called after rollover. + * @param fileName The name of the file rolling over. + */ + void rolloverComplete(String fileName); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/SizeBasedTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/SizeBasedTriggeringPolicy.java index f77d5714ad2..1e47a5dbb8d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/SizeBasedTriggeringPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/SizeBasedTriggeringPolicy.java @@ -16,16 +16,17 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; /** * */ -@Plugin(name = "SizeBasedTriggeringPolicy", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public class SizeBasedTriggeringPolicy extends AbstractTriggeringPolicy { /** @@ -92,7 +93,7 @@ public String toString() { * @return A SizeBasedTriggeringPolicy. */ @PluginFactory - public static SizeBasedTriggeringPolicy createPolicy(@PluginAttribute("size") final String size) { + public static SizeBasedTriggeringPolicy createPolicy(@PluginAttribute final String size) { final long maxSize = size == null ? MAX_FILE_SIZE : FileSize.parse(size, MAX_FILE_SIZE); return new SizeBasedTriggeringPolicy(maxSize); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java index 7f6ac79d7ed..b73136e269a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java @@ -16,39 +16,42 @@ */ package org.apache.logging.log4j.core.appender.rolling; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.util.Integers; - /** * Rolls a file over based on time. */ -@Plugin(name = "TimeBasedTriggeringPolicy", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class TimeBasedTriggeringPolicy extends AbstractTriggeringPolicy { - - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute private int interval = 1; - + @PluginBuilderAttribute private boolean modulate = false; - + @PluginBuilderAttribute private int maxRandomDelay = 0; - + + private Clock clock; + @Override public TimeBasedTriggeringPolicy build() { final long maxRandomDelayMillis = TimeUnit.SECONDS.toMillis(maxRandomDelay); - return new TimeBasedTriggeringPolicy(interval, modulate, maxRandomDelayMillis); + return new TimeBasedTriggeringPolicy(interval, modulate, maxRandomDelayMillis, clock); } public int getInterval() { @@ -62,35 +65,47 @@ public boolean isModulate() { public int getMaxRandomDelay() { return maxRandomDelay; } - - public Builder withInterval(final int interval){ + + public Clock getClock() { + return clock; + } + + public Builder setInterval(final int interval){ this.interval = interval; return this; } - - public Builder withModulate(final boolean modulate){ + + public Builder setModulate(final boolean modulate){ this.modulate = modulate; return this; } - - public Builder withMaxRandomDelay(final int maxRandomDelay){ + + public Builder setMaxRandomDelay(final int maxRandomDelay){ this.maxRandomDelay = maxRandomDelay; return this; } + @Inject + public Builder setClock(final Clock clock) { + this.clock = clock; + return this; + } } private long nextRolloverMillis; private final int interval; private final boolean modulate; private final long maxRandomDelayMillis; + private final Clock clock; private RollingFileManager manager; - private TimeBasedTriggeringPolicy(final int interval, final boolean modulate, final long maxRandomDelayMillis) { + private TimeBasedTriggeringPolicy( + final int interval, final boolean modulate, final long maxRandomDelayMillis, final Clock clock) { this.interval = interval; this.modulate = modulate; this.maxRandomDelayMillis = maxRandomDelayMillis; + this.clock = clock; } public int getInterval() { @@ -108,12 +123,17 @@ public long getNextRolloverMillis() { @Override public void initialize(final RollingFileManager aManager) { this.manager = aManager; - + long current = aManager.getFileTime(); + if (current == 0) { + current = clock.currentTimeMillis(); + } + // LOG4J2-531: call getNextTime twice to force initialization of both prevFileTime and nextFileTime - aManager.getPatternProcessor().getNextTime(aManager.getFileTime(), interval, modulate); - + aManager.getPatternProcessor().getNextTime(current, interval, modulate); + aManager.getPatternProcessor().setTimeBased(true); + nextRolloverMillis = ThreadLocalRandom.current().nextLong(0, 1 + maxRandomDelayMillis) - + aManager.getPatternProcessor().getNextTime(aManager.getFileTime(), interval, modulate); + + aManager.getPatternProcessor().getNextTime(current, interval, modulate); } /** @@ -123,36 +143,17 @@ public void initialize(final RollingFileManager aManager) { */ @Override public boolean isTriggeringEvent(final LogEvent event) { - if (manager.getFileSize() == 0) { - return false; - } final long nowMillis = event.getTimeMillis(); if (nowMillis >= nextRolloverMillis) { nextRolloverMillis = ThreadLocalRandom.current().nextLong(0, 1 + maxRandomDelayMillis) + manager.getPatternProcessor().getNextTime(nowMillis, interval, modulate); + manager.getPatternProcessor().setCurrentFileTime(clock.currentTimeMillis()); return true; } return false; } - /** - * Creates a TimeBasedTriggeringPolicy. - * @param interval The interval between rollovers. - * @param modulate If true the time will be rounded to occur on a boundary aligned with the increment. - * @return a TimeBasedTriggeringPolicy. - * @deprecated Use {@link #newBuilder()}. - */ - @Deprecated - public static TimeBasedTriggeringPolicy createPolicy( - @PluginAttribute("interval") final String interval, - @PluginAttribute("modulate") final String modulate) { - return newBuilder() - .withInterval(Integers.parseInt(interval, 1)) - .withModulate(Boolean.parseBoolean(modulate)) - .build(); - } - - @PluginBuilderFactory + @PluginFactory public static TimeBasedTriggeringPolicy.Builder newBuilder() { return new Builder(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TriggeringPolicy.java index ed6a3f1b649..bb68d907649 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TriggeringPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TriggeringPolicy.java @@ -22,7 +22,7 @@ * A TriggeringPolicy controls the conditions under which rollover * occurs. Such conditions include time of day, file size, an * external event, the log request or a combination thereof. - * + * * @see AbstractTriggeringPolicy */ public interface TriggeringPolicy /* TODO 3.0: extends LifeCycle */ { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractAction.java index 633d4bc2bad..c18163afd65 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractAction.java @@ -26,7 +26,7 @@ * Abstract base class for implementations of Action. */ public abstract class AbstractAction implements Action { - + /** * Allows subclasses access to the status logger without creating another instance. */ @@ -64,8 +64,12 @@ public synchronized void run() { if (!interrupted) { try { execute(); - } catch (final IOException ex) { + } catch (final RuntimeException | IOException ex) { reportException(ex); + } catch (final Error e) { + // reportException takes Exception, widening to Throwable would break custom implementations + // so we wrap Errors in RuntimeException for handling. + reportException(new RuntimeException(e)); } complete = true; @@ -101,6 +105,7 @@ public boolean isInterrupted() { * @param ex exception. */ protected void reportException(final Exception ex) { + LOGGER.warn("Exception reported by action '{}'", getClass(), ex); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java index f8ddf486a53..191ec0573ff 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractPathAction.java @@ -45,7 +45,7 @@ public abstract class AbstractPathAction extends AbstractAction { /** * Creates a new AbstractPathAction that starts scanning for files to process from the specified base path. - * + * * @param basePath base path from where to start scanning for files to process. * @param followSymbolicLinks whether to follow symbolic links. Default is false. * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0 @@ -57,7 +57,7 @@ public abstract class AbstractPathAction extends AbstractAction { protected AbstractPathAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, final PathCondition[] pathFilters, final StrSubstitutor subst) { this.basePathString = basePath; - this.options = followSymbolicLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS) + this.options = followSymbolicLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS) : Collections. emptySet(); this.maxDepth = maxDepth; this.pathConditions = Arrays.asList(Arrays.copyOf(pathFilters, pathFilters.length)); @@ -87,7 +87,7 @@ public boolean execute(final FileVisitor visitor) throws IOException { * method when the {@link #execute()} method is invoked. *

    * The visitor is responsible for processing the files it encounters that are accepted by all filters. - * + * * @param visitorBaseDir base dir from where to start scanning for files to process * @param conditions filters that determine if a file should be processed * @return a new {@code FileVisitor} @@ -99,7 +99,7 @@ protected abstract FileVisitor createFileVisitor(final Path visitorBaseDir * Returns the base path from where to start scanning for files to delete. Lookups are resolved, so if the * configuration was <Delete basePath="${sys:user.home}/abc" /> then this method returns a path * to the "abc" file or directory in the user's home directory. - * + * * @return the base path (all lookups resolved) */ public Path getBasePath() { @@ -108,7 +108,7 @@ public Path getBasePath() { /** * Returns the base path as it was specified in the configuration. Lookups are not resolved. - * + * * @return the base path as it was specified in the configuration */ public String getBasePathString() { @@ -121,16 +121,16 @@ public StrSubstitutor getStrSubstitutor() { /** * Returns whether to follow symbolic links or not. - * + * * @return the options */ public Set getOptions() { return Collections.unmodifiableSet(options); } - + /** * Returns whether to follow symbolic links or not. - * + * * @return whether to follow symbolic links or not */ public boolean isFollowSymbolicLinks() { @@ -138,8 +138,8 @@ public boolean isFollowSymbolicLinks() { } /** - * Returns the the maximum number of directory levels to visit. - * + * Returns the maximum number of directory levels to visit. + * * @return the maxDepth */ public int getMaxDepth() { @@ -148,7 +148,7 @@ public int getMaxDepth() { /** * Returns the list of PathCondition objects. - * + * * @return the pathFilters */ public List getPathConditions() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CommonsCompressAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CommonsCompressAction.java index 6d0c6e292b5..e461a8c5a6d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CommonsCompressAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CommonsCompressAction.java @@ -140,7 +140,7 @@ protected void reportException(final Exception ex) { @Override public String toString() { - return CommonsCompressAction.class.getSimpleName() + '[' + source + " to " + destination + return CommonsCompressAction.class.getSimpleName() + '[' + source + " to " + destination + ", deleteSource=" + deleteSource + ']'; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CompositeAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CompositeAction.java index 56371443656..8d5a48b64c7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CompositeAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/CompositeAction.java @@ -25,7 +25,7 @@ * A group of Actions to be executed in sequence. */ public class CompositeAction extends AbstractAction { - + /** * Actions to perform. */ @@ -99,7 +99,7 @@ public boolean execute() throws IOException { return status; } - + @Override public String toString() { return CompositeAction.class.getSimpleName() + Arrays.toString(actions); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java index 76e9b003a7c..d27cd3e2dd0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java @@ -17,6 +17,16 @@ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.script.ScriptConditional; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.io.IOException; import java.nio.file.FileVisitor; import java.nio.file.Files; @@ -24,28 +34,20 @@ import java.util.List; import java.util.Objects; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; - /** * Rollover or scheduled action for deleting old log files that are accepted by the specified PathFilters. */ -@Plugin(name = "Delete", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("Delete") public class DeleteAction extends AbstractPathAction { private final PathSorter pathSorter; private final boolean testMode; - private final ScriptCondition scriptCondition; + private final ScriptConditional scriptCondition; /** * Creates a new DeleteAction that starts scanning for files to delete from the specified base path. - * + * * @param basePath base path from where to start scanning for files to delete. * @param followSymbolicLinks whether to follow symbolic links. Default is false. * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0 @@ -60,7 +62,7 @@ public class DeleteAction extends AbstractPathAction { * @param scriptCondition */ DeleteAction(final String basePath, final boolean followSymbolicLinks, final int maxDepth, final boolean testMode, - final PathSorter sorter, final PathCondition[] pathConditions, final ScriptCondition scriptCondition, + final PathSorter sorter, final PathCondition[] pathConditions, final ScriptConditional scriptCondition, final StrSubstitutor subst) { super(basePath, followSymbolicLinks, maxDepth, pathConditions, subst); this.testMode = testMode; @@ -74,7 +76,7 @@ public class DeleteAction extends AbstractPathAction { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute() */ @Override @@ -95,8 +97,7 @@ private boolean executeScript() throws IOException { private List callScript() throws IOException { final List sortedPaths = getSortedPaths(); trace("Sorted paths:", sortedPaths); - final List result = scriptCondition.selectFilesToDelete(getBasePath(), sortedPaths); - return result; + return scriptCondition.selectFilesToDelete(getBasePath(), sortedPaths); } private void deleteSelectedFiles(final List selectedForDeletion) throws IOException { @@ -113,7 +114,7 @@ private void deleteSelectedFiles(final List selectedForDelet /** * Deletes the specified file. - * + * * @param path the file to delete * @throws IOException if a problem occurred deleting the file */ @@ -124,7 +125,7 @@ protected void delete(final Path path) throws IOException { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute(FileVisitor) */ @Override @@ -153,20 +154,19 @@ private void trace(final String label, final List sortedPath /** * Returns a sorted list of all files up to maxDepth under the basePath. - * + * * @return a sorted list of files * @throws IOException */ List getSortedPaths() throws IOException { final SortingVisitor sort = new SortingVisitor(pathSorter); super.execute(sort); - final List sortedPaths = sort.getSortedPaths(); - return sortedPaths; + return sort.getSortedPaths(); } /** * Returns {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise. - * + * * @return {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise */ public boolean isTestMode() { @@ -180,7 +180,7 @@ protected FileVisitor createFileVisitor(final Path visitorBaseDir, final L /** * Create a DeleteAction. - * + * * @param basePath base path from where to start scanning for files to delete. * @param followLinks whether to follow symbolic links. Default is false. * @param maxDepth The maxDepth parameter is the maximum number of levels of directories to visit. A value of 0 @@ -199,13 +199,13 @@ protected FileVisitor createFileVisitor(final Path visitorBaseDir, final L @PluginFactory public static DeleteAction createDeleteAction( // @formatter:off - @PluginAttribute("basePath") final String basePath, - @PluginAttribute(value = "followLinks") final boolean followLinks, - @PluginAttribute(value = "maxDepth", defaultInt = 1) final int maxDepth, - @PluginAttribute(value = "testMode") final boolean testMode, - @PluginElement("PathSorter") final PathSorter sorterParameter, - @PluginElement("PathConditions") final PathCondition[] pathConditions, - @PluginElement("ScriptCondition") final ScriptCondition scriptCondition, + @PluginAttribute final String basePath, + @PluginAttribute final boolean followLinks, + @PluginAttribute(defaultInt = 1) final int maxDepth, + @PluginAttribute final boolean testMode, + @PluginElement final PathSorter sorterParameter, + @PluginElement final PathCondition[] pathConditions, + @PluginElement final ScriptConditional scriptCondition, @PluginConfiguration final Configuration config) { // @formatter:on final PathSorter sorter = sorterParameter == null ? new PathSortByModificationTime(true) : sorterParameter; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java index 825e7741b53..69a59a7d8cd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; @@ -41,7 +42,7 @@ public class DeletingVisitor extends SimpleFileVisitor { /** * Constructs a new DeletingVisitor. - * + * * @param basePath used to relativize paths * @param pathConditions objects that need to confirm whether a file can be deleted * @param testMode if true, files are not deleted but instead a message is printed to the * Similarly to the {@code java.time.Duration} class, this class does not support year or month sections in the format. * This implementation does not support fractions or negative values. - * + * * @see #parse(CharSequence) */ public class Duration implements Serializable, Comparable { @@ -101,7 +101,7 @@ private Duration(final long seconds) { * positive symbol. The number of days, hours, minutes and seconds must parse to a {@code long}. *

    * Examples: - * + * *

          *    "PT20S" -- parses as "20 seconds"
          *    "PT15M"     -- parses as "15 minutes" (where a minute is 60 seconds)
    @@ -205,14 +205,14 @@ public int hashCode() {
          * all positive.
          * 

    * Examples: - * + * *

          *    "20 seconds"                     -- "PT20S
          *    "15 minutes" (15 * 60 seconds)   -- "PT15M"
          *    "10 hours" (10 * 3600 seconds)   -- "PT10H"
          *    "2 days" (2 * 86400 seconds)     -- "P2D"
          * 
    - * + * * @return an ISO-8601 representation of this duration, not null */ @Override @@ -247,7 +247,7 @@ public String toString() { /* * (non-Javadoc) - * + * * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameAction.java index 25383065f09..2da33aa2aa1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameAction.java @@ -19,7 +19,9 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; @@ -116,14 +118,11 @@ public static boolean execute(final File source, final File destination, final b } try { try { - Files.move(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()), - StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); - LOGGER.trace("Renamed file {} to {} with Files.move", source.getAbsolutePath(), - destination.getAbsolutePath()); - return true; + return moveFile(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath())); } catch (final IOException exMove) { - LOGGER.error("Unable to move file {} to {}: {} {}", source.getAbsolutePath(), - destination.getAbsolutePath(), exMove.getClass().getName(), exMove.getMessage()); + LOGGER.debug("Unable to move file {} to {}: {} {} - will try to copy and delete", + source.getAbsolutePath(), destination.getAbsolutePath(), exMove.getClass().getName(), + exMove.getMessage()); boolean result = source.renameTo(destination); if (!result) { try { @@ -139,6 +138,7 @@ public static boolean execute(final File source, final File destination, final b exDelete.getClass().getName(), exDelete.getMessage()); try { new PrintWriter(source.getAbsolutePath()).close(); + result = true; LOGGER.trace("Renamed file {} to {} with copy and truncation", source.getAbsolutePath(), destination.getAbsolutePath()); } catch (final IOException exOwerwrite) { @@ -173,6 +173,21 @@ public static boolean execute(final File source, final File destination, final b return false; } + private static boolean moveFile(final Path source, final Path target) throws IOException { + try { + Files.move(source, target, + StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); + LOGGER.trace("Renamed file {} to {} with Files.move", source.toFile().getAbsolutePath(), + target.toFile().getAbsolutePath()); + return true; + } catch (final AtomicMoveNotSupportedException ex) { + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); + LOGGER.trace("Renamed file {} to {} with Files.move", source.toFile().getAbsolutePath(), + target.toFile().getAbsolutePath()); + return true; + } + } + @Override public String toString() { return FileRenameAction.class.getSimpleName() + '[' + source + " to " + destination diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/GzCompressAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/GzCompressAction.java index 1c9b08206a8..a196a6c2299 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/GzCompressAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/GzCompressAction.java @@ -21,7 +21,9 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Objects; +import java.util.zip.Deflater; import java.util.zip.GZIPOutputStream; /** @@ -46,6 +48,13 @@ public final class GzCompressAction extends AbstractAction { */ private final boolean deleteSource; + /** + * GZIP compression level to use. + * + * @see Deflater#setLevel(int) + */ + private final int compressionLevel; + /** * Create new instance of GzCompressAction. * @@ -53,14 +62,28 @@ public final class GzCompressAction extends AbstractAction { * @param destination compressed file, may not be null. * @param deleteSource if true, attempt to delete file on completion. Failure to delete * does not cause an exception to be thrown or affect return value. + * @param compressionLevel + * Gzip deflater compression level. */ - public GzCompressAction(final File source, final File destination, final boolean deleteSource) { + public GzCompressAction( + final File source, final File destination, final boolean deleteSource, final int compressionLevel) { Objects.requireNonNull(source, "source"); Objects.requireNonNull(destination, "destination"); this.source = source; this.destination = destination; this.deleteSource = deleteSource; + this.compressionLevel = compressionLevel; + } + + /** + * Prefer the constructor with compression level. + * + * @deprecated Prefer {@link GzCompressAction#GzCompressAction(File, File, boolean, int)}. + */ + @Deprecated + public GzCompressAction(final File source, final File destination, final boolean deleteSource) { + this(source, destination, deleteSource, Deflater.DEFAULT_COMPRESSION); } /** @@ -71,7 +94,7 @@ public GzCompressAction(final File source, final File destination, final boolean */ @Override public boolean execute() throws IOException { - return execute(source, destination, deleteSource); + return execute(source, destination, deleteSource, compressionLevel); } /** @@ -83,13 +106,38 @@ public boolean execute() throws IOException { * does not cause an exception to be thrown or affect return value. * @return true if source file compressed. * @throws IOException on IO exception. + * @deprecated In favor of {@link #execute(File, File, boolean, int)}. */ + @Deprecated public static boolean execute(final File source, final File destination, final boolean deleteSource) throws IOException { + return execute(source, destination, deleteSource, Deflater.DEFAULT_COMPRESSION); + } + + /** + * Compress a file. + * + * @param source file to compress, may not be null. + * @param destination compressed file, may not be null. + * @param deleteSource if true, attempt to delete file on completion. Failure to delete + * does not cause an exception to be thrown or affect return value. + * @param compressionLevel + * Gzip deflater compression level. + * @return true if source file compressed. + * @throws IOException on IO exception. + */ + public static boolean execute( + final File source, + final File destination, + final boolean deleteSource, + final int compressionLevel) throws IOException { if (source.exists()) { try (final FileInputStream fis = new FileInputStream(source); - final BufferedOutputStream os = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream( - destination)))) { + final OutputStream fos = new FileOutputStream(destination); + final OutputStream gzipOut = new ConfigurableLevelGZIPOutputStream( + fos, BUF_SIZE, compressionLevel); + // Reduce native invocations by buffering data into GZIPOutputStream + final OutputStream os = new BufferedOutputStream(gzipOut, BUF_SIZE)) { final byte[] inbuf = new byte[BUF_SIZE]; int n; @@ -99,7 +147,7 @@ public static boolean execute(final File source, final File destination, final b } if (deleteSource && !source.delete()) { - LOGGER.warn("Unable to delete " + source.toString() + '.'); + LOGGER.warn("Unable to delete {}.", source); } return true; @@ -108,6 +156,13 @@ public static boolean execute(final File source, final File destination, final b return false; } + private static final class ConfigurableLevelGZIPOutputStream extends GZIPOutputStream { + + ConfigurableLevelGZIPOutputStream(final OutputStream out, final int bufSize, final int level) throws IOException { + super(out, bufSize); + def.setLevel(level); + } + } /** * Capture exception. @@ -121,7 +176,7 @@ protected void reportException(final Exception ex) { @Override public String toString() { - return GzCompressAction.class.getSimpleName() + '[' + source + " to " + destination + return GzCompressAction.class.getSimpleName() + '[' + source + " to " + destination + ", deleteSource=" + deleteSource + ']'; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCount.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCount.java index b47954fa192..821f1a2c322 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCount.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileCount.java @@ -16,37 +16,37 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; + import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.status.StatusLogger; - /** * PathCondition that accepts paths after some count threshold is exceeded during the file tree walk. */ -@Plugin(name = "IfAccumulatedFileCount", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class IfAccumulatedFileCount implements PathCondition { private static final Logger LOGGER = StatusLogger.getLogger(); private final int threshold; private int count; private final PathCondition[] nestedConditions; - private IfAccumulatedFileCount(final int thresholdParam, final PathCondition[] nestedConditions) { + private IfAccumulatedFileCount(final int thresholdParam, final PathCondition... nestedConditions) { if (thresholdParam <= 0) { throw new IllegalArgumentException("Count must be a positive integer but was " + thresholdParam); } this.threshold = thresholdParam; - this.nestedConditions = nestedConditions == null ? new PathCondition[0] : Arrays.copyOf(nestedConditions, - nestedConditions.length); + this.nestedConditions = PathCondition.copy(nestedConditions); } public int getThresholdCount() { @@ -59,7 +59,7 @@ public List getNestedConditions() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#accept(java.nio.file.Path, * java.nio.file.Path, java.nio.file.attribute.BasicFileAttributes) */ @@ -78,7 +78,7 @@ public boolean accept(final Path basePath, final Path relativePath, final BasicF /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#beforeFileTreeWalk() */ @Override @@ -89,12 +89,12 @@ public void beforeFileTreeWalk() { /** * Create an IfAccumulatedFileCount condition. - * + * * @param threshold The threshold count from which files will be deleted. * @return An IfAccumulatedFileCount condition. */ @PluginFactory - public static IfAccumulatedFileCount createFileCountCondition( + public static IfAccumulatedFileCount createFileCountCondition( // @formatter:off @PluginAttribute(value = "exceeds", defaultInt = Integer.MAX_VALUE) final int threshold, @PluginElement("PathConditions") final PathCondition... nestedConditions) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSize.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSize.java index 7c1d9084649..b6f9ecadd8d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSize.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAccumulatedFileSize.java @@ -16,38 +16,38 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.rolling.FileSize; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; + import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Collections; import java.util.List; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.appender.rolling.FileSize; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.status.StatusLogger; - /** * PathCondition that accepts paths after the accumulated file size threshold is exceeded during the file tree walk. */ -@Plugin(name = "IfAccumulatedFileSize", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class IfAccumulatedFileSize implements PathCondition { private static final Logger LOGGER = StatusLogger.getLogger(); private final long thresholdBytes; private long accumulatedSize; private final PathCondition[] nestedConditions; - private IfAccumulatedFileSize(final long thresholdSize, final PathCondition[] nestedConditions) { + private IfAccumulatedFileSize(final long thresholdSize, final PathCondition... nestedConditions) { if (thresholdSize <= 0) { throw new IllegalArgumentException("Count must be a positive integer but was " + thresholdSize); } this.thresholdBytes = thresholdSize; - this.nestedConditions = nestedConditions == null ? new PathCondition[0] : Arrays.copyOf(nestedConditions, - nestedConditions.length); + this.nestedConditions = PathCondition.copy(nestedConditions); } public long getThresholdBytes() { @@ -60,7 +60,7 @@ public List getNestedConditions() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#accept(java.nio.file.Path, * java.nio.file.Path, java.nio.file.attribute.BasicFileAttributes) */ @@ -80,7 +80,7 @@ public boolean accept(final Path basePath, final Path relativePath, final BasicF /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#beforeFileTreeWalk() */ @Override @@ -91,12 +91,12 @@ public void beforeFileTreeWalk() { /** * Create an IfAccumulatedFileSize condition. - * + * * @param threshold The threshold accumulated file size from which files will be deleted. * @return An IfAccumulatedFileSize condition. */ @PluginFactory - public static IfAccumulatedFileSize createFileSizeCondition( + public static IfAccumulatedFileSize createFileSizeCondition( // @formatter:off @PluginAttribute("exceeds") final String size, @PluginElement("PathConditions") final PathCondition... nestedConditions) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAll.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAll.java index 2eaea8bd909..2f6a461c81f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAll.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAll.java @@ -16,21 +16,22 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Objects; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Composite {@code PathCondition} that only accepts objects that are accepted by all component conditions. * Corresponds to logical "AND". */ -@Plugin(name = "IfAll", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class IfAll implements PathCondition { private final PathCondition[] components; @@ -45,7 +46,7 @@ public PathCondition[] getDeleteFilters() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#accept(java.nio.file.Path, * java.nio.file.Path, java.nio.file.attribute.BasicFileAttributes) */ @@ -59,7 +60,7 @@ public boolean accept(final Path baseDir, final Path relativePath, final BasicFi /** * Returns {@code true} if all the specified conditions accept the specified path, {@code false} otherwise. - * + * * @param list the array of conditions to evaluate * @param baseDir the directory from where to start scanning for deletion candidate files * @param relativePath the candidate for deletion. This path is relative to the baseDir. @@ -79,7 +80,7 @@ public static boolean accept(final PathCondition[] list, final Path baseDir, fin /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#beforeFileTreeWalk() */ @Override @@ -89,7 +90,7 @@ public void beforeFileTreeWalk() { /** * Calls {@link #beforeFileTreeWalk()} on all of the specified nested conditions. - * + * * @param nestedConditions the conditions to call {@link #beforeFileTreeWalk()} on */ public static void beforeFileTreeWalk(final PathCondition[] nestedConditions) { @@ -100,12 +101,12 @@ public static void beforeFileTreeWalk(final PathCondition[] nestedConditions) { /** * Create a Composite PathCondition whose components all need to accept before this condition accepts. - * + * * @param components The component filters. * @return A Composite PathCondition. */ @PluginFactory - public static IfAll createAndCondition( + public static IfAll createAndCondition( @PluginElement("PathConditions") final PathCondition... components) { return new IfAll(components); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAny.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAny.java index 6d5841fc940..410bf49ec68 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAny.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfAny.java @@ -16,21 +16,22 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Objects; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Composite {@code PathCondition} that accepts objects that are accepted by any component conditions. * Corresponds to logical "OR". */ -@Plugin(name = "IfAny", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class IfAny implements PathCondition { private final PathCondition[] components; @@ -70,12 +71,12 @@ public void beforeFileTreeWalk() { /** * Create a Composite PathCondition: accepts if any of the nested conditions accepts. - * + * * @param components The component conditions. * @return A Composite PathCondition. */ @PluginFactory - public static IfAny createOrCondition( + public static IfAny createOrCondition( @PluginElement("PathConditions") final PathCondition... components) { return new IfAny(components); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileName.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileName.java index 1084a7765bf..7c278d28c37 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileName.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfFileName.java @@ -16,6 +16,14 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; + import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; @@ -26,14 +34,6 @@ import java.util.List; import java.util.regex.Pattern; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.status.StatusLogger; - /** * PathCondition that accepts files for deletion if their relative path matches either a glob pattern or a regular * expression. If both a regular expression and a glob pattern are specified the glob pattern is used and the regular @@ -42,7 +42,8 @@ * The regular expression is a pattern as defined by the {@link Pattern} class. A glob is a simplified pattern * expression described in {@link FileSystem#getPathMatcher(String)}. */ -@Plugin(name = "IfFileName", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class IfFileName implements PathCondition { private static final Logger LOGGER = StatusLogger.getLogger(); private final PathMatcher pathMatcher; @@ -52,20 +53,19 @@ public final class IfFileName implements PathCondition { /** * Constructs a FileNameFilter filter. If both a regular expression and a glob pattern are specified the glob * pattern is used and the regular expression is ignored. - * + * * @param glob the baseDir-relative path pattern of the files to delete (may contain '*' and '?' wildcarts) * @param regex the regular expression that matches the baseDir-relative path of the file(s) to delete * @param nestedConditions nested conditions to evaluate if this condition accepts a path */ - private IfFileName(final String glob, final String regex, final PathCondition[] nestedConditions) { + private IfFileName(final String glob, final String regex, final PathCondition... nestedConditions) { if (regex == null && glob == null) { throw new IllegalArgumentException("Specify either a path glob or a regular expression. " + "Both cannot be null."); } this.syntaxAndPattern = createSyntaxAndPatternString(glob, regex); this.pathMatcher = FileSystems.getDefault().getPathMatcher(syntaxAndPattern); - this.nestedConditions = nestedConditions == null ? new PathCondition[0] : Arrays.copyOf(nestedConditions, - nestedConditions.length); + this.nestedConditions = PathCondition.copy(nestedConditions); } static String createSyntaxAndPatternString(final String glob, final String regex) { @@ -80,7 +80,7 @@ static String createSyntaxAndPatternString(final String glob, final String regex * {@code syntax:pattern} where syntax is one of "glob" or "regex" and the pattern is either a {@linkplain Pattern * regular expression} or a simplified pattern expression described under "glob" in * {@link FileSystem#getPathMatcher(String)}. - * + * * @return relative path of the file(s) to delete (may contain regular expression or wildcarts) */ public String getSyntaxAndPattern() { @@ -93,7 +93,7 @@ public List getNestedConditions() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#accept(java.nio.file.Path, * java.nio.file.Path, java.nio.file.attribute.BasicFileAttributes) */ @@ -112,7 +112,7 @@ public boolean accept(final Path basePath, final Path relativePath, final BasicF /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#beforeFileTreeWalk() */ @Override @@ -125,7 +125,7 @@ public void beforeFileTreeWalk() { * {@linkplain FileSystem#getPathMatcher(String) glob pattern} or the regular expression matches the relative path. * If both a regular expression and a glob pattern are specified the glob pattern is used and the regular expression * is ignored. - * + * * @param glob the baseDir-relative path pattern of the files to delete (may contain '*' and '?' wildcarts) * @param regex the regular expression that matches the baseDir-relative path of the file(s) to delete * @param nestedConditions nested conditions to evaluate if this condition accepts a path @@ -133,10 +133,10 @@ public void beforeFileTreeWalk() { * @see FileSystem#getPathMatcher(String) */ @PluginFactory - public static IfFileName createNameCondition( + public static IfFileName createNameCondition( // @formatter:off - @PluginAttribute("glob") final String glob, - @PluginAttribute("regex") final String regex, + @PluginAttribute final String glob, + @PluginAttribute final String regex, @PluginElement("PathConditions") final PathCondition... nestedConditions) { // @formatter:on return new IfFileName(glob, regex, nestedConditions); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModified.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModified.java index 4a4c7179e21..6e48df121bc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModified.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModified.java @@ -16,39 +16,41 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.ClockFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; + import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.util.Clock; -import org.apache.logging.log4j.core.util.ClockFactory; -import org.apache.logging.log4j.status.StatusLogger; +import java.util.function.Supplier; /** * PathCondition that accepts paths that are older than the specified duration. */ -@Plugin(name = "IfLastModified", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class IfLastModified implements PathCondition { private static final Logger LOGGER = StatusLogger.getLogger(); - private static final Clock CLOCK = ClockFactory.getClock(); + private final Clock clock; private final Duration age; private final PathCondition[] nestedConditions; - private IfLastModified(final Duration age, final PathCondition[] nestedConditions) { + private IfLastModified(final Duration age, final PathCondition[] nestedConditions, final Clock clock) { this.age = Objects.requireNonNull(age, "age"); - this.nestedConditions = nestedConditions == null ? new PathCondition[0] : Arrays.copyOf(nestedConditions, - nestedConditions.length); + this.nestedConditions = PathCondition.copy(nestedConditions); + this.clock = clock; } public Duration getAge() { @@ -56,12 +58,12 @@ public Duration getAge() { } public List getNestedConditions() { - return Collections.unmodifiableList(Arrays.asList(nestedConditions)); + return List.of(nestedConditions); } /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#accept(java.nio.file.Path, * java.nio.file.Path, java.nio.file.attribute.BasicFileAttributes) */ @@ -69,7 +71,7 @@ public List getNestedConditions() { public boolean accept(final Path basePath, final Path relativePath, final BasicFileAttributes attrs) { final FileTime fileTime = attrs.lastModifiedTime(); final long millis = fileTime.toMillis(); - final long ageMillis = CLOCK.currentTimeMillis() - millis; + final long ageMillis = clock.currentTimeMillis() - millis; final boolean result = ageMillis >= age.toMillis(); final String match = result ? ">=" : "<"; final String accept = result ? "ACCEPTED" : "REJECTED"; @@ -82,7 +84,7 @@ public boolean accept(final Path basePath, final Path relativePath, final BasicF /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.appender.rolling.action.PathCondition#beforeFileTreeWalk() */ @Override @@ -90,25 +92,56 @@ public void beforeFileTreeWalk() { IfAll.beforeFileTreeWalk(nestedConditions); } + @Override + public String toString() { + final String nested = nestedConditions.length == 0 ? "" : " AND " + Arrays.toString(nestedConditions); + return "IfLastModified(age=" + age + nested + ")"; + } + /** * Create an IfLastModified condition. - * + * * @param age The path age that is accepted by this condition. Must be a valid Duration. * @param nestedConditions nested conditions to evaluate if this condition accepts a path * @return An IfLastModified condition. */ + @Deprecated(since = "3.0.0", forRemoval = true) + public static IfLastModified createAgeCondition(final Duration age, final PathCondition... nestedConditions) { + return newBuilder().setAge(age).setNestedConditions(nestedConditions).get(); + } + @PluginFactory - public static IfLastModified createAgeCondition( - // @formatter:off - @PluginAttribute("age") final Duration age, - @PluginElement("PathConditions") final PathCondition... nestedConditions) { - // @formatter:on - return new IfLastModified(age, nestedConditions); + public static Builder newBuilder() { + return new Builder(); } - @Override - public String toString() { - final String nested = nestedConditions.length == 0 ? "" : " AND " + Arrays.toString(nestedConditions); - return "IfLastModified(age=" + age + nested + ")"; + public static class Builder implements Supplier { + private Duration age; + private PathCondition[] nestedConditions; + private Clock clock; + + public Builder setAge(@PluginAttribute final Duration age) { + this.age = age; + return this; + } + + public Builder setNestedConditions(@PluginElement final PathCondition... nestedConditions) { + this.nestedConditions = nestedConditions; + return this; + } + + @Inject + public Builder setClock(final Clock clock) { + this.clock = clock; + return this; + } + + @Override + public IfLastModified get() { + if (clock == null) { + clock = ClockFactory.getClock(); + } + return new IfLastModified(age, nestedConditions, clock); + } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfNot.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfNot.java index 1baf1877342..64354f82309 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfNot.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfNot.java @@ -16,19 +16,20 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Objects; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Wrapper {@code PathCondition} that accepts objects that are rejected by the wrapped component filter. */ -@Plugin(name = "IfNot", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class IfNot implements PathCondition { private final PathCondition negate; @@ -61,12 +62,12 @@ public void beforeFileTreeWalk() { /** * Create an IfNot PathCondition. - * + * * @param condition The condition to negate. * @return An IfNot PathCondition. */ @PluginFactory - public static IfNot createNotCondition( + public static IfNot createNotCondition( @PluginElement("PathConditions") final PathCondition condition) { return new IfNot(condition); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathCondition.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathCondition.java index 0fa7873158d..5dcdb4f629c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathCondition.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathCondition.java @@ -1,44 +1,60 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.appender.rolling.action; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; - -/** - * Filter that accepts or rejects a candidate {@code Path} for deletion. - */ -public interface PathCondition { - - /** - * Invoked before a new {@linkplain Files#walkFileTree(Path, java.util.Set, int, java.nio.file.FileVisitor) file - * tree walk} is started. Stateful PathConditions can reset their state when this method is called. - */ - void beforeFileTreeWalk(); - - /** - * Returns {@code true} if the specified candidate path should be deleted, {@code false} otherwise. - * - * @param baseDir the directory from where to start scanning for deletion candidate files - * @param relativePath the candidate for deletion. This path is relative to the baseDir. - * @param attrs attributes of the candidate path - * @return whether the candidate path should be deleted - */ - boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs); -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.appender.rolling.action; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Arrays; + +/** + * Filter that accepts or rejects a candidate {@code Path} for deletion. + */ +public interface PathCondition { + + /** + * Empty array. + */ + static final PathCondition[] EMPTY_ARRAY = {}; + + /** + * Copies the given input. + * + * @param source What to copy + * @return a copy, never null. + */ + static PathCondition[] copy(PathCondition... source) { + return source == null || source.length == 0 ? EMPTY_ARRAY : Arrays.copyOf(source, source.length); + } + + /** + * Invoked before a new {@linkplain Files#walkFileTree(Path, java.util.Set, int, java.nio.file.FileVisitor) file + * tree walk} is started. Stateful PathConditions can reset their state when this method is called. + */ + void beforeFileTreeWalk(); + + /** + * Returns {@code true} if the specified candidate path should be deleted, {@code false} otherwise. + * + * @param baseDir the directory from where to start scanning for deletion candidate files + * @param relativePath the candidate for deletion. This path is relative to the baseDir. + * @param attrs attributes of the candidate path + * @return whether the candidate path should be deleted + */ + boolean accept(final Path baseDir, final Path relativePath, final BasicFileAttributes attrs); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTime.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTime.java index e0a04b5ce2c..ab23b6ce947 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTime.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathSortByModificationTime.java @@ -17,17 +17,18 @@ package org.apache.logging.log4j.core.appender.rolling.action; -import java.io.Serializable; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import java.io.Serializable; /** * {@link PathSorter} that sorts path by their LastModified attribute. */ -@Plugin(name = "SortByModificationTime", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("SortByModificationTime") public class PathSortByModificationTime implements PathSorter, Serializable { private static final long serialVersionUID = 1L; @@ -37,7 +38,7 @@ public class PathSortByModificationTime implements PathSorter, Serializable { /** * Constructs a new SortByModificationTime sorter. - * + * * @param recentFirst if true, most recently modified paths should come first */ public PathSortByModificationTime(final boolean recentFirst) { @@ -47,19 +48,19 @@ public PathSortByModificationTime(final boolean recentFirst) { /** * Create a PathSorter that sorts by lastModified time. - * + * * @param recentFirst if true, most recently modified paths should come first. * @return A PathSorter. */ @PluginFactory - public static PathSorter createSorter( - @PluginAttribute(value = "recentFirst", defaultBoolean = true) final boolean recentFirst) { + public static PathSorter createSorter( + @PluginAttribute(defaultBoolean = true) final boolean recentFirst) { return new PathSortByModificationTime(recentFirst); } /** * Returns whether this sorter sorts recent files first. - * + * * @return whether this sorter sorts recent files first */ public boolean isRecentFirst() { @@ -68,7 +69,7 @@ public boolean isRecentFirst() { /* * (non-Javadoc) - * + * * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java index 42640c3202f..d69835427b9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PathWithAttributes.java @@ -41,7 +41,7 @@ public String toString() { /** * Returns the path. - * + * * @return the path */ public Path getPath() { @@ -50,7 +50,7 @@ public Path getPath() { /** * Returns the attributes. - * + * * @return the attributes */ public BasicFileAttributes getAttributes() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PosixViewAttributeAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PosixViewAttributeAction.java index bb5bc741087..2cb5d6eb6ae 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PosixViewAttributeAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PosixViewAttributeAction.java @@ -16,6 +16,18 @@ */ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.util.FileUtils; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; +import org.apache.logging.log4j.util.Strings; + import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; @@ -29,26 +41,15 @@ import java.util.List; import java.util.Set; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.util.FileUtils; -import org.apache.logging.log4j.util.Strings; - /** - * File posix attribute view action. - * - * Allow to define file permissions, user and group for log files on posix supported OS. + * File POSIX attribute view action. + * + * Allow to define file permissions, user and group for log files on POSIX supported OS. */ -@Plugin(name = "PosixViewAttribute", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("PosixViewAttribute") public class PosixViewAttributeAction extends AbstractPathAction { - + /** * File permissions. */ @@ -74,19 +75,19 @@ private PosixViewAttributeAction(final String basePath, final boolean followSymb this.fileGroup = fileGroup; } - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } /** - * Builder for the posix view attribute action. + * Builder for the POSIX view attribute action. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginConfiguration private Configuration configuration; - + private StrSubstitutor subst; @PluginBuilderAttribute @@ -145,7 +146,7 @@ public PosixViewAttributeAction build() { * @param configuration {@link AbstractPathAction#getStrSubstitutor()} * @return This builder */ - public Builder withConfiguration(final Configuration configuration) { + public Builder setConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } @@ -156,17 +157,17 @@ public Builder withConfiguration(final Configuration configuration) { * @param subst {@link AbstractPathAction#getStrSubstitutor()} * @return This builder */ - public Builder withSubst(final StrSubstitutor subst) { + public Builder setSubst(final StrSubstitutor subst) { this.subst = subst; return this; } /** - * Define base path to apply condition before execute posix file attribute action. + * Define base path to apply condition before execute POSIX file attribute action. * @param basePath {@link AbstractPathAction#getBasePath()} * @return This builder */ - public Builder withBasePath(final String basePath) { + public Builder setBasePath(final String basePath) { this.basePath = basePath; return this; } @@ -176,17 +177,17 @@ public Builder withBasePath(final String basePath) { * @param followLinks Follow synonyms links * @return This builder */ - public Builder withFollowLinks(final boolean followLinks) { + public Builder setFollowLinks(final boolean followLinks) { this.followLinks = followLinks; return this; } /** - * Define max folder depth to search for eligible files to apply posix attribute view. - * @param maxDepth Max search depth + * Define max folder depth to search for eligible files to apply POSIX attribute view. + * @param maxDepth Max search depth * @return This builder */ - public Builder withMaxDepth(final int maxDepth) { + public Builder setMaxDepth(final int maxDepth) { this.maxDepth = maxDepth; return this; } @@ -197,13 +198,13 @@ public Builder withMaxDepth(final int maxDepth) { * @param pathConditions {@link AbstractPathAction#getPathConditions()} * @return This builder */ - public Builder withPathConditions(final PathCondition[] pathConditions) { + public Builder setPathConditions(final PathCondition[] pathConditions) { this.pathConditions = pathConditions; return this; } /** - * Define file permissions in posix format to apply during action execution eligible files. + * Define file permissions in POSIX format to apply during action execution eligible files. * * Example: *

    rw-rw-rw @@ -211,7 +212,7 @@ public Builder withPathConditions(final PathCondition[] pathConditions) { * @param filePermissionsString Permissions to apply * @return This builder */ - public Builder withFilePermissionsString(final String filePermissionsString) { + public Builder setFilePermissionsString(final String filePermissionsString) { this.filePermissionsString = filePermissionsString; return this; } @@ -221,7 +222,7 @@ public Builder withFilePermissionsString(final String filePermissionsString) { * @param filePermissions Permissions to apply * @return This builder */ - public Builder withFilePermissions(final Set filePermissions) { + public Builder setFilePermissions(final Set filePermissions) { this.filePermissions = filePermissions; return this; } @@ -231,7 +232,7 @@ public Builder withFilePermissions(final Set filePermission * @param fileOwner File owner * @return This builder */ - public Builder withFileOwner(final String fileOwner) { + public Builder setFileOwner(final String fileOwner) { this.fileOwner = fileOwner; return this; } @@ -241,7 +242,7 @@ public Builder withFileOwner(final String fileOwner) { * @param fileGroup File group * @return This builder */ - public Builder withFileGroup(final String fileGroup) { + public Builder setFileGroup(final String fileGroup) { this.fileGroup = fileGroup; return this; } @@ -256,7 +257,7 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr for (final PathCondition pathFilter : conditions) { final Path relative = basePath.relativize(file); if (!pathFilter.accept(basePath, relative, attrs)) { - LOGGER.trace("Not defining posix attribute base={}, relative={}", basePath, relative); + LOGGER.trace("Not defining POSIX attribute base={}, relative={}", basePath, relative); return FileVisitResult.CONTINUE; } } @@ -267,18 +268,18 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr } /** - * Returns posix file permissions if defined and the OS supports posix file attribute, + * Returns POSIX file permissions if defined and the OS supports POSIX file attribute, * null otherwise. - * @return File posix permissions + * @return File POSIX permissions * @see PosixFileAttributeView */ public Set getFilePermissions() { return filePermissions; } - + /** * Returns file owner if defined and the OS supports owner file attribute view, - * null otherwise. + * null otherwise. * @return File owner * @see FileOwnerAttributeView */ @@ -287,8 +288,8 @@ public String getFileOwner() { } /** - * Returns file group if defined and the OS supports posix/group file attribute view, - * null otherwise. + * Returns file group if defined and the OS supports POSIX/group file attribute view, + * null otherwise. * @return File group * @see PosixFileAttributeView */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java index 7bf76b6fe23..ba7e00b601e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java @@ -17,8 +17,12 @@ package org.apache.logging.log4j.core.appender.rolling.action; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; + import java.io.IOException; import java.nio.file.FileVisitResult; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; @@ -32,12 +36,13 @@ */ public class SortingVisitor extends SimpleFileVisitor { + private static final Logger LOGGER = StatusLogger.getLogger(); private final PathSorter sorter; private final List collected = new ArrayList<>(); /** * Constructs a new DeletingVisitor. - * + * * @param basePath used to relativize paths * @param pathFilters objects that need to confirm whether a file can be deleted */ @@ -50,7 +55,19 @@ public FileVisitResult visitFile(final Path path, final BasicFileAttributes attr collected.add(new PathWithAttributes(path, attrs)); return FileVisitResult.CONTINUE; } - + + @Override + public FileVisitResult visitFileFailed(final Path file, final IOException ioException) throws IOException { + // LOG4J2-2677: Appenders may rollover and purge in parallel. SimpleVisitor rethrows exceptions from + // failed attempts to load file attributes. + if (ioException instanceof NoSuchFileException) { + LOGGER.info("File {} could not be accessed, it has likely already been deleted", file, ioException); + return FileVisitResult.CONTINUE; + } else { + return super.visitFileFailed(file, ioException); + } + } + public List getSortedPaths() { Collections.sort(collected, sorter); return collected; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ZipCompressAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ZipCompressAction.java index a7705da1076..3a0019f6228 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ZipCompressAction.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/ZipCompressAction.java @@ -132,7 +132,7 @@ protected void reportException(final Exception ex) { @Override public String toString() { - return ZipCompressAction.class.getSimpleName() + '[' + source + " to " + destination + return ZipCompressAction.class.getSimpleName() + '[' + source + " to " + destination + ", level=" + level + ", deleteSource=" + deleteSource + ']'; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/IdlePurgePolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/IdlePurgePolicy.java index e892f666807..19916ccd7a6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/IdlePurgePolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/IdlePurgePolicy.java @@ -16,32 +16,33 @@ */ package org.apache.logging.log4j.core.appender.routing; -import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.AbstractLifeCycle; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationScheduler; import org.apache.logging.log4j.core.config.Scheduled; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; /** * Policy is purging appenders that were not in use specified time in minutes */ -@Plugin(name = "IdlePurgePolicy", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin @Scheduled public class IdlePurgePolicy extends AbstractLifeCycle implements PurgePolicy, Runnable { private final long timeToLive; - private final long checkInterval; + private final long checkInterval; private final ConcurrentMap appendersUsage = new ConcurrentHashMap<>(); private RoutingAppender routingAppender; private final ConfigurationScheduler scheduler; @@ -73,9 +74,10 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { public void purge() { final long createTime = System.currentTimeMillis() - timeToLive; for (final Entry entry : appendersUsage.entrySet()) { - if (entry.getValue() < createTime) { - LOGGER.debug("Removing appender " + entry.getKey()); - if (appendersUsage.remove(entry.getKey(), entry.getValue())) { + final long entryValue = entry.getValue(); + if (entryValue < createTime) { + if (appendersUsage.remove(entry.getKey(), entryValue)) { + LOGGER.debug("Removing appender {}", entry.getKey()); routingAppender.deleteAppender(entry.getKey()); } } @@ -123,15 +125,15 @@ private void scheduleNext() { * Create the PurgePolicy * * @param timeToLive the number of increments of timeUnit before the Appender should be purged. - * @param checkInterval when all appenders purged, the number of increments of timeUnit to check if any appenders appeared + * @param checkInterval when all appenders purged, the number of increments of timeUnit to check if any appenders appeared * @param timeUnit the unit of time the timeToLive and the checkInterval is expressed in. * @return The Routes container. */ @PluginFactory public static PurgePolicy createPurgePolicy( - @PluginAttribute("timeToLive") final String timeToLive, - @PluginAttribute("checkInterval") final String checkInterval, - @PluginAttribute("timeUnit") final String timeUnit, + @PluginAttribute final String timeToLive, + @PluginAttribute final String checkInterval, + @PluginAttribute final String timeUnit, @PluginConfiguration final Configuration configuration) { if (timeToLive == null) { @@ -155,7 +157,7 @@ public static PurgePolicy createPurgePolicy( LOGGER.error("timeToLive must be positive. timeToLive set to 0"); ttl = 0; } - + long ci; if (checkInterval == null) { ci = ttl; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/PurgePolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/PurgePolicy.java index b0c8c6172d0..9f773e68664 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/PurgePolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/PurgePolicy.java @@ -24,20 +24,21 @@ public interface PurgePolicy { /** - * Activates purging appenders + * Activates purging appenders. Note that {@link PurgePolicy} implementations are responsible for invoking + * this method themselves. */ void purge(); - + /** - * - * @param routed appender key + * + * @param key routed appender key * @param event */ void update(String key, LogEvent event); /** * Initializes with routing appender - * + * * @param routingAppender */ void initialize(RoutingAppender routingAppender); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Route.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Route.java index 1a92b6699a3..75557dc7165 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Route.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Route.java @@ -17,18 +17,19 @@ package org.apache.logging.log4j.core.appender.routing; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.PluginNode; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.PluginNode; import org.apache.logging.log4j.status.StatusLogger; /** * A Route to an appender. */ -@Plugin(name = "Route", category = Core.CATEGORY_NAME, printObject = true, deferChildren = true) +@Configurable(printObject = true, deferChildren = true) +@Plugin public final class Route { private static final Logger LOGGER = StatusLogger.getLogger(); @@ -96,7 +97,7 @@ public String toString() { @PluginFactory public static Route createRoute( @PluginAttribute("ref") final String appenderRef, - @PluginAttribute("key") final String key, + @PluginAttribute final String key, @PluginNode final Node node) { if (node != null && node.hasChildren()) { if (appenderRef != null) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java index e179ad76432..0ebeb034021 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java @@ -16,47 +16,47 @@ */ package org.apache.logging.log4j.core.appender.routing; -import static org.apache.logging.log4j.core.appender.routing.RoutingAppender.STATIC_VARIABLES_KEY; - -import java.util.Objects; -import java.util.concurrent.ConcurrentMap; - -import javax.script.Bindings; - import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.script.AbstractScript; +import org.apache.logging.log4j.core.script.Script; +import org.apache.logging.log4j.core.script.ScriptBindings; import org.apache.logging.log4j.core.script.ScriptManager; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.status.StatusLogger; +import java.util.Objects; +import java.util.concurrent.ConcurrentMap; + +import static org.apache.logging.log4j.core.appender.routing.RoutingAppender.STATIC_VARIABLES_KEY; + /** * Contains the individual Route elements. */ -@Plugin(name = "Routes", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class Routes { private static final String LOG_EVENT_KEY = "logEvent"; - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { - @PluginConfiguration + @Inject private Configuration configuration; - @PluginAttribute("pattern") + @PluginAttribute private String pattern; - + @PluginElement("Script") - private AbstractScript patternScript; + private Script patternScript; - @PluginElement("Routes") + @PluginElement @Required private Route[] routes; @@ -66,14 +66,25 @@ public Routes build() { LOGGER.error("No Routes configured."); return null; } - if (patternScript != null && pattern != null) { + if ((patternScript != null && pattern != null) || (patternScript == null && pattern == null)) { LOGGER.warn("In a Routes element, you must configure either a Script element or a pattern attribute."); } if (patternScript != null) { if (configuration == null) { LOGGER.error("No Configuration defined for Routes; required for Script"); } else { - configuration.getScriptManager().addScript(patternScript); + ScriptManager scriptManager = configuration.getScriptManager(); + if (scriptManager == null) { + LOGGER.error("Script support is not enabled"); + return null; + } + if (!scriptManager.addScript(patternScript)) { + if (!scriptManager.isScriptRef(patternScript)) { + if (!scriptManager.addScript(patternScript)) { + return null; + } + } + } } } return new Routes(configuration, patternScript, pattern, routes); @@ -87,7 +98,7 @@ public String getPattern() { return pattern; } - public AbstractScript getPatternScript() { + public Script getPatternScript() { return patternScript; } @@ -95,63 +106,45 @@ public Route[] getRoutes() { return routes; } - public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { + public Builder setConfiguration(@SuppressWarnings("hiding") final Configuration configuration) { this.configuration = configuration; return this; } - public Builder withPattern(@SuppressWarnings("hiding") final String pattern) { + public Builder setPattern(@SuppressWarnings("hiding") final String pattern) { this.pattern = pattern; return this; } - public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) { + public Builder setPatternScript(@SuppressWarnings("hiding") final Script patternScript) { this.patternScript = patternScript; return this; } - public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) { + public Builder setRoutes(@SuppressWarnings("hiding") final Route... routes) { this.routes = routes; return this; } - + } private static final Logger LOGGER = StatusLogger.getLogger(); - /** - * Creates the Routes. - * @param pattern The pattern. - * @param routes An array of Route elements. - * @return The Routes container. - * @deprecated since 2.7; use {@link #newBuilder()}. - */ - @Deprecated - public static Routes createRoutes( - final String pattern, - final Route... routes) { - if (routes == null || routes.length == 0) { - LOGGER.error("No routes configured"); - return null; - } - return new Routes(null, null, pattern, routes); - } - - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - + private final Configuration configuration; - + private final String pattern; - private final AbstractScript patternScript; - + private final Script patternScript; + // TODO Why not make this a Map or add a Map. private final Route[] routes; - - private Routes(final Configuration configuration, final AbstractScript patternScript, final String pattern, final Route... routes) { + + private Routes(final Configuration configuration, final Script patternScript, final String pattern, final Route... routes) { this.configuration = configuration; this.patternScript = patternScript; this.pattern = pattern; @@ -167,7 +160,7 @@ private Routes(final Configuration configuration, final AbstractScript patternSc public String getPattern(final LogEvent event, final ConcurrentMap scriptStaticVariables) { if (patternScript != null) { final ScriptManager scriptManager = configuration.getScriptManager(); - final Bindings bindings = scriptManager.createBindings(patternScript); + final ScriptBindings bindings = scriptManager.createBindings(patternScript); bindings.put(STATIC_VARIABLES_KEY, scriptStaticVariables); bindings.put(LOG_EVENT_KEY, event); final Object object = scriptManager.execute(patternScript.getName(), bindings); @@ -181,7 +174,7 @@ public String getPattern(final LogEvent event, final ConcurrentMap> extends AbstractAppender.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { // Does not work unless the element is called "Script", I wanted "DefaultRounteScript"... @PluginElement("Script") - private AbstractScript defaultRouteScript; + private Script defaultRouteScript; @PluginElement("Routes") private Routes routes; @@ -71,6 +71,9 @@ public static class Builder> extends AbstractAppender.Build @PluginElement("PurgePolicy") private PurgePolicy purgePolicy; + @PluginElement("RequiresLocation") + private Boolean requiresLocation; + @Override public RoutingAppender build() { final String name = getName(); @@ -82,15 +85,27 @@ public RoutingAppender build() { LOGGER.error("No routes defined for RoutingAppender {}", name); return null; } + if (defaultRouteScript != null) { + ScriptManager scriptManager = getConfiguration().getScriptManager(); + if (scriptManager == null) { + LOGGER.error("Script support is not enabled"); + return null; + } + if (!scriptManager.isScriptRef(defaultRouteScript)) { + if (!scriptManager.addScript(defaultRouteScript)) { + return null; + } + } + } return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy, - getConfiguration(), purgePolicy, defaultRouteScript); + getConfiguration(), purgePolicy, defaultRouteScript, getPropertyArray(), requiresLocation); } public Routes getRoutes() { return routes; } - public AbstractScript getDefaultRouteScript() { + public Script getDefaultRouteScript() { return defaultRouteScript; } @@ -102,28 +117,38 @@ public PurgePolicy getPurgePolicy() { return purgePolicy; } - public B withRoutes(@SuppressWarnings("hiding") final Routes routes) { + public Boolean requiresLocation() { + return requiresLocation; + } + + public B setRoutes(@SuppressWarnings("hiding") final Routes routes) { this.routes = routes; return asBuilder(); } - public B withDefaultRouteScript(@SuppressWarnings("hiding") final AbstractScript defaultRouteScript) { + public B setDefaultRouteScript(@SuppressWarnings("hiding") final Script defaultRouteScript) { this.defaultRouteScript = defaultRouteScript; return asBuilder(); } - public B withRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) { + public B setRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) { this.rewritePolicy = rewritePolicy; return asBuilder(); } - public void withPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) { + public B setPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) { this.purgePolicy = purgePolicy; + return asBuilder(); + } + + public B setRequestLocation(@SuppressWarnings("hiding") final boolean requiresLocation) { + this.requiresLocation = requiresLocation; + return asBuilder(); } } - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } @@ -133,20 +158,25 @@ public static > B newBuilder() { private final Routes routes; private Route defaultRoute; private final Configuration configuration; - private final ConcurrentMap appenders = new ConcurrentHashMap<>(); + private final ConcurrentMap createdAppenders = new ConcurrentHashMap<>(); + private final Map createdAppendersUnmodifiableView + = Collections.unmodifiableMap(createdAppenders); + private final ConcurrentMap referencedAppenders = new ConcurrentHashMap<>(); private final RewritePolicy rewritePolicy; private final PurgePolicy purgePolicy; - private final AbstractScript defaultRouteScript; + private final Script defaultRouteScript; private final ConcurrentMap scriptStaticVariables = new ConcurrentHashMap<>(); + private final Boolean requiresLocation; private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes, final RewritePolicy rewritePolicy, final Configuration configuration, final PurgePolicy purgePolicy, - final AbstractScript defaultRouteScript) { - super(name, filter, null, ignoreExceptions); + final Script defaultRouteScript, final Property[] properties, final Boolean requiresLocation) { + super(name, filter, null, ignoreExceptions, properties); this.routes = routes; this.configuration = configuration; this.rewritePolicy = rewritePolicy; this.purgePolicy = purgePolicy; + this.requiresLocation = requiresLocation; if (this.purgePolicy != null) { this.purgePolicy.initialize(this); } @@ -172,7 +202,7 @@ public void start() { } else { final ScriptManager scriptManager = configuration.getScriptManager(); scriptManager.addScript(defaultRouteScript); - final Bindings bindings = scriptManager.createBindings(defaultRouteScript); + final ScriptBindings bindings = scriptManager.createBindings(defaultRouteScript); bindings.put(STATIC_VARIABLES_KEY, scriptStaticVariables); final Object object = scriptManager.execute(defaultRouteScript.getName(), bindings); final Route route = routes.getRoute(Objects.toString(object, null)); @@ -187,7 +217,7 @@ public void start() { final Appender appender = configuration.getAppender(route.getAppenderRef()); if (appender != null) { final String key = route == defaultRoute ? DEFAULT_KEY : route.getKey(); - appenders.put(key, new AppenderControl(appender, null, null)); + referencedAppenders.put(key, new ReferencedRouteAppenderControl(appender)); } else { error("Appender " + route.getAppenderRef() + " cannot be located. Route ignored"); } @@ -196,20 +226,20 @@ public void start() { super.start(); } + @Override + public boolean requiresLocation() { + return requiresLocation != null ? requiresLocation : false; + } + + @Override public boolean stop(final long timeout, final TimeUnit timeUnit) { setStopping(); super.stop(timeout, timeUnit, false); - final Map map = configuration.getAppenders(); - for (final Map.Entry entry : appenders.entrySet()) { + // Only stop appenders that were created by this RoutingAppender + for (final Map.Entry entry : createdAppenders.entrySet()) { final Appender appender = entry.getValue().getAppender(); - if (!map.containsKey(appender.getName())) { - if (appender instanceof LifeCycle2) { - ((LifeCycle2) appender).stop(timeout, timeUnit); - } else { - appender.stop(); - } - } + appender.stop(timeout, timeUnit); } setStopped(); return true; @@ -221,20 +251,32 @@ public void append(LogEvent event) { event = rewritePolicy.rewrite(event); } final String pattern = routes.getPattern(event, scriptStaticVariables); - final String key = pattern != null ? configuration.getStrSubstitutor().replace(event, pattern) : defaultRoute.getKey(); - final AppenderControl control = getControl(key, event); + final String key = pattern != null ? configuration.getStrSubstitutor().replace(event, pattern) : + defaultRoute.getKey() != null ? defaultRoute.getKey() : DEFAULT_KEY; + final RouteAppenderControl control = getControl(key, event); if (control != null) { - control.callAppender(event); + try { + control.callAppender(event); + } finally { + control.release(); + } } + updatePurgePolicy(key, event); + } - if (purgePolicy != null) { + private void updatePurgePolicy(final String key, final LogEvent event) { + if (purgePolicy != null + // LOG4J2-2631: PurgePolicy implementations do not need to be aware of appenders that + // were not created by this RoutingAppender. + && !referencedAppenders.containsKey(key)) { purgePolicy.update(key, event); } } - private synchronized AppenderControl getControl(final String key, final LogEvent event) { - AppenderControl control = appenders.get(key); + private synchronized RouteAppenderControl getControl(final String key, final LogEvent event) { + RouteAppenderControl control = getAppender(key); if (control != null) { + control.checkout(); return control; } Route route = null; @@ -246,8 +288,9 @@ private synchronized AppenderControl getControl(final String key, final LogEvent } if (route == null) { route = defaultRoute; - control = appenders.get(DEFAULT_KEY); + control = getAppender(DEFAULT_KEY); if (control != null) { + control.checkout(); return control; } } @@ -256,17 +299,29 @@ private synchronized AppenderControl getControl(final String key, final LogEvent if (app == null) { return null; } - control = new AppenderControl(app, null, null); - appenders.put(key, control); + final CreatedRouteAppenderControl created = new CreatedRouteAppenderControl(app); + control = created; + createdAppenders.put(key, created); } + if (control != null) { + control.checkout(); + } return control; } + private RouteAppenderControl getAppender(final String key) { + final RouteAppenderControl result = referencedAppenders.get(key); + if (result == null) { + return createdAppenders.get(key); + } + return result; + } + private Appender createAppender(final Route route, final LogEvent event) { final Node routeNode = route.getNode(); for (final Node node : routeNode.getChildren()) { - if (node.getType().getElementName().equals(Appender.ELEMENT_TYPE)) { + if (node.getType().getElementType().equals(Appender.ELEMENT_TYPE)) { final Node appNode = new Node(node); configuration.createConfiguration(appNode, event); if (appNode.getObject() instanceof Appender) { @@ -282,8 +337,12 @@ private Appender createAppender(final Route route, final LogEvent event) { return null; } + /** + * Returns an unmodifiable view of the appenders created by this {@link RoutingAppender}. + * Note that this map does not contain appenders that are routed by reference. + */ public Map getAppenders() { - return Collections.unmodifiableMap(appenders); + return createdAppendersUnmodifiableView; } /** @@ -292,55 +351,34 @@ public Map getAppenders() { * @param key The appender's key */ public void deleteAppender(final String key) { - LOGGER.debug("Deleting route with " + key + " key "); - final AppenderControl control = appenders.remove(key); + LOGGER.debug("Deleting route with {} key ", key); + // LOG4J2-2631: Only appenders created by this RoutingAppender are eligible for deletion. + final CreatedRouteAppenderControl control = createdAppenders.remove(key); if (null != control) { - LOGGER.debug("Stopping route with " + key + " key"); - control.getAppender().stop(); + LOGGER.debug("Stopping route with {} key", key); + // Synchronize with getControl to avoid triggering stopAppender before RouteAppenderControl.checkout + // can be invoked. + synchronized (this) { + control.pendingDeletion = true; + } + // Don't attempt to stop the appender in a synchronized block, since it may block flushing events + // to disk. + control.tryStopAppender(); } else { - LOGGER.debug("Route with " + key + " key already deleted"); + if (referencedAppenders.containsKey(key)) { + LOGGER.debug("Route {} using an appender reference may not be removed because " + + "the appender may be used outside of the RoutingAppender", key); + } else { + LOGGER.debug("Route with {} key already deleted", key); + } } } - /** - * Creates a RoutingAppender. - * @param name The name of the Appender. - * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise - * they are propagated to the caller. - * @param routes The routing definitions. - * @param config The Configuration (automatically added by the Configuration). - * @param rewritePolicy A RewritePolicy, if any. - * @param filter A Filter to restrict events processed by the Appender or null. - * @return The RoutingAppender - * @deprecated Since 2.7; use {@link #newBuilder()} - */ - @Deprecated - public static RoutingAppender createAppender( - final String name, - final String ignore, - final Routes routes, - final Configuration config, - final RewritePolicy rewritePolicy, - final PurgePolicy purgePolicy, - final Filter filter) { - - final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true); - if (name == null) { - LOGGER.error("No name provided for RoutingAppender"); - return null; - } - if (routes == null) { - LOGGER.error("No routes defined for RoutingAppender"); - return null; - } - return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null); - } - public Route getDefaultRoute() { return defaultRoute; } - public AbstractScript getDefaultRouteScript() { + public Script getDefaultRouteScript() { return defaultRouteScript; } @@ -363,4 +401,82 @@ public Configuration getConfiguration() { public ConcurrentMap getScriptStaticVariables() { return scriptStaticVariables; } + + /** + * LOG4J2-2629: PurgePolicy implementations can invoke {@link #deleteAppender(String)} after we have looked up + * an instance of a target appender but before events are appended, which could result in events not being + * recorded to any appender. + * This extension of {@link AppenderControl} allows to mark usage of an appender, allowing deferral of + * {@link Appender#stop()} until events have successfully been recorded. + * Alternative approaches considered: + * - More aggressive synchronization: Appenders may do expensive I/O that shouldn't block routing. + * - Move the 'updatePurgePolicy' invocation before appenders are called: Unfortunately this approach doesn't work + * if we consider an ImmediatePurgePolicy (or IdlePurgePolicy with a very small timeout) because it may attempt + * to remove an appender that doesn't exist yet. It's counterintuitive to get an event that a route has been + * used at a point when we expect the route doesn't exist in {@link #getAppenders()}. + */ + private static abstract class RouteAppenderControl extends AppenderControl { + + RouteAppenderControl(final Appender appender) { + super(appender, null, null); + } + + abstract void checkout(); + + abstract void release(); + } + + private static final class CreatedRouteAppenderControl extends RouteAppenderControl { + + private volatile boolean pendingDeletion; + private final AtomicInteger depth = new AtomicInteger(); + + CreatedRouteAppenderControl(final Appender appender) { + super(appender); + } + + @Override + void checkout() { + if (pendingDeletion) { + LOGGER.warn("CreatedRouteAppenderControl.checkout invoked on a " + + "RouteAppenderControl that is pending deletion"); + } + depth.incrementAndGet(); + } + + @Override + void release() { + depth.decrementAndGet(); + tryStopAppender(); + } + + void tryStopAppender() { + if (pendingDeletion + // Only attempt to stop the appender if we can CaS the depth away from zero, otherwise either + // 1. Another invocation of tryStopAppender has succeeded, or + // 2. Events are being appended, and will trigger stop when they complete + && depth.compareAndSet(0, -100_000)) { + final Appender appender = getAppender(); + LOGGER.debug("Stopping appender {}", appender); + appender.stop(); + } + } + } + + private static final class ReferencedRouteAppenderControl extends RouteAppenderControl { + + ReferencedRouteAppenderControl(final Appender appender) { + super(appender); + } + + @Override + void checkout() { + // nop + } + + @Override + void release() { + // nop + } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AbstractAsyncExceptionHandler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AbstractAsyncExceptionHandler.java new file mode 100644 index 00000000000..42c53f6cfb8 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AbstractAsyncExceptionHandler.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import com.lmax.disruptor.ExceptionHandler; + +/** + * Default disruptor exception handler for errors that occur in the AsyncLogger background thread. + */ +abstract class AbstractAsyncExceptionHandler implements ExceptionHandler { + + @Override + public void handleEventException(final Throwable throwable, final long sequence, final T event) { + try { + // Careful to avoid allocation in case of memory pressure. + // Sacrifice performance for safety by writing directly + // rather than using a buffer. + System.err.print("AsyncLogger error handling event seq="); + System.err.print(sequence); + System.err.print(", value='"); + try { + System.err.print(event); + } catch (final Throwable t) { + System.err.print("ERROR calling toString() on "); + System.err.print(event.getClass().getName()); + System.err.print(": "); + System.err.print(t.getClass().getName()); + System.err.print(": "); + System.err.print(t.getMessage()); + } + System.err.print("': "); + System.err.print(throwable.getClass().getName()); + System.err.print(": "); + System.err.println(throwable.getMessage()); + // Attempt to print the full stack trace, which may fail if we're already + // OOMing We've already provided sufficient information at this point. + throwable.printStackTrace(); + } catch (final Throwable ignored) { + // LOG4J2-2333: Not much we can do here without risking an OOM. + // Throwing an error here may kill the background thread. + } + } + + @Override + public void handleOnStartException(final Throwable throwable) { + System.err.println("AsyncLogger error starting:"); + throwable.printStackTrace(); + } + + @Override + public void handleOnShutdownException(final Throwable throwable) { + System.err.println("AsyncLogger error shutting down:"); + throwable.printStackTrace(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java index be4bf22ead1..3126a65e76e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java @@ -16,19 +16,20 @@ */ package org.apache.logging.log4j.core.async; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Factory for creating instances of {@link ArrayBlockingQueue}. * * @since 2.7 */ -@Plugin(name = "ArrayBlockingQueue", category = Node.CATEGORY, elementType = BlockingQueueFactory.ELEMENT_TYPE) +@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE) +@Plugin("ArrayBlockingQueue") public class ArrayBlockingQueueFactory implements BlockingQueueFactory { @Override public BlockingQueue create(final int capacity) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java index 358422c20ae..e314e6cf9c5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java @@ -26,13 +26,12 @@ import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.core.config.ReliabilityStrategy; import org.apache.logging.log4j.core.impl.ContextDataFactory; -import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; -import org.apache.logging.log4j.core.util.Clock; -import org.apache.logging.log4j.core.util.ClockFactory; -import org.apache.logging.log4j.core.util.NanoClock; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.message.ReusableMessage; @@ -47,8 +46,8 @@ /** * AsyncLogger is a logger designed for high throughput and low latency logging. It does not perform any I/O in the * calling (application) thread, but instead hands off the work to another thread as soon as possible. The actual - * logging is performed in the background thread. It uses the LMAX Disruptor library for inter-thread communication. (http://lmax-exchange.github.com/disruptor/) + * logging is performed in the background thread. It uses LMAX + * Disruptor for inter-thread communication. *

    * To use AsyncLogger, specify the System property * {@code -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector} before you obtain a @@ -70,8 +69,8 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg + * This re-uses a {@code RingBufferLogEventTranslator} instance cached in a {@code ThreadLocal} to avoid creating + * unnecessary objects with each event. + * + * @param fqcn fully qualified name of the caller + * @param location the Location of the caller. + * @param level level at which the caller wants to log the message + * @param marker message marker + * @param message the log message + * @param thrown a {@code Throwable} or {@code null} + */ + private void logWithThreadLocalTranslator(final String fqcn, final StackTraceElement location, final Level level, + final Marker marker, final Message message, final Throwable thrown) { + // Implementation note: this method is tuned for performance. MODIFY WITH CARE! + + final RingBufferLogEventTranslator translator = getCachedTranslator(); + initTranslator(translator, fqcn, location, level, marker, message, thrown); + initTranslatorThreadValues(translator); + publish(translator); + } + private void publish(final RingBufferLogEventTranslator translator) { if (!loggerDisruptor.tryPublish(translator)) { handleRingBufferFull(translator); @@ -170,27 +236,48 @@ private void publish(final RingBufferLogEventTranslator translator) { private void handleRingBufferFull(final RingBufferLogEventTranslator translator) { if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock - final Message message = AsyncQueueFullMessageUtil.transform(translator.message); - logMessageInCurrentThread(translator.fqcn, translator.level, translator.marker, message, + AsyncQueueFullMessageUtil.logWarningToStatusLogger(); + logMessageInCurrentThread(translator.fqcn, translator.level, translator.marker, translator.message, translator.thrown); + translator.clear(); return; } final EventRoute eventRoute = loggerDisruptor.getEventRoute(translator.level); switch (eventRoute) { case ENQUEUE: - loggerDisruptor.enqueueLogMessageInfo(translator); + loggerDisruptor.enqueueLogMessageWhenQueueFull(translator); break; case SYNCHRONOUS: logMessageInCurrentThread(translator.fqcn, translator.level, translator.marker, translator.message, translator.thrown); + translator.clear(); break; case DISCARD: + translator.clear(); break; default: throw new IllegalStateException("Unknown EventRoute " + eventRoute); } } + private void initTranslator(final RingBufferLogEventTranslator translator, final String fqcn, + final StackTraceElement location, final Level level, final Marker marker, + final Message message, final Throwable thrown) { + + translator.setBasicValues(this, name, marker, fqcn, level, message, // + // don't construct ThrowableProxy until required + thrown, + + // needs shallow copy to be fast (LOG4J2-154) + ThreadContext.getImmutableStack(), // + + location, + clock, // + nanoClock, // + contextDataInjector + ); + } + private void initTranslator(final RingBufferLogEventTranslator translator, final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) { @@ -203,8 +290,9 @@ private void initTranslator(final RingBufferLogEventTranslator translator, final // location (expensive to calculate) calcLocationIfRequested(fqcn), // - CLOCK, // - nanoClock // + clock, // + nanoClock, // + contextDataInjector ); } @@ -267,6 +355,45 @@ private void logWithVarargTranslator(final String fqcn, final Level level, final } } + /** + * Enqueues the specified log event data for logging in a background thread. + *

    + * This creates a new varargs Object array for each invocation, but does not store any non-JDK classes in a + * {@code ThreadLocal} to avoid memory leaks in web applications (see LOG4J2-1172). + * + * @param fqcn fully qualified name of the caller + * @param location location of the caller. + * @param level level at which the caller wants to log the message + * @param marker message marker + * @param message the log message + * @param thrown a {@code Throwable} or {@code null} + */ + private void logWithVarargTranslator(final String fqcn, final StackTraceElement location, final Level level, + final Marker marker, final Message message, final Throwable thrown) { + // Implementation note: candidate for optimization: exceeds 35 bytecodes. + + final Disruptor disruptor = loggerDisruptor.getDisruptor(); + if (disruptor == null) { + LOGGER.error("Ignoring log event after Log4j has been shut down."); + return; + } + // if the Message instance is reused, there is no point in freezing its message here + if (!isReused(message)) { + InternalAsyncUtil.makeMessageImmutable(message); + } + // calls the translateTo method on this AsyncLogger + if (!disruptor.getRingBuffer().tryPublishEvent(this, + this, // asyncLogger: 0 + location, // location: 1 + fqcn, // 2 + level, // 3 + marker, // 4 + message, // 5 + thrown)) { // 6 + handleRingBufferFull(location, fqcn, level, marker, message, thrown); + } + } + /* * (non-Javadoc) * @@ -291,9 +418,9 @@ public void translateTo(final RingBufferLogEvent event, final long sequence, fin event.setValues(asyncLogger, asyncLogger.getName(), marker, fqcn, level, message, thrown, // config properties are taken care of in the EventHandler thread // in the AsyncLogger#actualAsyncLog method - CONTEXT_DATA_INJECTOR.injectContextData(null, (StringMap) event.getContextData()), + contextDataInjector.injectContextData(null, (StringMap) event.getContextData()), contextStack, currentThread.getId(), threadName, currentThread.getPriority(), location, - CLOCK, nanoClock); + clock, nanoClock); } /** @@ -321,14 +448,14 @@ private void handleRingBufferFull(final StackTraceElement location, final Throwable thrown) { if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock - final Message message = AsyncQueueFullMessageUtil.transform(msg); - logMessageInCurrentThread(fqcn, level, marker, message, thrown); + AsyncQueueFullMessageUtil.logWarningToStatusLogger(); + logMessageInCurrentThread(fqcn, level, marker, msg, thrown); return; } final EventRoute eventRoute = loggerDisruptor.getEventRoute(level); switch (eventRoute) { case ENQUEUE: - loggerDisruptor.getDisruptor().getRingBuffer().publishEvent(this, + loggerDisruptor.enqueueLogMessageWhenQueueFull(this, this, // asyncLogger: 0 location, // location: 1 fqcn, // 2 @@ -355,29 +482,44 @@ private void handleRingBufferFull(final StackTraceElement location, * @param event the event to log */ public void actualAsyncLog(final RingBufferLogEvent event) { - final List properties = privateConfig.loggerConfig.getPropertyList(); + final LoggerConfig privateConfigLoggerConfig = privateConfig.loggerConfig; + final List properties = privateConfigLoggerConfig.getPropertyList(); if (properties != null) { - StringMap contextData = (StringMap) event.getContextData(); - if (contextData.isFrozen()) { - final StringMap temp = ContextDataFactory.createContextData(); - temp.putAll(contextData); - contextData = temp; - } - for (int i = 0; i < properties.size(); i++) { - final Property prop = properties.get(i); - if (contextData.getValue(prop.getName()) != null) { - continue; // contextMap overrides config properties - } - final String value = prop.isValueNeedsLookup() // - ? privateConfig.config.getStrSubstitutor().replace(event, prop.getValue()) // - : prop.getValue(); - contextData.putValue(prop.getName(), value); + onPropertiesPresent(event, properties); + } + + privateConfigLoggerConfig.getReliabilityStrategy().log(this, event); + } + + @SuppressWarnings("ForLoopReplaceableByForEach") // Avoid iterator allocation + private void onPropertiesPresent(final RingBufferLogEvent event, final List properties) { + final StringMap contextData = getContextData(event); + for (int i = 0, size = properties.size(); i < size; i++) { + final Property prop = properties.get(i); + if (contextData.getValue(prop.getName()) != null) { + continue; // contextMap overrides config properties } - event.setContextData(contextData); + final String value = prop.isValueNeedsLookup() // + ? privateConfig.config.getStrSubstitutor().replace(event, prop.getValue()) // + : prop.getValue(); + contextData.putValue(prop.getName(), value); } + event.setContextData(contextData); + } - final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy(); - strategy.log(this, event); + private static StringMap getContextData(final RingBufferLogEvent event) { + final StringMap contextData = (StringMap) event.getContextData(); + if (contextData.isFrozen()) { + final StringMap temp = ContextDataFactory.createContextData(); + temp.putAll(contextData); + return temp; + } + return contextData; + } + + // package-protected for tests + AsyncLoggerDisruptor getAsyncLoggerDisruptor() { + return loggerDisruptor; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java index 56744ce0c85..7593f7fb520 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java @@ -16,32 +16,27 @@ */ package org.apache.logging.log4j.core.async; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.AppenderRef; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.LogEventFactory; import org.apache.logging.log4j.core.jmx.RingBufferAdmin; import org.apache.logging.log4j.core.util.Booleans; -import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.spi.AbstractLogger; import org.apache.logging.log4j.util.Strings; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + /** * Asynchronous Logger object that is created via configuration and can be * combined with synchronous loggers. @@ -49,10 +44,9 @@ * AsyncLoggerConfig is a logger designed for high throughput and low latency * logging. It does not perform any I/O in the calling (application) thread, but * instead hands off the work to another thread as soon as possible. The actual - * logging is performed in the background thread. It uses the LMAX Disruptor - * library for inter-thread communication. (http://lmax-exchange.github.com/disruptor/) + * logging is performed in the background thread. It uses + * LMAX Disruptor + * for inter-thread communication. *

    * To use AsyncLoggerConfig, specify {@code } or * {@code } in configuration. @@ -70,40 +64,97 @@ * with immediateFlush=false, there will never be any items left in the buffer; * all log events will all be written to disk in a very efficient manner. */ -@Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin("asyncLogger") public class AsyncLoggerConfig extends LoggerConfig { + private static final ThreadLocal ASYNC_LOGGER_ENTERED = ThreadLocal.withInitial(() -> Boolean.FALSE); private final AsyncLoggerConfigDelegate delegate; - protected AsyncLoggerConfig(final String name, + @PluginFactory + public static > B newAsyncBuilder() { + return new Builder().asBuilder(); + } + + public static class Builder> extends LoggerConfig.Builder { + + @Override + public LoggerConfig build() { + final String name = getLoggerName().equals(ROOT) ? Strings.EMPTY : getLoggerName(); + LevelAndRefs container = LoggerConfig.getLevelAndRefs(getLevel(), getRefs(), getLevelAndRefs(), + getConfig()); + return new AsyncLoggerConfig(name, container.refs,getFilter(), container.level, isAdditivity(), + getProperties(), getConfig(), includeLocation(getIncludeLocation()), getLogEventFactory()); + } + } + + protected AsyncLoggerConfig( + final String name, final List appenders, final Filter filter, final Level level, final boolean additive, final Property[] properties, final Configuration config, - final boolean includeLocation) { + final boolean includeLocation, final LogEventFactory logEventFactory) { super(name, appenders, filter, level, additive, properties, config, - includeLocation); + includeLocation, logEventFactory); delegate = config.getAsyncLoggerConfigDelegate(); delegate.setLogEventFactory(getLogEventFactory()); } - /** - * Passes on the event to a separate thread that will call - * {@link #asyncCallAppenders(LogEvent)}. - */ + protected void log(final LogEvent event, final LoggerConfigPredicate predicate) { + // See LOG4J2-2301 + if (predicate == LoggerConfigPredicate.ALL && + ASYNC_LOGGER_ENTERED.get() == Boolean.FALSE && + // Optimization: AsyncLoggerConfig is identical to LoggerConfig + // when no appenders are present. Avoid splitting for synchronous + // and asynchronous execution paths until encountering an + // AsyncLoggerConfig with appenders. + hasAppenders()) { + // This is the first AsnycLoggerConfig encountered by this LogEvent + ASYNC_LOGGER_ENTERED.set(Boolean.TRUE); + try { + // Detect the first time we encounter an AsyncLoggerConfig. We must log + // to all non-async loggers first. + super.log(event, LoggerConfigPredicate.SYNCHRONOUS_ONLY); + // Then pass the event to the background thread where + // all async logging is executed. It is important this + // happens at most once and after all synchronous loggers + // have been invoked, because we lose parameter references + // from reusable messages. + logToAsyncDelegate(event); + } finally { + ASYNC_LOGGER_ENTERED.set(Boolean.FALSE); + } + } else { + super.log(event, predicate); + } + } + + // package-protected for testing + AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() { + return delegate; + } + @Override protected void callAppenders(final LogEvent event) { - populateLazilyInitializedFields(event); + super.callAppenders(event); + } - if (!delegate.tryEnqueue(event, this)) { - handleQueueFull(event); + private void logToAsyncDelegate(final LogEvent event) { + if (!isFiltered(event)) { + // Passes on the event to a separate thread that will call + // asyncCallAppenders(LogEvent). + populateLazilyInitializedFields(event); + if (!delegate.tryEnqueue(event, this)) { + handleQueueFull(event); + } } } private void handleQueueFull(final LogEvent event) { if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031 // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock - final Message message = AsyncQueueFullMessageUtil.transform(event.getMessage()); - callAppendersInCurrentThread(new Log4jLogEvent.Builder(event).setMessage(message).build()); + AsyncQueueFullMessageUtil.logWarningToStatusLogger(); + logToAsyncLoggerConfigsOnCurrentThread(event); } else { // otherwise, we leave it to the user preference final EventRoute eventRoute = delegate.getEventRoute(event.getLevel()); @@ -116,17 +167,18 @@ private void populateLazilyInitializedFields(final LogEvent event) { event.getThreadName(); } - void callAppendersInCurrentThread(final LogEvent event) { - super.callAppenders(event); - } - - void callAppendersInBackgroundThread(final LogEvent event) { + void logInBackgroundThread(final LogEvent event) { delegate.enqueueEvent(event, this); } - /** Called by AsyncLoggerConfigHelper.RingBufferLog4jEventHandler. */ - void asyncCallAppenders(final LogEvent event) { - super.callAppenders(event); + /** + * Called by AsyncLoggerConfigHelper.RingBufferLog4jEventHandler. + * + * This method will log the provided event to only configs of type {@link AsyncLoggerConfig} (not + * default {@link LoggerConfig} definitions), which will be invoked on the calling thread. + */ + void logToAsyncLoggerConfigsOnCurrentThread(final LogEvent event) { + log(event, LoggerConfigPredicate.ASYNCHRONOUS_ONLY); } private String displayName() { @@ -171,17 +223,18 @@ public RingBufferAdmin createRingBufferAdmin(final String contextName) { * @param config The Configuration. * @param filter A Filter. * @return A new LoggerConfig. + * @deprecated use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)} */ - @PluginFactory + @Deprecated public static LoggerConfig createLogger( - @PluginAttribute("additivity") final String additivity, - @PluginAttribute("level") final String levelName, - @PluginAttribute("name") final String loggerName, - @PluginAttribute("includeLocation") final String includeLocation, - @PluginElement("AppenderRef") final AppenderRef[] refs, - @PluginElement("Properties") final Property[] properties, - @PluginConfiguration final Configuration config, - @PluginElement("Filter") final Filter filter) { + final String additivity, + final String levelName, + final String loggerName, + final String includeLocation, + final AppenderRef[] refs, + final Property[] properties, + final Configuration config, + final Filter filter) { if (loggerName == null) { LOGGER.error("Loggers cannot be configured without a name"); return null; @@ -201,7 +254,31 @@ public static LoggerConfig createLogger( final boolean additive = Booleans.parseBoolean(additivity, true); return new AsyncLoggerConfig(name, appenderRefs, filter, level, - additive, properties, config, includeLocation(includeLocation)); + additive, properties, config, includeLocation(includeLocation), + config.getComponent(LogEventFactory.KEY)); + } + + /** + * Factory method to create a LoggerConfig. + * + * @param additivity True if additive, false otherwise. + * @param level The Level to be associated with the Logger. + * @param loggerName The name of the Logger. + * @param includeLocation "true" if location should be passed downstream + * @param refs An array of Appender names. + * @param properties Properties to pass to the Logger. + * @param config The Configuration. + * @param filter A Filter. + * @return A new LoggerConfig. + * @since 3.0 + */ + @Deprecated + public static LoggerConfig createLogger( + final boolean additivity, final Level level, final String loggerName, final String includeLocation, + final AppenderRef[] refs, final Property[] properties, final Configuration config, final Filter filter) { + final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; + return new AsyncLoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, + includeLocation(includeLocation), config.getComponent(LogEventFactory.KEY)); } // Note: for asynchronous loggers, includeLocation default is FALSE @@ -212,33 +289,36 @@ protected static boolean includeLocation(final String includeLocationConfigValue /** * An asynchronous root Logger. */ - @Plugin(name = "asyncRoot", category = Core.CATEGORY_NAME, printObject = true) + @Configurable(printObject = true) + @Plugin("asyncRoot") public static class RootLogger extends LoggerConfig { @PluginFactory - public static LoggerConfig createLogger( - @PluginAttribute("additivity") final String additivity, - @PluginAttribute("level") final String levelName, - @PluginAttribute("includeLocation") final String includeLocation, - @PluginElement("AppenderRef") final AppenderRef[] refs, - @PluginElement("Properties") final Property[] properties, - @PluginConfiguration final Configuration config, - @PluginElement("Filter") final Filter filter) { - final List appenderRefs = Arrays.asList(refs); - Level level; - try { - level = Level.toLevel(levelName, Level.ERROR); - } catch (final Exception ex) { - LOGGER.error( - "Invalid Log level specified: {}. Defaulting to Error", - levelName); - level = Level.ERROR; + public static > B newAsyncRootBuilder() { + return new Builder().asBuilder(); + } + + public static class Builder> extends RootLogger.Builder { + + @Override + public LoggerConfig build() { + LevelAndRefs container = LoggerConfig.getLevelAndRefs(getLevel(), getRefs(), getLevelAndRefs(), + getConfig()); + return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, container.refs, getFilter(), container.level, + isAdditivity(), getProperties(), getConfig(), + AsyncLoggerConfig.includeLocation(getIncludeLocation()), getLogEventFactory()); } - final boolean additive = Booleans.parseBoolean(additivity, true); + } - return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, - appenderRefs, filter, level, additive, properties, config, - AsyncLoggerConfig.includeLocation(includeLocation)); + @Deprecated + public static LoggerConfig createLogger(final String additivity, final Level level, final String includeLocation, + final AppenderRef[] refs, final Property[] properties, final Configuration config, final Filter filter) { + final List appenderRefs = Arrays.asList(refs); + final Level actualLevel = level == null ? Level.ERROR : level; + final boolean additive = Booleans.parseBoolean(additivity, true); + return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, + properties, config, AsyncLoggerConfig.includeLocation(includeLocation), + config.getComponent(LogEventFactory.KEY)); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDefaultExceptionHandler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDefaultExceptionHandler.java index 961c349e43a..a0faa6987a3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDefaultExceptionHandler.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDefaultExceptionHandler.java @@ -16,39 +16,9 @@ */ package org.apache.logging.log4j.core.async; -import com.lmax.disruptor.ExceptionHandler; - /** * Default disruptor exception handler for errors that occur in the AsyncLogger background thread. */ public class AsyncLoggerConfigDefaultExceptionHandler - implements ExceptionHandler { - - @Override - public void handleEventException(final Throwable throwable, final long sequence, - final AsyncLoggerConfigDisruptor.Log4jEventWrapper event) { - final StringBuilder sb = new StringBuilder(512); - sb.append("AsyncLogger error handling event seq=").append(sequence).append(", value='"); - try { - sb.append(event); - } catch (final Exception ignored) { - sb.append("[ERROR calling ").append(event.getClass()).append(".toString(): "); - sb.append(ignored).append("]"); - } - sb.append("':"); - System.err.println(sb); - throwable.printStackTrace(); - } - - @Override - public void handleOnStartException(final Throwable throwable) { - System.err.println("AsyncLogger error starting:"); - throwable.printStackTrace(); - } - - @Override - public void handleOnShutdownException(final Throwable throwable) { - System.err.println("AsyncLogger error shutting down:"); - throwable.printStackTrace(); - } + extends AbstractAsyncExceptionHandler { } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java index 0e230bb7e60..b791f0afe41 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java @@ -46,13 +46,18 @@ public interface AsyncLoggerConfigDelegate { */ EventRoute getEventRoute(final Level level); + /** + * Enqueues the {@link LogEvent} on the mixed configuration ringbuffer. + * This method must only be used after {@link #tryEnqueue(LogEvent, AsyncLoggerConfig)} returns false + * indicating that the ringbuffer is full, otherwise it may incur unnecessary synchronization. + */ void enqueueEvent(LogEvent event, AsyncLoggerConfig asyncLoggerConfig); boolean tryEnqueue(LogEvent event, AsyncLoggerConfig asyncLoggerConfig); /** * Notifies the delegate what LogEventFactory an AsyncLoggerConfig is using, so the delegate can determine - * whether to populate the ring buffer with mutable log events or not. This method may be invoced multiple times + * whether to populate the ring buffer with mutable log events or not. This method may be invoked multiple times * for all AsyncLoggerConfigs that use this delegate. * * @param logEventFactory the factory used diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java index cc3e77b6777..f40c90b8e8c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java @@ -23,10 +23,12 @@ import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.impl.LogEventFactory; import org.apache.logging.log4j.core.impl.MutableLogEvent; import org.apache.logging.log4j.core.impl.ReusableLogEventFactory; import org.apache.logging.log4j.core.jmx.RingBufferAdmin; +import org.apache.logging.log4j.core.util.Log4jThread; import org.apache.logging.log4j.core.util.Log4jThreadFactory; import org.apache.logging.log4j.core.util.Throwables; import org.apache.logging.log4j.message.ReusableMessage; @@ -108,7 +110,7 @@ public void setSequenceCallback(final Sequence sequenceCallback) { public void onEvent(final Log4jEventWrapper event, final long sequence, final boolean endOfBatch) throws Exception { event.event.setEndOfBatch(endOfBatch); - event.loggerConfig.asyncCallAppenders(event.event); + event.loggerConfig.logToAsyncLoggerConfigsOnCurrentThread(event.event); event.clear(); notifyIntermediateProgress(sequence); @@ -130,51 +132,31 @@ private void notifyIntermediateProgress(final long sequence) { * Factory used to populate the RingBuffer with events. These event objects are then re-used during the life of the * RingBuffer. */ - private static final EventFactory FACTORY = new EventFactory() { - @Override - public Log4jEventWrapper newInstance() { - return new Log4jEventWrapper(); - } - }; + private static final EventFactory FACTORY = Log4jEventWrapper::new; /** * Factory used to populate the RingBuffer with events. These event objects are then re-used during the life of the * RingBuffer. */ - private static final EventFactory MUTABLE_FACTORY = new EventFactory() { - @Override - public Log4jEventWrapper newInstance() { - return new Log4jEventWrapper(new MutableLogEvent()); - } - }; + private static final EventFactory MUTABLE_FACTORY = () -> new Log4jEventWrapper(new MutableLogEvent()); /** * Object responsible for passing on data to a specific RingBuffer event. */ private static final EventTranslatorTwoArg TRANSLATOR = - new EventTranslatorTwoArg() { - - @Override - public void translateTo(final Log4jEventWrapper ringBufferElement, final long sequence, - final LogEvent logEvent, final AsyncLoggerConfig loggerConfig) { - ringBufferElement.event = logEvent; - ringBufferElement.loggerConfig = loggerConfig; - } - }; + (ringBufferElement, sequence, logEvent, loggerConfig) -> { + ringBufferElement.event = logEvent; + ringBufferElement.loggerConfig = loggerConfig; + }; /** * Object responsible for passing on data to a RingBuffer event with a MutableLogEvent. */ private static final EventTranslatorTwoArg MUTABLE_TRANSLATOR = - new EventTranslatorTwoArg() { - - @Override - public void translateTo(final Log4jEventWrapper ringBufferElement, final long sequence, - final LogEvent logEvent, final AsyncLoggerConfig loggerConfig) { - ((MutableLogEvent) ringBufferElement.event).initFrom(logEvent); - ringBufferElement.loggerConfig = loggerConfig; - } - }; + (ringBufferElement, sequence, logEvent, loggerConfig) -> { + ((MutableLogEvent) ringBufferElement.event).initFrom(logEvent); + ringBufferElement.loggerConfig = loggerConfig; + }; private int ringBufferSize; private AsyncQueueFullPolicy asyncQueueFullPolicy; @@ -184,9 +166,19 @@ public void translateTo(final Log4jEventWrapper ringBufferElement, final long se private long backgroundThreadId; // LOG4J2-471 private EventFactory factory; private EventTranslatorTwoArg translator; - private volatile boolean alreadyLoggedWarning = false; + private volatile boolean alreadyLoggedWarning; + private final AsyncWaitStrategyFactory asyncWaitStrategyFactory; + private WaitStrategy waitStrategy; + + private final Object queueFullEnqueueLock = new Object(); + + public AsyncLoggerConfigDisruptor(AsyncWaitStrategyFactory asyncWaitStrategyFactory) { + this.asyncWaitStrategyFactory = asyncWaitStrategyFactory; // may be null + } - public AsyncLoggerConfigDisruptor() { + // package-protected for testing + WaitStrategy getWaitStrategy() { + return waitStrategy; } // called from AsyncLoggerConfig constructor @@ -211,10 +203,11 @@ public synchronized void start() { return; } LOGGER.trace("AsyncLoggerConfigDisruptor creating new disruptor for this configuration."); - ringBufferSize = DisruptorUtil.calculateRingBufferSize("AsyncLoggerConfig.RingBufferSize"); - final WaitStrategy waitStrategy = DisruptorUtil.createWaitStrategy("AsyncLoggerConfig.WaitStrategy"); + ringBufferSize = DisruptorUtil.calculateRingBufferSize(Log4jProperties.ASYNC_CONFIG_RING_BUFFER_SIZE); + waitStrategy = DisruptorUtil.createWaitStrategy( + Log4jProperties.ASYNC_CONFIG_WAIT_STRATEGY, asyncWaitStrategyFactory); - final ThreadFactory threadFactory = new Log4jThreadFactory("AsyncLoggerConfig-", true, Thread.NORM_PRIORITY) { + final ThreadFactory threadFactory = new Log4jThreadFactory("AsyncLoggerConfig", true, Thread.NORM_PRIORITY) { @Override public Thread newThread(final Runnable r) { final Thread result = super.newThread(r); @@ -369,7 +362,24 @@ private void showWarningAboutCustomLogEventWithReusableMessage(final LogEvent lo } private void enqueue(final LogEvent logEvent, final AsyncLoggerConfig asyncLoggerConfig) { - disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig); + if (synchronizeEnqueueWhenQueueFull()) { + synchronized (queueFullEnqueueLock) { + disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig); + } + } else { + disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig); + } + } + + private boolean synchronizeEnqueueWhenQueueFull() { + return DisruptorUtil.ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL + // Background thread must never block + && backgroundThreadId != Thread.currentThread().getId() + // Threads owned by log4j are most likely to result in + // deadlocks because they generally consume events. + // This prevents deadlocks between AsyncLoggerContext + // disruptors. + && !(Thread.currentThread() instanceof Log4jThread); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java index 68326f75ccd..ddb1c47ba49 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java @@ -16,17 +16,18 @@ */ package org.apache.logging.log4j.core.async; -import java.net.URI; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.jmx.RingBufferAdmin; import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.plugins.di.Injector; import org.apache.logging.log4j.status.StatusLogger; +import java.net.URI; +import java.util.concurrent.TimeUnit; + /** * {@code LoggerContext} that creates {@code AsyncLogger} objects. */ @@ -36,29 +37,40 @@ public class AsyncLoggerContext extends LoggerContext { public AsyncLoggerContext(final String name) { super(name); - loggerDisruptor = new AsyncLoggerDisruptor(name); + loggerDisruptor = new AsyncLoggerDisruptor(name, () -> getConfiguration().getAsyncWaitStrategyFactory()); } public AsyncLoggerContext(final String name, final Object externalContext) { super(name, externalContext); - loggerDisruptor = new AsyncLoggerDisruptor(name); + loggerDisruptor = new AsyncLoggerDisruptor(name, () -> getConfiguration().getAsyncWaitStrategyFactory()); } public AsyncLoggerContext(final String name, final Object externalContext, final URI configLocn) { super(name, externalContext, configLocn); - loggerDisruptor = new AsyncLoggerDisruptor(name); + loggerDisruptor = new AsyncLoggerDisruptor(name, () -> getConfiguration().getAsyncWaitStrategyFactory()); + } + + public AsyncLoggerContext(final String name, final Object externalContext, final URI configLocn, final Injector injector) { + super(name, externalContext, configLocn, injector); + loggerDisruptor = new AsyncLoggerDisruptor(name, () -> getConfiguration().getAsyncWaitStrategyFactory()); } public AsyncLoggerContext(final String name, final Object externalContext, final String configLocn) { super(name, externalContext, configLocn); - loggerDisruptor = new AsyncLoggerDisruptor(name); + loggerDisruptor = new AsyncLoggerDisruptor(name, () -> getConfiguration().getAsyncWaitStrategyFactory()); + } + + public AsyncLoggerContext( + final String name, final Object externalContext, final String configLocn, final Injector injector) { + super(name, externalContext, configLocn, injector); + loggerDisruptor = new AsyncLoggerDisruptor(name, () -> getConfiguration().getAsyncWaitStrategyFactory()); } @Override protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { return new AsyncLogger(ctx, name, messageFactory, loggerDisruptor); } - + @Override public void setName(final String name) { super.setName("AsyncContext[" + name + "]"); @@ -67,7 +79,7 @@ public void setName(final String name) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.LoggerContext#start() */ @Override @@ -78,7 +90,7 @@ public void start() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.LoggerContext#start(org.apache.logging.log4j.core.config.Configuration) */ @Override @@ -102,7 +114,7 @@ private void maybeStartHelper(final Configuration config) { public boolean stop(final long timeout, final TimeUnit timeUnit) { setStopping(); // first stop Disruptor - loggerDisruptor.stop(timeout, timeUnit); + loggerDisruptor.stop(timeout, timeUnit); super.stop(timeout, timeUnit); return true; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java index 358b2653a5d..37f585e7e0c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.java @@ -19,8 +19,11 @@ import java.net.URI; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; -import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.di.Injector; import org.apache.logging.log4j.util.PropertiesUtil; /** @@ -28,22 +31,29 @@ *

    * As of version 2.5, this class extends ClassLoaderContextSelector for better web app support. */ +@Singleton public class AsyncLoggerContextSelector extends ClassLoaderContextSelector { /** * Returns {@code true} if the user specified this selector as the Log4jContextSelector, to make all loggers * asynchronous. - * + * * @return {@code true} if all loggers are asynchronous, {@code false} otherwise. */ public static boolean isSelected() { + // FIXME(ms): this should check Injector bindings return AsyncLoggerContextSelector.class.getName().equals( - PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR)); + PropertiesUtil.getProperties().getStringProperty(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME)); + } + + @Inject + public AsyncLoggerContextSelector(final Injector injector) { + super(injector); } @Override - protected LoggerContext createContext(final String name, final URI configLocation) { - return new AsyncLoggerContext(name, null, configLocation); + protected LoggerContext createContext(final String name, final URI configLocation, final Injector injector) { + return new AsyncLoggerContext(name, null, configLocation, injector); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultExceptionHandler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultExceptionHandler.java index e224e1e0ffa..6f1eea5b25d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultExceptionHandler.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDefaultExceptionHandler.java @@ -16,37 +16,8 @@ */ package org.apache.logging.log4j.core.async; -import com.lmax.disruptor.ExceptionHandler; - /** * Default disruptor exception handler for errors that occur in the AsyncLogger background thread. */ -public class AsyncLoggerDefaultExceptionHandler implements ExceptionHandler { - - @Override - public void handleEventException(final Throwable throwable, final long sequence, final RingBufferLogEvent event) { - final StringBuilder sb = new StringBuilder(512); - sb.append("AsyncLogger error handling event seq=").append(sequence).append(", value='"); - try { - sb.append(event); - } catch (final Exception ignored) { - sb.append("[ERROR calling ").append(event.getClass()).append(".toString(): "); - sb.append(ignored).append("]"); - } - sb.append("':"); - System.err.println(sb); - throwable.printStackTrace(); - } - - @Override - public void handleOnStartException(final Throwable throwable) { - System.err.println("AsyncLogger error starting:"); - throwable.printStackTrace(); - } - - @Override - public void handleOnShutdownException(final Throwable throwable) { - System.err.println("AsyncLogger error shutting down:"); - throwable.printStackTrace(); - } +public class AsyncLoggerDefaultExceptionHandler extends AbstractAsyncExceptionHandler { } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java index 7a7e546a8ca..80ecc8e921f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java @@ -14,18 +14,24 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.async; +import java.util.Objects; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.AbstractLifeCycle; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.jmx.RingBufferAdmin; +import org.apache.logging.log4j.core.util.Log4jThread; import org.apache.logging.log4j.core.util.Log4jThreadFactory; import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.message.Message; +import com.lmax.disruptor.EventTranslatorVararg; import com.lmax.disruptor.ExceptionHandler; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.TimeoutException; @@ -43,16 +49,26 @@ class AsyncLoggerDisruptor extends AbstractLifeCycle { private static final int SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS = 50; private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 200; + private final Object queueFullEnqueueLock = new Object(); + private volatile Disruptor disruptor; private String contextName; + private final Supplier waitStrategyFactorySupplier; private boolean useThreadLocalTranslator = true; private long backgroundThreadId; private AsyncQueueFullPolicy asyncQueueFullPolicy; private int ringBufferSize; + private WaitStrategy waitStrategy; - AsyncLoggerDisruptor(final String contextName) { + AsyncLoggerDisruptor(final String contextName, final Supplier waitStrategyFactorySupplier) { this.contextName = contextName; + this.waitStrategyFactorySupplier = Objects.requireNonNull(waitStrategyFactorySupplier, "waitStrategyFactorySupplier"); + } + + // package-protected for testing + WaitStrategy getWaitStrategy() { + return waitStrategy; } public String getContextName() { @@ -80,9 +96,15 @@ public synchronized void start() { contextName); return; } + if (isStarting()) { + LOGGER.trace("[{}] AsyncLoggerDisruptor is already starting.", contextName); + return; + } + setStarting(); LOGGER.trace("[{}] AsyncLoggerDisruptor creating new disruptor for this context.", contextName); - ringBufferSize = DisruptorUtil.calculateRingBufferSize("AsyncLogger.RingBufferSize"); - final WaitStrategy waitStrategy = DisruptorUtil.createWaitStrategy("AsyncLogger.WaitStrategy"); + ringBufferSize = DisruptorUtil.calculateRingBufferSize(Log4jProperties.ASYNC_LOGGER_RING_BUFFER_SIZE); + AsyncWaitStrategyFactory factory = waitStrategyFactorySupplier.get(); // get factory from configuration + waitStrategy = DisruptorUtil.createWaitStrategy(Log4jProperties.ASYNC_LOGGER_WAIT_STRATEGY, factory); final ThreadFactory threadFactory = new Log4jThreadFactory("AsyncLogger[" + contextName + "]", true, Thread.NORM_PRIORITY) { @Override @@ -202,32 +224,99 @@ private boolean hasLog4jBeenShutDown(final Disruptor aDisrup return false; } - public boolean tryPublish(final RingBufferLogEventTranslator translator) { + boolean tryPublish(final RingBufferLogEventTranslator translator) { try { + // Note: we deliberately access the volatile disruptor field afresh here. + // Avoiding this and using an older reference could result in adding a log event to the disruptor after it + // was shut down, which could cause the publishEvent method to hang and never return. return disruptor.getRingBuffer().tryPublishEvent(translator); } catch (final NullPointerException npe) { // LOG4J2-639: catch NPE if disruptor field was set to null in stop() - LOGGER.warn("[{}] Ignoring log event after log4j was shut down: {} [{}] {}", contextName, - translator.level, translator.loggerName, translator.message.getFormattedMessage() - + (translator.thrown == null ? "" : Throwables.toStringList(translator.thrown))); + logWarningOnNpeFromDisruptorPublish(translator); return false; } } - void enqueueLogMessageInfo(final RingBufferLogEventTranslator translator) { + void enqueueLogMessageWhenQueueFull(final RingBufferLogEventTranslator translator) { + try { + // Note: we deliberately access the volatile disruptor field afresh here. + // Avoiding this and using an older reference could result in adding a log event to the disruptor after it + // was shut down, which could cause the publishEvent method to hang and never return. + if (synchronizeEnqueueWhenQueueFull()) { + synchronized (queueFullEnqueueLock) { + disruptor.publishEvent(translator); + } + } else { + disruptor.publishEvent(translator); + } + } catch (final NullPointerException npe) { + // LOG4J2-639: catch NPE if disruptor field was set to null in stop() + logWarningOnNpeFromDisruptorPublish(translator); + } + } + + void enqueueLogMessageWhenQueueFull( + final EventTranslatorVararg translator, + final AsyncLogger asyncLogger, + final StackTraceElement location, + final String fqcn, + final Level level, + final Marker marker, + final Message msg, + final Throwable thrown) { try { // Note: we deliberately access the volatile disruptor field afresh here. // Avoiding this and using an older reference could result in adding a log event to the disruptor after it // was shut down, which could cause the publishEvent method to hang and never return. - disruptor.publishEvent(translator); + if (synchronizeEnqueueWhenQueueFull()) { + synchronized (queueFullEnqueueLock) { + disruptor.getRingBuffer().publishEvent(translator, + asyncLogger, // asyncLogger: 0 + location, // location: 1 + fqcn, // 2 + level, // 3 + marker, // 4 + msg, // 5 + thrown); // 6 + } + } else { + disruptor.getRingBuffer().publishEvent(translator, + asyncLogger, // asyncLogger: 0 + location, // location: 1 + fqcn, // 2 + level, // 3 + marker, // 4 + msg, // 5 + thrown); // 6 + } } catch (final NullPointerException npe) { // LOG4J2-639: catch NPE if disruptor field was set to null in stop() - LOGGER.warn("[{}] Ignoring log event after log4j was shut down: {} [{}] {}", contextName, - translator.level, translator.loggerName, translator.message.getFormattedMessage() - + (translator.thrown == null ? "" : Throwables.toStringList(translator.thrown))); + logWarningOnNpeFromDisruptorPublish(level, fqcn, msg, thrown); } } + private boolean synchronizeEnqueueWhenQueueFull() { + return DisruptorUtil.ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL + // Background thread must never block + && backgroundThreadId != Thread.currentThread().getId() + // Threads owned by log4j are most likely to result in + // deadlocks because they generally consume events. + // This prevents deadlocks between AsyncLoggerContext + // disruptors. + && !(Thread.currentThread() instanceof Log4jThread); + } + + private void logWarningOnNpeFromDisruptorPublish(final RingBufferLogEventTranslator translator) { + logWarningOnNpeFromDisruptorPublish( + translator.level, translator.loggerName, translator.message, translator.thrown); + } + + private void logWarningOnNpeFromDisruptorPublish( + final Level level, final String fqcn, final Message msg, final Throwable thrown) { + LOGGER.warn("[{}] Ignoring log event after log4j was shut down: {} [{}] {}{}", contextName, + level, fqcn, msg.getFormattedMessage(), thrown == null ? "" : Throwables.toStringList(thrown)); + } + /** * Returns whether it is allowed to store non-JDK classes in ThreadLocal objects for efficiency. * diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullMessageUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullMessageUtil.java index 9609bbd401b..9c0956b6aa5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullMessageUtil.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullMessageUtil.java @@ -16,30 +16,25 @@ */ package org.apache.logging.log4j.core.async; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.status.StatusLogger; /** * Consider this class private. *

    - * Transforms the specified user message to append an internal Log4j2 message explaining why this message appears out - * of order in the appender. + * Logs a warning to the {@link StatusLogger} when events are logged out of order to avoid deadlocks. *

    */ -public class AsyncQueueFullMessageUtil { +public final class AsyncQueueFullMessageUtil { + private AsyncQueueFullMessageUtil() { + // Utility Class + } + /** - * Returns a new {@code Message} based on the original message that appends an internal Log4j2 message - * explaining why this message appears out of order in the appender. - *

    - * Any parameter objects present in the original message are not included in the returned message. - *

    - * @param message the message to replace - * @return a new {@code Message} object + * Logs a warning to the {@link StatusLogger} explaining why a message appears out of order in the appender. */ - public static Message transform(Message message) { - SimpleMessage result = new SimpleMessage(message.getFormattedMessage() + - " (Log4j2 logged this message out of order to prevent deadlock caused by domain " + - "objects logging from their toString method when the async queue is full - LOG4J2-2031)"); - return result; + public static void logWarningToStatusLogger() { + StatusLogger.getLogger() + .warn("LOG4J2-2031: Log4j2 logged an event out of order to prevent deadlock caused by domain " + + "objects logging from their toString method when the async queue is full"); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java index 534a899bf0b..1e67bdb0123 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java @@ -18,21 +18,23 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; /** * Creates {@link AsyncQueueFullPolicy} instances based on user-specified system properties. The {@code AsyncQueueFullPolicy} * created by this factory is used in AsyncLogger, AsyncLoggerConfig and AsyncAppender * to control if events are logged in the current thread, the background thread, or discarded. *

    - * Property {@code "log4j2.AsyncQueueFullPolicy"} controls the routing behaviour. If this property is not specified or has + * Property {@value Log4jProperties#ASYNC_LOGGER_QUEUE_FULL_POLICY} controls the routing behaviour. If this property is not specified or has * value {@code "Default"}, this factory creates {@link DefaultAsyncQueueFullPolicy} objects. *

    * If this property has value {@code "Discard"}, this factory creates {@link DiscardingAsyncQueueFullPolicy} objects. * By default, this router discards events of level {@code INFO}, {@code DEBUG} and {@code TRACE} if the queue is full. - * This can be adjusted with property {@code "log4j2.DiscardThreshold"} (name of the level at which to start + * This can be adjusted with property {@value Log4jProperties#ASYNC_LOGGER_DISCARD_THRESHOLD} (name of the level at which to start * discarding). *

    * For any other value, this @@ -43,10 +45,8 @@ * @since 2.6 */ public class AsyncQueueFullPolicyFactory { - static final String PROPERTY_NAME_ASYNC_EVENT_ROUTER = "log4j2.AsyncQueueFullPolicy"; static final String PROPERTY_VALUE_DEFAULT_ASYNC_EVENT_ROUTER = "Default"; static final String PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER = "Discard"; - static final String PROPERTY_NAME_DISCARDING_THRESHOLD_LEVEL = "log4j2.DiscardThreshold"; private static final Logger LOGGER = StatusLogger.getLogger(); @@ -65,23 +65,30 @@ public class AsyncQueueFullPolicyFactory { * @return a new AsyncQueueFullPolicy */ public static AsyncQueueFullPolicy create() { - final String router = PropertiesUtil.getProperties().getStringProperty(PROPERTY_NAME_ASYNC_EVENT_ROUTER); - if (router == null || PROPERTY_VALUE_DEFAULT_ASYNC_EVENT_ROUTER.equals(router) - || DefaultAsyncQueueFullPolicy.class.getSimpleName().equals(router) - || DefaultAsyncQueueFullPolicy.class.getName().equals(router)) { + final String router = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.ASYNC_LOGGER_QUEUE_FULL_POLICY); + if (router == null || isRouterSelected( + router, DefaultAsyncQueueFullPolicy.class, PROPERTY_VALUE_DEFAULT_ASYNC_EVENT_ROUTER)) { return new DefaultAsyncQueueFullPolicy(); } - if (PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER.equals(router) - || DiscardingAsyncQueueFullPolicy.class.getSimpleName().equals(router) - || DiscardingAsyncQueueFullPolicy.class.getName().equals(router)) { + if (isRouterSelected( + router, DiscardingAsyncQueueFullPolicy.class, PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER)) { return createDiscardingAsyncQueueFullPolicy(); } return createCustomRouter(router); } + private static boolean isRouterSelected( + final String propertyValue, + final Class policy, + final String shortPropertyValue) { + return propertyValue != null && (shortPropertyValue.equalsIgnoreCase(propertyValue) + || policy.getName().equals(propertyValue) + || policy.getSimpleName().equals(propertyValue)); + } + private static AsyncQueueFullPolicy createCustomRouter(final String router) { try { - final Class cls = LoaderUtil.loadClass(router).asSubclass(AsyncQueueFullPolicy.class); + final Class cls = Loader.loadClass(router).asSubclass(AsyncQueueFullPolicy.class); LOGGER.debug("Creating custom AsyncQueueFullPolicy '{}'", router); return cls.newInstance(); } catch (final Exception ex) { @@ -92,8 +99,8 @@ private static AsyncQueueFullPolicy createCustomRouter(final String router) { } private static AsyncQueueFullPolicy createDiscardingAsyncQueueFullPolicy() { - final PropertiesUtil util = PropertiesUtil.getProperties(); - final String level = util.getStringProperty(PROPERTY_NAME_DISCARDING_THRESHOLD_LEVEL, Level.INFO.name()); + final PropertyEnvironment properties = PropertiesUtil.getProperties(); + final String level = properties.getStringProperty(Log4jProperties.ASYNC_LOGGER_DISCARD_THRESHOLD, Level.INFO.name()); final Level thresholdLevel = Level.toLevel(level, Level.INFO); LOGGER.debug("Creating custom DiscardingAsyncQueueFullPolicy(discardThreshold:{})", thresholdLevel); return new DiscardingAsyncQueueFullPolicy(thresholdLevel); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactory.java new file mode 100644 index 00000000000..51597b60039 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactory.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.async; + +import com.lmax.disruptor.WaitStrategy; + +/** + * This interface allows users to configure a custom Disruptor WaitStrategy used for + * Async Loggers and Async LoggerConfigs. + * + * @since 2.17.3 + */ +public interface AsyncWaitStrategyFactory { + /** + * Creates and returns a non-null implementation of the LMAX Disruptor's WaitStrategy interface. + * This WaitStrategy will be used by Log4j Async Loggers and Async LoggerConfigs. + * + * @return the WaitStrategy instance to be used by Async Loggers and Async LoggerConfigs + */ + WaitStrategy createWaitStrategy(); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfig.java new file mode 100644 index 00000000000..fb9fdf423ec --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncWaitStrategyFactoryConfig.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; +import org.apache.logging.log4j.status.StatusLogger; + +import java.util.Objects; + +/** + * This class allows users to configure the factory used to create + * an instance of the LMAX disruptor WaitStrategy + * used by Async Loggers in the log4j configuration. + */ +@Configurable(printObject = true) +@Plugin("AsyncWaitStrategyFactory") +public class AsyncWaitStrategyFactoryConfig { + + /** + * Status logger for internal logging. + */ + protected static final Logger LOGGER = StatusLogger.getLogger(); + + private final String factoryClassName; + + public AsyncWaitStrategyFactoryConfig(final String factoryClassName) { + this.factoryClassName = Objects.requireNonNull(factoryClassName, "factoryClassName"); + } + + @PluginFactory + public static > B newBuilder() { + return new AsyncWaitStrategyFactoryConfig.Builder().asBuilder(); + } + + /** + * Builds AsyncWaitStrategyFactoryConfig instances. + * + * @param + * The type to build + */ + public static class Builder> + implements org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute("class") + @Required(message = "AsyncWaitStrategyFactory cannot be configured without a factory class name") + private String factoryClassName; + + public String getFactoryClassName() { + return factoryClassName; + } + + public B withFactoryClassName(String className) { + this.factoryClassName = className; + return asBuilder(); + } + + @Override + public AsyncWaitStrategyFactoryConfig build() { + return new AsyncWaitStrategyFactoryConfig(factoryClassName); + } + + @SuppressWarnings("unchecked") + public B asBuilder() { + return (B) this; + } + } + + public AsyncWaitStrategyFactory createWaitStrategyFactory() { + try { + @SuppressWarnings("unchecked") + final Class klass = (Class) Loader.loadClass(factoryClassName); + if (AsyncWaitStrategyFactory.class.isAssignableFrom(klass)) { + return klass.newInstance(); + } + LOGGER.error("Ignoring factory '{}': it is not assignable to AsyncWaitStrategyFactory", factoryClassName); + return null; + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + LOGGER.info("Invalid implementation class name value: error creating AsyncWaitStrategyFactory {}: {}", factoryClassName, e); + return null; + } + + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java new file mode 100644 index 00000000000..9658f0302f9 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BasicAsyncLoggerContextSelector.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.net.URI; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.selector.BasicContextSelector; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.di.Injector; + +/** + * Returns either this Thread's context or the default {@link AsyncLoggerContext}. + * Single-application instances should prefer this implementation over the {@link AsyncLoggerContextSelector} + * due to the reduced overhead avoiding classloader lookups. + */ +@Singleton +public class BasicAsyncLoggerContextSelector extends BasicContextSelector { + + @Inject + public BasicAsyncLoggerContextSelector(Injector injector) { + super(injector); + } + + @Override + protected LoggerContext createContext() { + return new AsyncLoggerContext("AsyncDefault", null, (URI) null, injector); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java index b495d5fd31a..8f0e7b745e2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java @@ -26,7 +26,7 @@ public interface BlockingQueueFactory { /** - * The {@link org.apache.logging.log4j.core.config.plugins.Plugin#elementType() element type} to use for plugins + * The {@link org.apache.logging.log4j.plugins.Configurable#elementType() element type} to use for plugins * implementing this interface. */ String ELEMENT_TYPE = "BlockingQueueFactory"; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java index 1d4811332cc..e8abfcc8331 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.core.async; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.util.Log4jThread; /** * Default router: enqueue the event for asynchronous logging in the background thread, unless the current thread is the @@ -29,7 +30,13 @@ public EventRoute getRoute(final long backgroundThreadId, final Level level) { // LOG4J2-471: prevent deadlock when RingBuffer is full and object // being logged calls Logger.log() from its toString() method - if (Thread.currentThread().getId() == backgroundThreadId) { + final Thread currentThread = Thread.currentThread(); + if (currentThread.getId() == backgroundThreadId + // Threads owned by log4j are most likely to result in + // deadlocks because they generally consume events. + // This prevents deadlocks between AsyncLoggerContext + // disruptors. + || currentThread instanceof Log4jThread) { return EventRoute.SYNCHRONOUS; } return EventRoute.ENQUEUE; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncWaitStrategyFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncWaitStrategyFactory.java new file mode 100644 index 00000000000..f2e9b5d3af5 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncWaitStrategyFactory.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.Strings; + +import com.lmax.disruptor.BlockingWaitStrategy; +import com.lmax.disruptor.BusySpinWaitStrategy; +import com.lmax.disruptor.SleepingWaitStrategy; +import com.lmax.disruptor.WaitStrategy; +import com.lmax.disruptor.YieldingWaitStrategy; + +class DefaultAsyncWaitStrategyFactory implements AsyncWaitStrategyFactory { + static final String DEFAULT_WAIT_STRATEGY_CLASSNAME = TimeoutBlockingWaitStrategy.class.getName(); + private static final Logger LOGGER = StatusLogger.getLogger(); + private final String propertyName; + + public DefaultAsyncWaitStrategyFactory(String propertyName) { + this.propertyName = propertyName; + } + + @Override + public WaitStrategy createWaitStrategy() { + final String strategy = PropertiesUtil.getProperties().getStringProperty(propertyName, "TIMEOUT"); + LOGGER.trace("DefaultAsyncWaitStrategyFactory property {}={}", propertyName, strategy); + final String strategyUp = Strings.toRootUpperCase(strategy); + // String (not enum) is deliberately used here to avoid IllegalArgumentException being thrown. In case of + // incorrect property value, default WaitStrategy is created. + switch (strategyUp) { + case "SLEEP": + final long sleepTimeNs = + parseAdditionalLongProperty(propertyName, "SleepTimeNs", 100L); + final String key = getFullPropertyKey(propertyName, "Retries"); + final int retries = + PropertiesUtil.getProperties().getIntegerProperty(key, 200); + LOGGER.trace("DefaultAsyncWaitStrategyFactory creating SleepingWaitStrategy(retries={}, sleepTimeNs={})", retries, sleepTimeNs); + return new SleepingWaitStrategy(retries, sleepTimeNs); + case "YIELD": + LOGGER.trace("DefaultAsyncWaitStrategyFactory creating YieldingWaitStrategy"); + return new YieldingWaitStrategy(); + case "BLOCK": + LOGGER.trace("DefaultAsyncWaitStrategyFactory creating BlockingWaitStrategy"); + return new BlockingWaitStrategy(); + case "BUSYSPIN": + LOGGER.trace("DefaultAsyncWaitStrategyFactory creating BusySpinWaitStrategy"); + return new BusySpinWaitStrategy(); + case "TIMEOUT": + return createDefaultWaitStrategy(propertyName); + default: + return createDefaultWaitStrategy(propertyName); + } + } + + static WaitStrategy createDefaultWaitStrategy(final String propertyName) { + final long timeoutMillis = parseAdditionalLongProperty(propertyName, "Timeout", 10L); + LOGGER.trace("DefaultAsyncWaitStrategyFactory creating TimeoutBlockingWaitStrategy(timeout={}, unit=MILLIS)", timeoutMillis); + return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS); + } + + private static String getFullPropertyKey(final String strategyKey, final String additionalKey) { + if (strategyKey.startsWith("AsyncLogger.")) { + return "AsyncLogger." + additionalKey; + } else if (strategyKey.startsWith("AsyncLoggerConfig.")) { + return "AsyncLoggerConfig." + additionalKey; + } + return strategyKey + additionalKey; + } + + private static long parseAdditionalLongProperty( + final String propertyName, + final String additionalKey, + long defaultValue) { + final String key = getFullPropertyKey(propertyName, additionalKey); + return PropertiesUtil.getProperties().getLongProperty(key, defaultValue); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java index 5c941dbf067..39bc3ccda3c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java @@ -16,21 +16,22 @@ */ package org.apache.logging.log4j.core.async; -import java.util.concurrent.BlockingQueue; - import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; import com.conversantmedia.util.concurrent.SpinPolicy; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.util.concurrent.BlockingQueue; /** * Factory for creating instances of {@link DisruptorBlockingQueue}. * * @since 2.7 */ -@Plugin(name = "DisruptorBlockingQueue", category = Node.CATEGORY, elementType = BlockingQueueFactory.ELEMENT_TYPE) +@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true) +@Plugin("DisruptorBlockingQueue") public class DisruptorBlockingQueueFactory implements BlockingQueueFactory { private final SpinPolicy spinPolicy; @@ -46,7 +47,7 @@ public BlockingQueue create(final int capacity) { @PluginFactory public static DisruptorBlockingQueueFactory createFactory( - @PluginAttribute(value = "SpinPolicy", defaultString = "WAITING") final SpinPolicy spinPolicy + @PluginAttribute(defaultString = "WAITING") final SpinPolicy spinPolicy ) { return new DisruptorBlockingQueueFactory<>(spinPolicy); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java index 4fc5ea0b0c2..cf61a736834 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java @@ -14,23 +14,23 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.async; -import java.util.Locale; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import com.lmax.disruptor.*; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; +import com.lmax.disruptor.ExceptionHandler; +import com.lmax.disruptor.WaitStrategy; + +import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; + /** * Utility methods for getting Disruptor related configuration. */ @@ -40,43 +40,33 @@ final class DisruptorUtil { private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024; private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024; - private DisruptorUtil() { - } + /** + * LOG4J2-2606: Users encountered excessive CPU utilization with Disruptor v3.4.2 when the application + * was logging more than the underlying appender could keep up with and the ringbuffer became full, + * especially when the number of application threads vastly outnumbered the number of cores. + * CPU utilization is significantly reduced by restricting access to the enqueue operation. + */ + static final boolean ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties() + .getBooleanProperty(Log4jProperties.ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL, true); + static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties() + .getBooleanProperty(Log4jProperties.ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL, true); - static long getTimeout(final String propertyName, final long defaultTimeout) { - return PropertiesUtil.getProperties().getLongProperty(propertyName, defaultTimeout); + private DisruptorUtil() { } - static WaitStrategy createWaitStrategy(final String propertyName) { - final String key = propertyName.startsWith("AsyncLogger.") - ? "AsyncLogger.Timeout" - : "AsyncLoggerConfig.Timeout"; - final long timeoutMillis = DisruptorUtil.getTimeout(key, 10L); - return createWaitStrategy(propertyName, timeoutMillis); - } + static WaitStrategy createWaitStrategy(final String propertyName, + final AsyncWaitStrategyFactory asyncWaitStrategyFactory) { - static WaitStrategy createWaitStrategy(final String propertyName, final long timeoutMillis) { - final String strategy = PropertiesUtil.getProperties().getStringProperty(propertyName, "TIMEOUT"); - LOGGER.trace("property {}={}", propertyName, strategy); - final String strategyUp = strategy.toUpperCase(Locale.ROOT); // TODO Refactor into Strings.toRootUpperCase(String) - switch (strategyUp) { // TODO Define a DisruptorWaitStrategy enum? - case "SLEEP": - return new SleepingWaitStrategy(); - case "YIELD": - return new YieldingWaitStrategy(); - case "BLOCK": - return new BlockingWaitStrategy(); - case "BUSYSPIN": - return new BusySpinWaitStrategy(); - case "TIMEOUT": - return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS); - default: - return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS); + if (asyncWaitStrategyFactory == null) { + LOGGER.debug("No AsyncWaitStrategyFactory was configured in the configuration, using default factory..."); + return new DefaultAsyncWaitStrategyFactory(propertyName).createWaitStrategy(); } + LOGGER.debug("Using configured AsyncWaitStrategyFactory {}", asyncWaitStrategyFactory.getClass().getName()); + return asyncWaitStrategyFactory.createWaitStrategy(); } static int calculateRingBufferSize(final String propertyName) { - int ringBufferSize = Constants.ENABLE_THREADLOCALS ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE; + int ringBufferSize = isThreadLocalsEnabled() ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE; final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName, String.valueOf(ringBufferSize)); try { @@ -94,33 +84,33 @@ static int calculateRingBufferSize(final String propertyName) { } static ExceptionHandler getAsyncLoggerExceptionHandler() { - final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ExceptionHandler"); + final String cls = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.ASYNC_LOGGER_EXCEPTION_HANDLER_CLASS_NAME); if (cls == null) { return new AsyncLoggerDefaultExceptionHandler(); } try { @SuppressWarnings("unchecked") final Class> klass = - (Class>) LoaderUtil.loadClass(cls); + (Class>) Loader.loadClass(cls); return klass.newInstance(); - } catch (final Exception ignored) { - LOGGER.debug("Invalid AsyncLogger.ExceptionHandler value: error creating {}: ", cls, ignored); + } catch (final Exception e) { + LOGGER.debug("Invalid {} value: error creating {}: ", Log4jProperties.ASYNC_LOGGER_EXCEPTION_HANDLER_CLASS_NAME, cls, e); return new AsyncLoggerDefaultExceptionHandler(); } } static ExceptionHandler getAsyncLoggerConfigExceptionHandler() { - final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLoggerConfig.ExceptionHandler"); + final String cls = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.ASYNC_CONFIG_EXCEPTION_HANDLER_CLASS_NAME); if (cls == null) { return new AsyncLoggerConfigDefaultExceptionHandler(); } try { @SuppressWarnings("unchecked") final Class> klass = - (Class>) LoaderUtil.loadClass(cls); + (Class>) Loader.loadClass(cls); return klass.newInstance(); - } catch (final Exception ignored) { - LOGGER.debug("Invalid AsyncLoggerConfig.ExceptionHandler value: error creating {}: ", cls, ignored); + } catch (final Exception e) { + LOGGER.debug("Invalid {} value: error creating {}: ", Log4jProperties.ASYNC_CONFIG_EXCEPTION_HANDLER_CLASS_NAME, cls, e); return new AsyncLoggerConfigDefaultExceptionHandler(); } } @@ -133,12 +123,7 @@ static ExceptionHandler getAsyncLo * @return the thread ID of the background appender thread */ public static long getExecutorThreadId(final ExecutorService executor) { - final Future result = executor.submit(new Callable() { - @Override - public Long call() { - return Thread.currentThread().getId(); - } - }); + final Future result = executor.submit(() -> Thread.currentThread().getId()); try { return result.get(); } catch (final Exception ex) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/EventRoute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/EventRoute.java index a1b9bb6c3c8..bc2f5c7fd01 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/EventRoute.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/EventRoute.java @@ -43,7 +43,7 @@ public void logMessage(final AsyncLogger asyncLogger, final String fqcn, final L @Override public void logMessage(final AsyncLoggerConfig asyncLoggerConfig, final LogEvent event) { - asyncLoggerConfig.callAppendersInBackgroundThread(event); + asyncLoggerConfig.logInBackgroundThread(event); } @Override @@ -53,6 +53,8 @@ public void logMessage(final AsyncAppender asyncAppender, final LogEvent logEven }, /** * Logs the event synchronously: sends the event directly to the appender (in the current thread). + * WARNING: This may result in lines logged out of order as synchronous events may be persisted before + * earlier events, even from the same thread, which wait in the queue. */ SYNCHRONOUS { @Override @@ -62,7 +64,7 @@ public void logMessage(final AsyncLogger asyncLogger, final String fqcn, final L @Override public void logMessage(final AsyncLoggerConfig asyncLoggerConfig, final LogEvent event) { - asyncLoggerConfig.callAppendersInCurrentThread(event); + asyncLoggerConfig.logToAsyncLoggerConfigsOnCurrentThread(event); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java index facc59eebbc..1e304dd8a58 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/JCToolsBlockingQueueFactory.java @@ -16,23 +16,24 @@ */ package org.apache.logging.log4j.core.async; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.jctools.queues.MpscArrayQueue; + import java.util.Collection; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.jctools.queues.MpscArrayQueue; - /** * Factory for creating instances of BlockingQueues backed by JCTools {@link MpscArrayQueue}. * * @since 2.7 */ -@Plugin(name = "JCToolsBlockingQueue", category = Node.CATEGORY, elementType = BlockingQueueFactory.ELEMENT_TYPE) +@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true) +@Plugin("JCToolsBlockingQueue") public class JCToolsBlockingQueueFactory implements BlockingQueueFactory { private final WaitStrategy waitStrategy; @@ -48,7 +49,7 @@ public BlockingQueue create(final int capacity) { @PluginFactory public static JCToolsBlockingQueueFactory createFactory( - @PluginAttribute(value = "WaitStrategy", defaultString = "PARK") final WaitStrategy waitStrategy) { + @PluginAttribute(defaultString = "PARK") final WaitStrategy waitStrategy) { return new JCToolsBlockingQueueFactory<>(waitStrategy); } @@ -71,12 +72,7 @@ public int drainTo(final Collection c) { @Override public int drainTo(final Collection c, final int maxElements) { - return drain(new Consumer() { - @Override - public void accept(final E e) { - c.add(e); - } - }, maxElements); + return drain(c::add, maxElements); } @Override @@ -148,36 +144,22 @@ public E take() throws InterruptedException { } public enum WaitStrategy { - SPIN(new Idle() { - @Override - public int idle(final int idleCounter) { - return idleCounter + 1; - } + SPIN(idleCounter -> idleCounter + 1), + YIELD(idleCounter -> { + Thread.yield(); + return idleCounter + 1; }), - YIELD(new Idle() { - @Override - public int idle(final int idleCounter) { - Thread.yield(); - return idleCounter + 1; - } + PARK(idleCounter -> { + LockSupport.parkNanos(1L); + return idleCounter + 1; }), - PARK(new Idle() { - @Override - public int idle(final int idleCounter) { + PROGRESSIVE(idleCounter -> { + if (idleCounter > 200) { LockSupport.parkNanos(1L); - return idleCounter + 1; - } - }), - PROGRESSIVE(new Idle() { - @Override - public int idle(final int idleCounter) { - if (idleCounter > 200) { - LockSupport.parkNanos(1L); - } else if (idleCounter > 100) { - Thread.yield(); - } - return idleCounter + 1; + } else if (idleCounter > 100) { + Thread.yield(); } + return idleCounter + 1; }); private final Idle idle; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java index 0d4628ec2e5..d5dfa11e88e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/LinkedTransferQueueFactory.java @@ -17,19 +17,20 @@ package org.apache.logging.log4j.core.async; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedTransferQueue; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Factory for creating instances of {@link LinkedTransferQueue}. * * @since 2.7 */ -@Plugin(name = "LinkedTransferQueue", category = Node.CATEGORY, elementType = BlockingQueueFactory.ELEMENT_TYPE) +@Configurable(elementType = BlockingQueueFactory.ELEMENT_TYPE, printObject = true) +@Plugin("LinkedTransferQueue") public class LinkedTransferQueueFactory implements BlockingQueueFactory { @Override public BlockingQueue create(final int capacity) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java index 9e5181538af..4d58ae5309f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.util.Arrays; -import java.util.Map; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -26,12 +25,16 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.MementoMessage; import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.apache.logging.log4j.core.util.*; +import org.apache.logging.log4j.core.time.Clock; import org.apache.logging.log4j.core.time.Instant; import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.time.NanoClock; +import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.message.ParameterConsumer; +import org.apache.logging.log4j.message.ParameterVisitable; import org.apache.logging.log4j.message.ReusableMessage; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.TimestampMessage; @@ -42,11 +45,13 @@ import com.lmax.disruptor.EventFactory; +import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; + /** * When the Disruptor is started, the RingBuffer is populated with event objects. These objects are then re-used during * the life of the RingBuffer. */ -public class RingBufferLogEvent implements LogEvent, ReusableMessage, CharSequence { +public class RingBufferLogEvent implements LogEvent, ReusableMessage, CharSequence, ParameterVisitable { /** The {@code EventFactory} for {@code RingBufferLogEvent}s. */ public static final Factory FACTORY = new Factory(); @@ -61,18 +66,14 @@ private static class Factory implements EventFactory { @Override public RingBufferLogEvent newInstance() { - final RingBufferLogEvent result = new RingBufferLogEvent(); - if (Constants.ENABLE_THREADLOCALS) { - result.messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE); - result.parameters = new Object[10]; - } - return result; + return new RingBufferLogEvent(); } } + private boolean populated; private int threadPriority; private long threadId; - private MutableInstant instant = new MutableInstant(); + private final MutableInstant instant = new MutableInstant(); private long nanoTime; private short parameterCount; private boolean includeLocation; @@ -81,6 +82,7 @@ public RingBufferLogEvent newInstance() { private String threadName; private String loggerName; private Message message; + private String messageFormat; private StringBuilder messageText; private Object[] parameters; private transient Throwable thrown; @@ -114,6 +116,7 @@ public void setValues(final AsyncLogger anAsyncLogger, final String aLoggerName, this.contextData = mutableContextData; this.contextStack = aContextStack; this.asyncLogger = anAsyncLogger; + this.populated = true; } private void initTime(final Clock clock) { @@ -133,10 +136,9 @@ private void setMessage(final Message msg) { if (msg instanceof ReusableMessage) { final ReusableMessage reusable = (ReusableMessage) msg; reusable.formatTo(getMessageTextForWriting()); - if (parameters != null) { - parameters = reusable.swapParameters(parameters); - parameterCount = reusable.getParameterCount(); - } + messageFormat = reusable.getFormat(); + parameters = reusable.swapParameters(parameters == null ? new Object[10] : parameters); + parameterCount = reusable.getParameterCount(); } else { this.message = InternalAsyncUtil.makeMessageImmutable(msg); } @@ -144,8 +146,8 @@ private void setMessage(final Message msg) { private StringBuilder getMessageTextForWriting() { if (messageText == null) { - // Should never happen: - // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false + // Happens the first time messageText is requested or if a user logs + // a custom reused message when Constants.ENABLE_THREADLOCALS is false messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE); } messageText.setLength(0); @@ -162,6 +164,13 @@ public void execute(final boolean endOfBatch) { asyncLogger.actualAsyncLog(this); } + /** + * @return {@code true} if this event is populated with data, {@code false} otherwise + */ + public boolean isPopulated() { + return populated; + } + /** * Returns {@code true} if this event is the end of a batch, {@code false} otherwise. * @@ -233,7 +242,7 @@ public String getFormattedMessage() { */ @Override public String getFormat() { - return null; + return messageFormat; } /** @@ -281,13 +290,21 @@ public short getParameterCount() { return parameterCount; } + @Override + public void forEachParameter(final ParameterConsumer action, final S state) { + if (parameters != null) { + for (short i = 0; i < parameterCount; i++) { + action.accept(parameters[i], i, state); + } + } + } + @Override public Message memento() { - if (message != null) { - return message; + if (message == null) { + message = new MementoMessage(String.valueOf(messageText), messageFormat, getParameters()); } - final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount); - return new ParameterizedMessage(messageText.toString(), params); + return message; } // CharSequence impl @@ -307,11 +324,6 @@ public CharSequence subSequence(final int start, final int end) { return messageText.subSequence(start, end); } - - private Message getNonNullImmutableMessage() { - return message != null ? message : new SimpleMessage(String.valueOf(messageText)); - } - @Override public Throwable getThrown() { // after deserialization, thrown is null but thrownProxy may be non-null @@ -344,12 +356,6 @@ void setContextData(final StringMap contextData) { this.contextData = contextData; } - @SuppressWarnings("unchecked") - @Override - public Map getContextMap() { - return contextData.toMap(); - } - @Override public ContextStack getContextStack() { return contextStack; @@ -394,12 +400,15 @@ public long getNanoTime() { * Release references held by ring buffer to allow objects to be garbage-collected. */ public void clear() { + this.populated = false; + this.asyncLogger = null; this.loggerName = null; this.marker = null; this.fqcn = null; this.level = null; this.message = null; + this.messageFormat = null; this.thrown = null; this.thrownProxy = null; this.contextStack = null; @@ -413,12 +422,18 @@ public void clear() { } // ensure that excessively long char[] arrays are not kept in memory forever - StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE); + if (isThreadLocalsEnabled()) { + StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE); - if (parameters != null) { - for (int i = 0; i < parameters.length; i++) { - parameters[i] = null; + if (parameters != null) { + Arrays.fill(parameters, null); } + } else { + // A user may have manually logged a ReusableMessage implementation, when thread locals are + // disabled we remove the reference in order to avoid permanently holding references to these + // buffers. + messageText = null; + parameters = null; } } @@ -450,7 +465,7 @@ public void initializeBuilder(final Log4jLogEvent.Builder builder) { .setLoggerFqcn(fqcn) // .setLoggerName(loggerName) // .setMarker(marker) // - .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable + .setMessage(memento()) // ensure non-null & immutable .setNanoTime(nanoTime) // .setSource(location) // .setThreadId(threadId) // diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler.java index e4a496cd326..0cc1bdcd722 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler.java @@ -42,12 +42,25 @@ public void setSequenceCallback(final Sequence sequenceCallback) { @Override public void onEvent(final RingBufferLogEvent event, final long sequence, final boolean endOfBatch) throws Exception { - event.execute(endOfBatch); - event.clear(); + try { + // RingBufferLogEvents are populated by an EventTranslator. If an exception is thrown during event + // translation, the event may not be fully populated, but Disruptor requires that the associated sequence + // still be published since a slot has already been claimed in the ring buffer. Ignore any such unpopulated + // events. The exception that occurred during translation will have already been propagated. + if (event.isPopulated()) { + event.execute(endOfBatch); + } + } + finally { + event.clear(); + // notify the BatchEventProcessor that the sequence has progressed. + // Without this callback the sequence would not be progressed + // until the batch has completely finished. + notifyCallback(sequence); + } + } - // notify the BatchEventProcessor that the sequence has progressed. - // Without this callback the sequence would not be progressed - // until the batch has completely finished. + private void notifyCallback(final long sequence) { if (++counter > NOTIFY_PROGRESS_THRESHOLD) { sequenceCallback.set(sequence); counter = 0; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java index ec2ff1c897e..33b72929bb8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java @@ -16,17 +16,15 @@ */ package org.apache.logging.log4j.core.async; +import com.lmax.disruptor.EventTranslator; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext.ContextStack; import org.apache.logging.log4j.core.ContextDataInjector; -import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; -import org.apache.logging.log4j.core.util.Clock; -import org.apache.logging.log4j.core.util.NanoClock; -import org.apache.logging.log4j.util.StringMap; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.message.Message; - -import com.lmax.disruptor.EventTranslator; +import org.apache.logging.log4j.util.StringMap; /** * This class is responsible for writing elements that make up a log event into @@ -37,7 +35,7 @@ public class RingBufferLogEventTranslator implements EventTranslator { - private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); + private ContextDataInjector contextDataInjector; private AsyncLogger asyncLogger; String loggerName; protected Marker marker; @@ -56,20 +54,21 @@ public class RingBufferLogEventTranslator implements // @Override @Override public void translateTo(final RingBufferLogEvent event, final long sequence) { - - event.setValues(asyncLogger, loggerName, marker, fqcn, level, message, thrown, - // config properties are taken care of in the EventHandler thread - // in the AsyncLogger#actualAsyncLog method - injector.injectContextData(null, (StringMap) event.getContextData()), contextStack, - threadId, threadName, threadPriority, location, clock, nanoClock); - - clear(); // clear the translator + try { + event.setValues(asyncLogger, loggerName, marker, fqcn, level, message, thrown, + // config properties are taken care of in the EventHandler thread + // in the AsyncLogger#actualAsyncLog method + contextDataInjector.injectContextData(null, (StringMap) event.getContextData()), contextStack, + threadId, threadName, threadPriority, location, clock, nanoClock); + } finally { + clear(); // clear the translator + } } /** * Release references held by this object to allow objects to be garbage-collected. */ - private void clear() { + void clear() { setBasicValues(null, // asyncLogger null, // loggerName null, // marker @@ -80,14 +79,15 @@ private void clear() { null, // contextStack null, // location null, // clock - null // nanoClock + null, // nanoClock + null // contextDataInjector ); } public void setBasicValues(final AsyncLogger anAsyncLogger, final String aLoggerName, final Marker aMarker, final String theFqcn, final Level aLevel, final Message msg, final Throwable aThrowable, final ContextStack aContextStack, final StackTraceElement aLocation, - final Clock aClock, final NanoClock aNanoClock) { + final Clock aClock, final NanoClock aNanoClock, final ContextDataInjector aContextDataInjector) { this.asyncLogger = anAsyncLogger; this.loggerName = aLoggerName; this.marker = aMarker; @@ -99,6 +99,7 @@ public void setBasicValues(final AsyncLogger anAsyncLogger, final String aLogger this.location = aLocation; this.clock = aClock; this.nanoClock = aNanoClock; + this.contextDataInjector = aContextDataInjector; } public void updateThreadValues() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ThreadNameCachingStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ThreadNameCachingStrategy.java index 1741155983f..636857f4f35 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ThreadNameCachingStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ThreadNameCachingStrategy.java @@ -1,67 +1,86 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.async; - -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.PropertiesUtil; - -/** - * Strategy for deciding whether thread name should be cached or not. - */ -public enum ThreadNameCachingStrategy { // LOG4J2-467 - CACHED { - @Override - public String getThreadName() { - String result = THREADLOCAL_NAME.get(); - if (result == null) { - result = Thread.currentThread().getName(); - THREADLOCAL_NAME.set(result); - } - return result; - } - }, - UNCACHED { - @Override - public String getThreadName() { - return Thread.currentThread().getName(); - } - }; - - private static final StatusLogger LOGGER = StatusLogger.getLogger(); - private static final ThreadLocal THREADLOCAL_NAME = new ThreadLocal<>(); - - abstract String getThreadName(); - - public static ThreadNameCachingStrategy create() { - final String defaultStrategy = System.getProperty("java.version").compareTo("1.8.0_102") < 0 - ? "CACHED" // LOG4J2-2052 JDK 8u102 removed the String allocation in Thread.getName() - : "UNCACHED"; - final String name = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ThreadNameStrategy"); - try { - final ThreadNameCachingStrategy result = ThreadNameCachingStrategy.valueOf( - name != null ? name : defaultStrategy); - LOGGER.debug("AsyncLogger.ThreadNameStrategy={} (user specified {}, default is {})", - result, name, defaultStrategy); - return result; - } catch (final Exception ex) { - LOGGER.debug("Using AsyncLogger.ThreadNameStrategy.{}: '{}' not valid: {}", - defaultStrategy, name, ex.toString()); - return ThreadNameCachingStrategy.valueOf(defaultStrategy); - } - } -} \ No newline at end of file +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.async; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Constants; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * Strategy for deciding whether thread name should be cached or not. + */ +public enum ThreadNameCachingStrategy { // LOG4J2-467 + CACHED { + @Override + public String getThreadName() { + String result = THREADLOCAL_NAME.get(); + if (result == null) { + result = Thread.currentThread().getName(); + THREADLOCAL_NAME.set(result); + } + return result; + } + }, + UNCACHED { + @Override + public String getThreadName() { + return Thread.currentThread().getName(); + } + }; + + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + private static final ThreadLocal THREADLOCAL_NAME = new ThreadLocal<>(); + static final ThreadNameCachingStrategy DEFAULT_STRATEGY = isAllocatingThreadGetName() ? CACHED : UNCACHED; + + abstract String getThreadName(); + + public static ThreadNameCachingStrategy create() { + final String name = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY); + try { + final ThreadNameCachingStrategy result = name != null ? ThreadNameCachingStrategy.valueOf(name) : DEFAULT_STRATEGY; + LOGGER.debug("{}={} (user specified {}, default is {})", Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, + result.name(), name, DEFAULT_STRATEGY.name()); + return result; + } catch (final Exception ex) { + LOGGER.debug("Using {}.{}: '{}' not valid: {}", Log4jProperties.ASYNC_LOGGER_THREAD_NAME_STRATEGY, + DEFAULT_STRATEGY.name(), name, ex.toString()); + return DEFAULT_STRATEGY; + } + } + + static boolean isAllocatingThreadGetName() { + // LOG4J2-2052, LOG4J2-2635 JDK 8u102 ("1.8.0_102") removed the String allocation in Thread.getName() + if (Constants.JAVA_MAJOR_VERSION == 8) { + try { + final Pattern javaVersionPattern = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)_(\\d+)"); + final Matcher m = javaVersionPattern.matcher(System.getProperty("java.version")); + if (m.matches()) { + return Integer.parseInt(m.group(3)) == 0 && Integer.parseInt(m.group(4)) < 102; + } + return true; + } catch (final Exception e) { + return true; + } + } else { + return Constants.JAVA_MAJOR_VERSION < 8; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/TimeoutBlockingWaitStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/TimeoutBlockingWaitStrategy.java new file mode 100644 index 00000000000..804e9c901d7 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/TimeoutBlockingWaitStrategy.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +// Where the above is the copyright notice of the derivate work, +// below is the copyright notice of the original source of this work: +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.core.async; + +import com.lmax.disruptor.AlertException; +import com.lmax.disruptor.Sequence; +import com.lmax.disruptor.SequenceBarrier; +import com.lmax.disruptor.TimeoutException; +import com.lmax.disruptor.WaitStrategy; + +import java.util.concurrent.TimeUnit; + +/** + * Blocking strategy that uses a lock and condition variable for {@link EventProcessor}s waiting on a barrier. + * However, it will periodically wake up if it has been idle for specified period by throwing a + * {@link TimeoutException}. To make use of this, the event handler class should override + * {@link EventHandler#onTimeout(long)}, which the {@link BatchEventProcessor} will call if the timeout occurs. + * + *

    This strategy can be used when throughput and low-latency are not as important as CPU resource. + */ +// IMPLEMENTATION NOTE: +// This is a copy of the com.lmax.disruptor.TimeoutBlockingWaitStrategy class in disruptor-4.0.0-RC1. +// The difference between this code and the implementation of this class in disruptor-3.4.4 +// is that this class is garbage-free, because it uses a synchronized block instead of a ReentrantLock. +// This class is package-protected, so that it can be used internally as the default WaitStrategy +// by Log4j Async Loggers, but can be removed in a future Log4j release without impacting binary compatibility. +// (disruptor-4.0.0-RC1 requires Java 11 and has other incompatible changes so cannot be used in Log4j 2.x.) +class TimeoutBlockingWaitStrategy implements WaitStrategy { + private final Object mutex = new Object(); + private final long timeoutInNanos; + + /** + * @param timeout how long to wait before waking up + * @param units the unit in which timeout is specified + */ + public TimeoutBlockingWaitStrategy(final long timeout, final TimeUnit units) { + timeoutInNanos = units.toNanos(timeout); + } + + @Override + public long waitFor( + final long sequence, + final Sequence cursorSequence, + final Sequence dependentSequence, + final SequenceBarrier barrier) + throws AlertException, InterruptedException, TimeoutException { + long timeoutNanos = timeoutInNanos; + + long availableSequence; + if (cursorSequence.get() < sequence) { + synchronized (mutex) { + while (cursorSequence.get() < sequence) { + barrier.checkAlert(); + timeoutNanos = awaitNanos(mutex, timeoutNanos); + if (timeoutNanos <= 0) { + throw TimeoutException.INSTANCE; + } + } + } + } + + while ((availableSequence = dependentSequence.get()) < sequence) { + barrier.checkAlert(); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() { + synchronized (mutex) { + mutex.notifyAll(); + } + } + + @Override + public String toString() { + return "TimeoutBlockingWaitStrategy{" + + "mutex=" + mutex + + ", timeoutInNanos=" + timeoutInNanos + + '}'; + } + + // below code is from com.lmax.disruptor.util.Util class in disruptor 4.0.0-RC1 + private static final int ONE_MILLISECOND_IN_NANOSECONDS = 1_000_000; + + /** + * @param mutex The object to wait on + * @param timeoutNanos The number of nanoseconds to wait for + * @return the number of nanoseconds waited (approximately) + * @throws InterruptedException if the underlying call to wait is interrupted + */ + private static long awaitNanos(final Object mutex, final long timeoutNanos) throws InterruptedException { + long millis = timeoutNanos / ONE_MILLISECOND_IN_NANOSECONDS; + long nanos = timeoutNanos % ONE_MILLISECOND_IN_NANOSECONDS; + + long t0 = System.nanoTime(); + mutex.wait(millis, (int) nanos); + long t1 = System.nanoTime(); + + return timeoutNanos - (t1 - t0); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java index 3c0e81089f9..db1a5c83250 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java @@ -16,17 +16,15 @@ */ package org.apache.logging.log4j.core.config; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; +import java.lang.invoke.MethodHandles; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -35,51 +33,68 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LifeCycle2; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.Version; import org.apache.logging.log4j.core.appender.AsyncAppender; import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.async.AsyncLoggerConfig; import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate; import org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor; -import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; +import org.apache.logging.log4j.core.async.AsyncWaitStrategyFactory; +import org.apache.logging.log4j.core.async.AsyncWaitStrategyFactoryConfig; +import org.apache.logging.log4j.core.config.arbiters.Arbiter; +import org.apache.logging.log4j.core.config.arbiters.SelectArbiter; import org.apache.logging.log4j.core.filter.AbstractFilterable; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor; import org.apache.logging.log4j.core.lookup.Interpolator; -import org.apache.logging.log4j.core.lookup.MapLookup; +import org.apache.logging.log4j.core.lookup.InterpolatorFactory; +import org.apache.logging.log4j.core.lookup.PropertiesLookup; +import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor; import org.apache.logging.log4j.core.lookup.StrLookup; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.net.Advertiser; -import org.apache.logging.log4j.core.script.AbstractScript; import org.apache.logging.log4j.core.script.ScriptManager; -import org.apache.logging.log4j.core.script.ScriptRef; +import org.apache.logging.log4j.core.script.ScriptManagerFactory; +import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.core.util.DummyNanoClock; -import org.apache.logging.log4j.core.util.Loader; -import org.apache.logging.log4j.core.util.NameUtil; -import org.apache.logging.log4j.core.util.NanoClock; +import org.apache.logging.log4j.core.util.Source; import org.apache.logging.log4j.core.util.WatchManager; +import org.apache.logging.log4j.core.util.Watcher; +import org.apache.logging.log4j.core.util.WatcherFactory; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.util.Cast; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.NameUtil; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.ServiceRegistry; /** * The base Configuration. Many configuration implementations will extend this class. */ public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration { - private static final int BUF_SIZE = 16384; - /** * The root node of the configuration. */ - protected Node rootNode; + protected Node rootNode = new Node(); /** * Listeners for configuration changes. @@ -92,9 +107,9 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement protected final List pluginPackages = new ArrayList<>(); /** - * The plugin manager. + * Core plugins. */ - protected PluginManager pluginManager; + protected PluginNamespace corePlugins; /** * Shutdown hook is enabled by default. @@ -104,33 +119,36 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement /** * Shutdown timeout in milliseconds. */ - protected long shutdownTimeoutMillis = 0; + protected long shutdownTimeoutMillis; /** * The Script manager. */ protected ScriptManager scriptManager; + protected final Injector injector; /** * The Advertiser which exposes appender configurations to external systems. */ private Advertiser advertiser = new DefaultAdvertiser(); - private Node advertiserNode = null; + private Node advertiserNode; private Object advertisement; private String name; private ConcurrentMap appenders = new ConcurrentHashMap<>(); private ConcurrentMap loggerConfigs = new ConcurrentHashMap<>(); - private List customLevels = Collections.emptyList(); + private List customLevels = List.of(); private final ConcurrentMap properties = new ConcurrentHashMap<>(); - private final StrLookup tempLookup = new Interpolator(properties); - private final StrSubstitutor subst = new StrSubstitutor(tempLookup); + private final InterpolatorFactory interpolatorFactory; + private final Interpolator tempLookup; + private final StrSubstitutor runtimeStrSubstitutor; + private final StrSubstitutor configurationStrSubstitutor; private LoggerConfig root = new LoggerConfig(); private final ConcurrentMap componentMap = new ConcurrentHashMap<>(); private final ConfigurationSource configurationSource; - private final ConfigurationScheduler configurationScheduler = new ConfigurationScheduler(); - private final WatchManager watchManager = new WatchManager(configurationScheduler); + private final ConfigurationScheduler configurationScheduler; + private final WatchManager watchManager; private AsyncLoggerConfigDisruptor asyncLoggerConfigDisruptor; - private NanoClock nanoClock = new DummyNanoClock(); + private AsyncWaitStrategyFactory asyncWaitStrategyFactory; private final WeakReference loggerContext; /** @@ -141,11 +159,22 @@ protected AbstractConfiguration(final LoggerContext loggerContext, final Configu // The loggerContext is null for the NullConfiguration class. // this.loggerContext = new WeakReference(Objects.requireNonNull(loggerContext, "loggerContext is null")); this.configurationSource = Objects.requireNonNull(configurationSource, "configurationSource is null"); + if (loggerContext != null) { + injector = loggerContext.getInjector(); + } else { + // for NullConfiguration + injector = DI.createInjector(); + injector.init(); + } componentMap.put(Configuration.CONTEXT_PROPERTIES, properties); - pluginManager = new PluginManager(Node.CATEGORY); - rootNode = new Node(); + interpolatorFactory = injector.getInstance(InterpolatorFactory.class); + tempLookup = interpolatorFactory.newInterpolator(new PropertiesLookup(properties)); + tempLookup.setLoggerContext(loggerContext); + runtimeStrSubstitutor = new RuntimeStrSubstitutor(tempLookup); + configurationStrSubstitutor = new ConfigurationStrSubstitutor(runtimeStrSubstitutor); + configurationScheduler = injector.getInstance(ConfigurationScheduler.class); + watchManager = injector.getInstance(WatchManager.class); setState(State.INITIALIZING); - } @Override @@ -170,14 +199,16 @@ public ScriptManager getScriptManager() { public void setScriptManager(final ScriptManager scriptManager) { this.scriptManager = scriptManager; + injector.registerBinding(ScriptManager.KEY, this::getScriptManager); } - public PluginManager getPluginManager() { - return pluginManager; + public PluginNamespace getCorePlugins() { + return corePlugins; } - public void setPluginManager(final PluginManager pluginManager) { - this.pluginManager = pluginManager; + public void setCorePlugins(final PluginNamespace corePlugins) { + this.corePlugins = corePlugins; + injector.registerBinding(Core.PLUGIN_NAMESPACE_KEY, this::getCorePlugins); } @Override @@ -199,44 +230,94 @@ public AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() { // lazily instantiate only when requested by AsyncLoggers: // loading AsyncLoggerConfigDisruptor requires LMAX Disruptor jar on classpath if (asyncLoggerConfigDisruptor == null) { - asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor(); + asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor(asyncWaitStrategyFactory); } return asyncLoggerConfigDisruptor; } + @Override + public AsyncWaitStrategyFactory getAsyncWaitStrategyFactory() { + return asyncWaitStrategyFactory; + } + /** * Initialize the configuration. */ @Override public void initialize() { - LOGGER.debug("Initializing configuration {}", this); - subst.setConfiguration(this); + LOGGER.debug("{} initializing configuration {}", Version.getProductString(), this); + injector.registerBinding(Configuration.KEY, () -> this); + runtimeStrSubstitutor.setConfiguration(this); + configurationStrSubstitutor.setConfiguration(this); + initializeScriptManager(); + corePlugins = injector.getInstance(Core.PLUGIN_NAMESPACE_KEY); + final PluginNamespace levelPlugins = injector.getInstance(new @Namespace(Level.CATEGORY) Key<>() {}); + levelPlugins.forEach(type -> { + final Class pluginClass = type.getPluginClass(); + try { + // Cause the class to be initialized if it isn't already. + Class.forName(pluginClass.getName(), true, pluginClass.getClassLoader()); + } catch (final Exception e) { + LOGGER.error("Unable to initialize {} due to {}", pluginClass.getName(), e.getClass() + .getSimpleName(), e); + } + }); + setup(); + setupAdvertisement(); + doConfigure(); + setState(State.INITIALIZED); + LOGGER.debug("Configuration {} initialized", this); + } + + private void initializeScriptManager() { try { - scriptManager = new ScriptManager(this, watchManager); + ServiceRegistry.getInstance() + .getServices(ScriptManagerFactory.class, MethodHandles.lookup(), null) + .stream() + .findFirst() + .ifPresent(factory -> setScriptManager(factory.createScriptManager(this, getWatchManager()))); } catch (final LinkageError | Exception e) { // LOG4J2-1920 ScriptEngineManager is not available in Android LOGGER.info("Cannot initialize scripting support because this JRE does not support it.", e); } - pluginManager.collectPlugins(pluginPackages); - final PluginManager levelPlugins = new PluginManager(Level.CATEGORY); - levelPlugins.collectPlugins(pluginPackages); - final Map> plugins = levelPlugins.getPlugins(); - if (plugins != null) { - for (final PluginType type : plugins.values()) { - try { - // Cause the class to be initialized if it isn't already. - Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader()); - } catch (final Exception e) { - LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass() - .getSimpleName(), e); + } + + protected void initializeWatchers(final Reconfigurable reconfigurable, final ConfigurationSource configSource, + final int monitorIntervalSeconds) { + if (configSource != null && (configSource.getFile() != null || configSource.getURL() != null)) { + if (monitorIntervalSeconds > 0) { + watchManager.setIntervalSeconds(monitorIntervalSeconds); + if (configSource.getFile() != null) { + final Source cfgSource = new Source(configSource); + final long lastModified = configSource.getFile().lastModified(); + final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(this, reconfigurable, + listeners, lastModified); + watchManager.watch(cfgSource, watcher); + } else { + if (configSource.getURL() != null) { + monitorSource(reconfigurable, configSource); + } } + } else if (watchManager.hasEventListeners() && configSource.getURL() != null && monitorIntervalSeconds >= 0) { + monitorSource(reconfigurable, configSource); } } - setup(); - setupAdvertisement(); - doConfigure(); - setState(State.INITIALIZED); - LOGGER.debug("Configuration {} initialized", this); + } + + private void monitorSource(final Reconfigurable reconfigurable, final ConfigurationSource configSource) { + if (configSource.getLastModified() > 0) { + final Source cfgSource = new Source(configSource); + final Key key = Key.forClass(WatcherFactory.class); + injector.registerBindingIfAbsent(key, Lazy.lazy(() -> + new WatcherFactory(injector.getInstance(Watcher.PLUGIN_CATEGORY_KEY)))); + final Watcher watcher = injector.getInstance(key) + .newWatcher(cfgSource, this, reconfigurable, listeners, configSource.getLastModified()); + if (watcher != null) { + watchManager.watch(cfgSource, watcher); + } + } else { + LOGGER.info("{} does not support dynamic reconfiguration", configSource.getURI()); + } } /** @@ -250,7 +331,7 @@ public void start() { } LOGGER.debug("Starting configuration {}", this); this.setStarting(); - if (watchManager.getIntervalSeconds() > 0) { + if (watchManager.getIntervalSeconds() >= 0) { watchManager.start(); } if (hasAsyncLoggers()) { @@ -330,36 +411,28 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { asyncLoggerConfigDisruptor.stop(timeout, timeUnit); } + LOGGER.trace("{} notifying ReliabilityStrategies that appenders will be stopped.", cls); + for (final LoggerConfig loggerConfig : loggerConfigs.values()) { + loggerConfig.getReliabilityStrategy().beforeStopAppenders(); + } + root.getReliabilityStrategy().beforeStopAppenders(); + // Stop the appenders in reverse order in case they still have activity. - final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]); + final Appender[] array = appenders.values().toArray(Appender.EMPTY_ARRAY); final List async = getAsyncAppenders(array); if (!async.isEmpty()) { // LOG4J2-511, LOG4J2-392 stop AsyncAppenders first LOGGER.trace("{} stopping {} AsyncAppenders.", cls, async.size()); for (final Appender appender : async) { - if (appender instanceof LifeCycle2) { - ((LifeCycle2) appender).stop(timeout, timeUnit); - } else { - appender.stop(); - } + appender.stop(timeout, timeUnit); } } - LOGGER.trace("{} notifying ReliabilityStrategies that appenders will be stopped.", cls); - for (final LoggerConfig loggerConfig : loggerConfigs.values()) { - loggerConfig.getReliabilityStrategy().beforeStopAppenders(); - } - root.getReliabilityStrategy().beforeStopAppenders(); - LOGGER.trace("{} stopping remaining Appenders.", cls); int appenderCount = 0; for (int i = array.length - 1; i >= 0; --i) { if (array[i].isStarted()) { // then stop remaining Appenders - if (array[i] instanceof LifeCycle2) { - ((LifeCycle2) array[i]).stop(timeout, timeUnit); - } else { - array[i].stop(); - } + array[i].stop(timeout, timeUnit); appenderCount++; } } @@ -415,13 +488,7 @@ public void setup() { } protected Level getDefaultStatus() { - final String statusLevel = PropertiesUtil.getProperties().getStringProperty( - Constants.LOG4J_DEFAULT_STATUS_LEVEL, Level.ERROR.name()); - try { - return Level.toLevel(statusLevel); - } catch (final Exception ex) { - return Level.ERROR; - } + return injector.getInstance(Constants.DEFAULT_STATUS_LEVEL_KEY); } protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource, @@ -442,25 +509,22 @@ protected void createAdvertiser(final String advertiserString, final Configurati private void setupAdvertisement() { if (advertiserNode != null) { final String nodeName = advertiserNode.getName(); - final PluginType type = pluginManager.getPluginType(nodeName); + final PluginType type = corePlugins.get(nodeName); if (type != null) { - final Class clazz = type.getPluginClass().asSubclass(Advertiser.class); - try { - advertiser = clazz.newInstance(); - advertisement = advertiser.advertise(advertiserNode.getAttributes()); - } catch (final InstantiationException e) { - LOGGER.error("InstantiationException attempting to instantiate advertiser: {}", nodeName, e); - } catch (final IllegalAccessException e) { - LOGGER.error("IllegalAccessException attempting to instantiate advertiser: {}", nodeName, e); - } + advertiser = injector.getInstance(type.getPluginClass().asSubclass(Advertiser.class)); + advertisement = advertiser.advertise(advertiserNode.getAttributes()); } } } - @SuppressWarnings("unchecked") @Override public T getComponent(final String componentName) { - return (T) componentMap.get(componentName); + return Cast.cast(componentMap.get(componentName)); + } + + @Override + public Supplier getFactory(final Key key) { + return injector.getFactory(key); } @Override @@ -472,7 +536,7 @@ protected void preConfigure(final Node node) { try { for (final Node child : node.getChildren()) { if (child.getType() == null) { - LOGGER.error("Unable to locate plugin type for " + child.getName()); + LOGGER.error("Unable to locate plugin type for {}", child.getName()); continue; } final Class clazz = child.getType().getPluginClass(); @@ -482,30 +546,131 @@ protected void preConfigure(final Node node) { preConfigure(child); } } catch (final Exception ex) { - LOGGER.error("Error capturing node data for node " + node.getName(), ex); + LOGGER.error("Error capturing node data for node {}", node.getName(), ex); + } + } + + + /** + * Process conditions by evaluating them and including the children of conditions that are true + * and discarding those that are not. + * @param node The node to evaluate. + */ + protected void processConditionals(final Node node) { + try { + final List addList = new ArrayList<>(); + final List removeList = new ArrayList<>(); + for (final Node child : node.getChildren()) { + final PluginType type = child.getType(); + if (type != null && Arbiter.ELEMENT_TYPE.equals(type.getElementType())) { + final Class clazz = type.getPluginClass(); + if (SelectArbiter.class.isAssignableFrom(clazz)) { + removeList.add(child); + addList.addAll(processSelect(child, type)); + } else if (Arbiter.class.isAssignableFrom(clazz)) { + removeList.add(child); + try { + final Arbiter condition = injector.configure(child); + if (condition.isCondition()) { + addList.addAll(child.getChildren()); + processConditionals(child); + } + } catch (final Exception inner) { + LOGGER.error("Exception processing {}: Ignoring and including children", + type.getPluginClass()); + processConditionals(child); + } + } else { + LOGGER.error("Encountered Condition Plugin that does not implement Condition: {}", + child.getName()); + processConditionals(child); + } + } else { + processConditionals(child); + } + } + if (!removeList.isEmpty()) { + final List children = node.getChildren(); + children.removeAll(removeList); + children.addAll(addList); + for (final Node grandChild : addList) { + grandChild.setParent(node); + } + } + } catch (final Exception ex) { + LOGGER.error("Error capturing node data for node {}", node.getName(), ex); } } + /** + * Handle Select nodes. This finds the first child condition that returns true and attaches its children + * to the parent of the Select Node. Other Nodes are discarded. + * @param selectNode The Select Node. + * @param type The PluginType of the Select Node. + * @return The list of Nodes to be added to the parent. + */ + protected List processSelect(final Node selectNode, final PluginType type) { + final List addList = new ArrayList<>(); + final SelectArbiter select = injector.configure(selectNode); + final List conditions = new ArrayList<>(); + for (final Node child : selectNode.getChildren()) { + final PluginType nodeType = child.getType(); + if (nodeType != null) { + if (Arbiter.class.isAssignableFrom(nodeType.getPluginClass())) { + final Arbiter condition = injector.configure(child); + conditions.add(condition); + } else { + LOGGER.error("Invalid Node {} for Select. Must be a Condition", + child.getName()); + } + } else { + LOGGER.error("No PluginType for node {}", child.getName()); + } + } + final Arbiter condition = select.evaluateConditions(conditions); + if (condition != null) { + for (final Node child : selectNode.getChildren()) { + if (condition == child.getObject()) { + addList.addAll(child.getChildren()); + processConditionals(child); + } + } + } + return addList; + } + + protected void doConfigure() { + injector.registerBinding(Keys.SUBSTITUTOR_KEY, () -> configurationStrSubstitutor::replace); + injector.registerBinding(LoggerContext.KEY, () -> loggerContext); + processConditionals(rootNode); preConfigure(rootNode); configurationScheduler.start(); if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) { final Node first = rootNode.getChildren().get(0); createConfiguration(first, null); if (first.getObject() != null) { - subst.setVariableResolver((StrLookup) first.getObject()); + StrLookup lookup = first.getObject(); + if (lookup instanceof LoggerContextAware) { + ((LoggerContextAware) lookup).setLoggerContext(loggerContext.get()); + } + runtimeStrSubstitutor.setVariableResolver(lookup); + configurationStrSubstitutor.setVariableResolver(lookup); } } else { final Map map = this.getComponent(CONTEXT_PROPERTIES); - final StrLookup lookup = map == null ? null : new MapLookup(map); - subst.setVariableResolver(new Interpolator(lookup, pluginPackages)); + final StrLookup lookup = map == null ? null : new PropertiesLookup(map); + Interpolator interpolator = interpolatorFactory.newInterpolator(lookup); + interpolator.setLoggerContext(loggerContext.get()); + runtimeStrSubstitutor.setVariableResolver(interpolator); + configurationStrSubstitutor.setVariableResolver(interpolator); } boolean setLoggers = false; boolean setRoot = false; for (final Node child : rootNode.getChildren()) { if (child.getName().equalsIgnoreCase("Properties")) { - if (tempLookup == subst.getVariableResolver()) { + if (tempLookup == runtimeStrSubstitutor.getVariableResolver()) { LOGGER.error("Properties declaration must be the first element in the configuration"); } continue; @@ -515,14 +680,8 @@ protected void doConfigure() { continue; } if (child.getName().equalsIgnoreCase("Scripts")) { - for (final AbstractScript script : child.getObject(AbstractScript[].class)) { - if (script instanceof ScriptRef) { - LOGGER.error("Script reference to {} not added. Scripts definition cannot contain script references", - script.getName()); - } else { - if (scriptManager != null) { - scriptManager.addScript(script); - }} + if (scriptManager != null) { + scriptManager.addScripts(child); } } else if (child.getName().equalsIgnoreCase("Appenders")) { appenders = child.getObject(); @@ -542,6 +701,9 @@ protected void doConfigure() { final List copy = new ArrayList<>(customLevels); copy.add(child.getObject(CustomLevelConfig.class)); customLevels = copy; + } else if (child.isInstanceOf(AsyncWaitStrategyFactoryConfig.class)) { + AsyncWaitStrategyFactoryConfig awsfc = child.getObject(AsyncWaitStrategyFactoryConfig.class); + asyncWaitStrategyFactory = awsfc.createWaitStrategyFactory(); } else { final List expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"", "\"Scripts\"", "\"CustomLevels\""); @@ -581,8 +743,8 @@ protected void setToDefault() { // LOG4J2-1176 facilitate memory leak investigation setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode())); final Layout layout = PatternLayout.newBuilder() - .withPattern(DefaultConfiguration.DEFAULT_PATTERN) - .withConfiguration(this) + .setPattern(DefaultConfiguration.DEFAULT_PATTERN) + .setConfiguration(this) .build(); final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout); appender.start(); @@ -591,7 +753,7 @@ protected void setToDefault() { rootLoggerConfig.addAppender(appender, null, null); final Level defaultLevel = Level.ERROR; - final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL, + final String levelName = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.CONFIG_DEFAULT_LEVEL, defaultLevel.name()); final Level level = Level.valueOf(levelName); rootLoggerConfig.setLevel(level != null ? level : defaultLevel); @@ -643,9 +805,8 @@ public void removeListener(final ConfigurationListener listener) { * @return the Appender with the specified name or null if the Appender cannot be located. */ @Override - @SuppressWarnings("unchecked") public T getAppender(final String appenderName) { - return (T) appenders.get(appenderName); + return appenderName != null ? Cast.cast(appenders.get(appenderName)) : null; } /** @@ -665,12 +826,19 @@ public Map getAppenders() { */ @Override public void addAppender(final Appender appender) { - appenders.putIfAbsent(appender.getName(), appender); + if (appender != null) { + appenders.putIfAbsent(appender.getName(), appender); + } } @Override public StrSubstitutor getStrSubstitutor() { - return subst; + return runtimeStrSubstitutor; + } + + @Override + public StrSubstitutor getConfigurationStrSubstitutor() { + return configurationStrSubstitutor; } @Override @@ -706,6 +874,9 @@ public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfi @Override public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger, final Appender appender) { + if (appender == null || logger == null) { + return; + } final String loggerName = logger.getName(); appenders.putIfAbsent(appender.getName(), appender); final LoggerConfig lc = getLoggerConfig(loggerName); @@ -781,7 +952,7 @@ public synchronized void removeAppender(final String appenderName) { for (final LoggerConfig logger : loggerConfigs.values()) { logger.removeAppender(appenderName); } - final Appender app = appenders.remove(appenderName); + final Appender app = appenderName != null ? appenders.remove(appenderName) : null; if (app != null) { app.stop(); @@ -882,99 +1053,41 @@ public synchronized void removeLogger(final String loggerName) { @Override public void createConfiguration(final Node node, final LogEvent event) { - final PluginType type = node.getType(); - if (type != null && type.isDeferChildren()) { - node.setObject(createPluginObject(type, node, event)); + final Function stringSubstitutionStrategy; + if (event == null) { + stringSubstitutionStrategy = configurationStrSubstitutor::replace; } else { - for (final Node child : node.getChildren()) { - createConfiguration(child, event); - } - - if (type == null) { - if (node.getParent() != null) { - LOGGER.error("Unable to locate plugin for {}", node.getName()); - } - } else { - node.setObject(createPluginObject(type, node, event)); - } + stringSubstitutionStrategy = str -> runtimeStrSubstitutor.replace(event, str); } + final Injector injector = this.injector.copy().registerBinding(Keys.SUBSTITUTOR_KEY, () -> stringSubstitutionStrategy); + injector.configure(node); } /** - * Invokes a static factory method to either create the desired object or to create a builder object that creates - * the desired object. In the case of a factory method, it should be annotated with - * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with - * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with - * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from a - * string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters} - * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or - * an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is - * called can create these from an array. - * - * Plugins can also be created using a builder class that implements - * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with - * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class, and - * the various fields in the builder class should be annotated similarly to the method parameters. However, instead - * of using PluginAttribute, one should use - * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be - * specified as the default field value instead of as an additional annotation parameter. - * - * In either case, there are also annotations for specifying a - * {@link org.apache.logging.log4j.core.config.Configuration} ( - * {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a - * {@link org.apache.logging.log4j.core.config.Node} ( - * {@link org.apache.logging.log4j.core.config.plugins.PluginNode}). - * - * Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally - * result in unhelpful InvocationTargetExceptions. - * - * @param type the type of plugin to create. - * @param node the corresponding configuration node for this plugin to create. - * @param event the LogEvent that spurred the creation of this plugin - * @return the created plugin object or {@code null} if there was an error setting it up. - * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder - * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor - * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter + * This method is used by Arbiters to create specific children. + * @param node The Node. + * @return The created object or null; */ - private Object createPluginObject(final PluginType type, final Node node, final LogEvent event) { - final Class clazz = type.getPluginClass(); - - if (Map.class.isAssignableFrom(clazz)) { - try { - return createPluginMap(node); - } catch (final Exception e) { - LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e); - } - } - - if (Collection.class.isAssignableFrom(clazz)) { - try { - return createPluginCollection(node); - } catch (final Exception e) { - LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e); - } - } - - return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build(); - } - - private static Map createPluginMap(final Node node) { - final Map map = new LinkedHashMap<>(); - for (final Node child : node.getChildren()) { - final Object object = child.getObject(); - map.put(child.getName(), object); + public Object createPluginObject(final Node node) { + if (this.getState().equals(State.INITIALIZING)) { + final Injector injector = + this.injector.copy().registerBinding(Keys.SUBSTITUTOR_KEY, () -> configurationStrSubstitutor::replace); + return injector.configure(node); } - return map; + LOGGER.warn("Plugin Object creation is not allowed after initialization"); + return null; } - private static Collection createPluginCollection(final Node node) { - final List children = node.getChildren(); - final Collection list = new ArrayList<>(children.size()); - for (final Node child : children) { - final Object object = child.getObject(); - list.add(object); - } - return list; + /** + * This method is used by Arbiters to create specific children. + * @param type The PluginType. + * @param node The Node. + * @return The created object or null; + * @deprecated use {@link #createPluginObject(Node)} + */ + @Deprecated + public Object createPluginObject(final PluginType type, final Node node) { + return createPluginObject(node); } private void setParents() { @@ -1004,27 +1117,20 @@ private void setParents() { * @param is the InputStream to read into a byte array buffer. * @return a byte array of the InputStream contents. * @throws IOException if the {@code read} method of the provided InputStream throws this exception. + * @deprecated use {@link InputStream#readAllBytes()} */ + @Deprecated(since = "3.0.0") protected static byte[] toByteArray(final InputStream is) throws IOException { - final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - - int nRead; - final byte[] data = new byte[BUF_SIZE]; - - while ((nRead = is.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - - return buffer.toByteArray(); + return is.readAllBytes(); } @Override public NanoClock getNanoClock() { - return nanoClock; + return injector.getInstance(NanoClock.class); } @Override public void setNanoClock(final NanoClock nanoClock) { - this.nanoClock = Objects.requireNonNull(nanoClock, "nanoClock"); + injector.registerBinding(NanoClock.KEY, () -> nanoClock); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java index de0a777ec16..b99145494ab 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControl.java @@ -40,14 +40,14 @@ public class AppenderControl extends AbstractFilterable { /** * Constructor. - * + * * @param appender The target Appender. * @param level the Level to filter on. * @param filter the Filter(s) to apply. */ public AppenderControl(final Appender appender, final Level level, final Filter filter) { - super(filter); - this.appender = appender; + super(filter, null); + this.appender = Objects.requireNonNull(appender, "appender"); this.appenderName = appender.getName(); this.level = level; this.intLevel = level == null ? Level.ALL.intLevel() : level.intLevel(); @@ -56,7 +56,7 @@ public AppenderControl(final Appender appender, final Level level, final Filter /** * Returns the name the appender had when this AppenderControl was constructed. - * + * * @return the appender name */ public String getAppenderName() { @@ -65,7 +65,7 @@ public String getAppenderName() { /** * Returns the Appender. - * + * * @return the Appender. */ public Appender getAppender() { @@ -74,7 +74,7 @@ public Appender getAppender() { /** * Call the appender. - * + * * @param event The event to process. */ public void callAppender(final LogEvent event) { @@ -154,15 +154,15 @@ private boolean isFilteredByAppender(final LogEvent event) { private void tryCallAppender(final LogEvent event) { try { appender.append(event); - } catch (final RuntimeException ex) { - handleAppenderError(ex); - } catch (final Exception ex) { - handleAppenderError(new AppenderLoggingException(ex)); + } catch (final RuntimeException error) { + handleAppenderError(event, error); + } catch (final Throwable throwable) { + handleAppenderError(event, new AppenderLoggingException(throwable)); } } - private void handleAppenderError(final RuntimeException ex) { - appender.getHandler().error(createErrorMsg("An exception occurred processing Appender "), ex); + private void handleAppenderError(final LogEvent event, final RuntimeException ex) { + appender.getHandler().error(createErrorMsg("An exception occurred processing Appender "), event, ex); if (!appender.ignoreExceptions()) { throw ex; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControlArraySet.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControlArraySet.java index b0bcab89b5e..54e89ab38de 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControlArraySet.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderControlArraySet.java @@ -20,7 +20,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.util.PerformanceSensitive; @@ -32,7 +32,9 @@ */ @PerformanceSensitive public class AppenderControlArraySet { - private final AtomicReference appenderArray = new AtomicReference<>(new AppenderControl[0]); + private static final AtomicReferenceFieldUpdater appenderArrayUpdater = + AtomicReferenceFieldUpdater.newUpdater(AppenderControlArraySet.class, AppenderControl[].class, "appenderArray"); + private volatile AppenderControl[] appenderArray = new AppenderControl[0]; /** * Adds an AppenderControl to this set. If this set already contains the element, the call leaves the set unchanged @@ -44,7 +46,7 @@ public class AppenderControlArraySet { public boolean add(final AppenderControl control) { boolean success; do { - final AppenderControl[] original = appenderArray.get(); + final AppenderControl[] original = appenderArray; for (final AppenderControl existing : original) { if (existing.equals(control)) { return false; // the appender is already in the list @@ -52,7 +54,7 @@ public boolean add(final AppenderControl control) { } final AppenderControl[] copy = Arrays.copyOf(original, original.length + 1); copy[copy.length - 1] = control; - success = appenderArray.compareAndSet(original, copy); + success = appenderArrayUpdater.compareAndSet(this, original, copy); } while (!success); // could not swap: array was modified by another thread return true; // successfully added } @@ -67,12 +69,12 @@ public AppenderControl remove(final String name) { boolean success; do { success = true; - final AppenderControl[] original = appenderArray.get(); + final AppenderControl[] original = appenderArray; for (int i = 0; i < original.length; i++) { final AppenderControl appenderControl = original[i]; if (Objects.equals(name, appenderControl.getAppenderName())) { final AppenderControl[] copy = removeElementAt(i, original); - if (appenderArray.compareAndSet(original, copy)) { + if (appenderArrayUpdater.compareAndSet(this, original, copy)) { return appenderControl; // successfully removed } success = false; // could not swap: array was modified by another thread @@ -96,7 +98,7 @@ private AppenderControl[] removeElementAt(final int i, final AppenderControl[] a */ public Map asMap() { final Map result = new HashMap<>(); - for (final AppenderControl appenderControl : appenderArray.get()) { + for (final AppenderControl appenderControl : appenderArray) { result.put(appenderControl.getAppenderName(), appenderControl.getAppender()); } return result; @@ -108,11 +110,11 @@ public Map asMap() { * @return the contents before this collection was cleared. */ public AppenderControl[] clear() { - return appenderArray.getAndSet(new AppenderControl[0]); + return appenderArrayUpdater.getAndSet(this, new AppenderControl[0]); } public boolean isEmpty() { - return appenderArray.get().length == 0; + return appenderArray.length == 0; } /** @@ -121,11 +123,11 @@ public boolean isEmpty() { * @return the array supporting this collection */ public AppenderControl[] get() { - return appenderArray.get(); + return appenderArray; } @Override public String toString() { - return "AppenderControlArraySet [appenderArray=" + appenderArray + "]"; + return "AppenderControlArraySet [appenderArray=" + Arrays.toString(appenderArray) + "]"; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderRef.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderRef.java index e0ee77ad0a3..bc94de72764 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderRef.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderRef.java @@ -17,22 +17,22 @@ package org.apache.logging.log4j.core.config; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; /** * An Appender reference. */ -@Plugin(name = "AppenderRef", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin @PluginAliases("appender-ref") public final class AppenderRef { - private static final Logger LOGGER = StatusLogger.getLogger(); private final String ref; private final Level level; @@ -70,14 +70,9 @@ public String toString() { */ @PluginFactory public static AppenderRef createAppenderRef( - @PluginAttribute("ref") final String ref, - @PluginAttribute("level") final Level level, - @PluginElement("Filter") final Filter filter) { - - if (ref == null) { - LOGGER.error("Appender references must contain a reference"); - return null; - } + @PluginAttribute @Required(message = "Appender references must contain a reference") final String ref, + @PluginAttribute final Level level, + @PluginElement final Filter filter) { return new AppenderRef(ref, level, filter); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppendersPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppendersPlugin.java index 03f5e06a4ac..cf45c5f75a5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppendersPlugin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppendersPlugin.java @@ -16,19 +16,20 @@ */ package org.apache.logging.log4j.core.config; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * An Appender container. */ -@Plugin(name = "appenders", category = Core.CATEGORY_NAME) +@Configurable +@Plugin("appenders") public final class AppendersPlugin { private AppendersPlugin() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitCompletionReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitCompletionReliabilityStrategy.java index ccc35d00c16..c3f84430d71 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitCompletionReliabilityStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitCompletionReliabilityStrategy.java @@ -38,7 +38,7 @@ public class AwaitCompletionReliabilityStrategy implements ReliabilityStrategy { private static final int MAX_RETRIES = 3; private final AtomicInteger counter = new AtomicInteger(); - private final AtomicBoolean shutdown = new AtomicBoolean(false); + private final AtomicBoolean shutdown = new AtomicBoolean(); private final Lock shutdownLock = new ReentrantLock(); private final Condition noLogEvents = shutdownLock.newCondition(); // should only be used when shutdown == true private final LoggerConfig loggerConfig; @@ -49,7 +49,7 @@ public AwaitCompletionReliabilityStrategy(final LoggerConfig loggerConfig) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level, * org.apache.logging.log4j.message.Message, java.lang.Throwable) @@ -68,7 +68,26 @@ public void log(final Supplier reconfigured, final String loggerNa /* * (non-Javadoc) - * + * + * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, + * java.lang.String, java.lang.String, java.lang.StackTraceElement, org.apache.logging.log4j.Marker, + * org.apache.logging.log4j.Level, org.apache.logging.log4j.message.Message, java.lang.Throwable) + */ + @Override + public void log(final Supplier reconfigured, final String loggerName, final String fqcn, + final StackTraceElement location, final Marker marker, final Level level, final Message data, + final Throwable t) { + final LoggerConfig config = getActiveLoggerConfig(reconfigured); + try { + config.log(loggerName, fqcn, location, marker, level, data, t); + } finally { + config.getReliabilityStrategy().afterLogEvent(); + } + } + + /* + * (non-Javadoc) + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * org.apache.logging.log4j.core.LogEvent) */ @@ -84,7 +103,7 @@ public void log(final Supplier reconfigured, final LogEvent event) /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config. * LoggerConfig, org.apache.logging.log4j.util.Supplier) @@ -94,7 +113,7 @@ public LoggerConfig getActiveLoggerConfig(final Supplier next) { LoggerConfig result = this.loggerConfig; if (!beforeLogEvent()) { result = next.get(); - return result.getReliabilityStrategy().getActiveLoggerConfig(next); + return result == this.loggerConfig ? result : result.getReliabilityStrategy().getActiveLoggerConfig(next); } return result; } @@ -122,7 +141,7 @@ private void signalCompletionIfShutdown() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -162,7 +181,7 @@ private void waitForCompletion() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitUnconditionallyReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitUnconditionallyReliabilityStrategy.java index 357f18b001b..abc5f59370b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitUnconditionallyReliabilityStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitUnconditionallyReliabilityStrategy.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.config; import java.util.Objects; @@ -22,6 +21,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.PropertiesUtil; @@ -41,13 +41,13 @@ public AwaitUnconditionallyReliabilityStrategy(final LoggerConfig loggerConfig) } private static long sleepMillis() { - return PropertiesUtil.getProperties().getLongProperty("log4j.waitMillisBeforeStopOldConfig", + return PropertiesUtil.getProperties().getLongProperty(Log4jProperties.CONFIG_RELIABILITY_STRATEGY_AWAIT_UNCONDITIONALLY_MILLIS, DEFAULT_SLEEP_MILLIS); } /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level, * org.apache.logging.log4j.message.Message, java.lang.Throwable) @@ -60,7 +60,21 @@ public void log(final Supplier reconfigured, final String loggerNa /* * (non-Javadoc) - * + * + * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, + * java.lang.String, java.lang.String, java.lang.StackTraceElement, org.apache.logging.log4j.Marker, + * org.apache.logging.log4j.Level, org.apache.logging.log4j.message.Message, java.lang.Throwable) + */ + @Override + public void log(final Supplier reconfigured, final String loggerName, final String fqcn, + final StackTraceElement location, final Marker marker, final Level level, final Message data, + final Throwable t) { + loggerConfig.log(loggerName, fqcn, location, marker, level, data, t); + } + + /* + * (non-Javadoc) + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * org.apache.logging.log4j.core.LogEvent) */ @@ -71,7 +85,7 @@ public void log(final Supplier reconfigured, final LogEvent event) /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config. * LoggerConfig, org.apache.logging.log4j.util.Supplier) @@ -83,7 +97,7 @@ public LoggerConfig getActiveLoggerConfig(final Supplier next) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#afterLogEvent() */ @Override @@ -93,7 +107,7 @@ public void afterLogEvent() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -103,7 +117,7 @@ public void beforeStopAppenders() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java index 95e6edcc17b..53fa9a4f94f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java @@ -16,9 +16,6 @@ */ package org.apache.logging.log4j.core.config; -import java.util.List; -import java.util.Map; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Filter; @@ -26,12 +23,20 @@ import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate; +import org.apache.logging.log4j.core.async.AsyncWaitStrategyFactory; import org.apache.logging.log4j.core.filter.Filterable; +import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.net.Advertiser; import org.apache.logging.log4j.core.script.ScriptManager; -import org.apache.logging.log4j.core.util.NanoClock; +import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.core.util.WatchManager; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.Key; + +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; /** * Interface that must be implemented to create a configuration. @@ -40,10 +45,12 @@ *

    * * @see AbstractConfiguration - * @see org.apache.logging.log4j.core.LifeCycle2 */ public interface Configuration extends Filterable { + /** Injection key for the current Configuration. */ + Key KEY = new Key<>() {}; + /** Key for storing the Context properties. */ String CONTEXT_PROPERTIES = "ContextProperties"; @@ -116,10 +123,24 @@ public interface Configuration extends Filterable { StrSubstitutor getStrSubstitutor(); + default StrSubstitutor getConfigurationStrSubstitutor() { + StrSubstitutor defaultSubstitutor = getStrSubstitutor(); + if (defaultSubstitutor == null) { + return new ConfigurationStrSubstitutor(); + } + return new ConfigurationStrSubstitutor(defaultSubstitutor); + } + void createConfiguration(Node node, LogEvent event); T getComponent(String name); + Supplier getFactory(Key key); + + default T getComponent(Key key) { + return getFactory(key).get(); + } + void addComponent(String name, Object object); void setAdvertiser(Advertiser advertiser); @@ -135,7 +156,10 @@ public interface Configuration extends Filterable { /** * Returns the source of this configuration. * - * @return the source of this configuration + * @return the source of this configuration, never {@code null}, but may be + * {@link org.apache.logging.log4j.core.config.ConfigurationSource#NULL_SOURCE} + * or + * {@link org.apache.logging.log4j.core.config.ConfigurationSource#COMPOSITE_SOURCE} */ ConfigurationSource getConfigurationSource(); @@ -167,6 +191,16 @@ public interface Configuration extends Filterable { */ AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate(); + /** + * Returns the {@code AsyncWaitStrategyFactory} defined in this Configuration; + * this factory is used to create the LMAX disruptor {@code WaitStrategy} used + * by the disruptor ringbuffer for Async Loggers. + * + * @return the {@code AsyncWaitStrategyFactory} + * @since 2.17.3 + */ + AsyncWaitStrategyFactory getAsyncWaitStrategyFactory(); + /** * Return the WatchManager. * diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java index 7e6dfb256c0..689e685550e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java @@ -16,35 +16,23 @@ */ package org.apache.logging.log4j.core.config; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.net.URI; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -import org.apache.logging.log4j.Level; + import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; -import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; -import org.apache.logging.log4j.core.lookup.Interpolator; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.util.FileUtils; -import org.apache.logging.log4j.core.util.NetUtils; -import org.apache.logging.log4j.core.util.ReflectionUtil; +import org.apache.logging.log4j.core.util.AuthorizationProvider; +import org.apache.logging.log4j.core.util.BasicAuthorizationProvider; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.model.PluginNamespace; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.LoaderUtil; -import org.apache.logging.log4j.util.PropertiesUtil; -import org.apache.logging.log4j.util.Strings; +import org.apache.logging.log4j.util.PropertyEnvironment; /** * Factory class for parsed {@link Configuration} objects from a configuration file. @@ -53,13 +41,10 @@ *
      *
    1. A system property named "log4j.configurationFactory" can be set with the * name of the ConfigurationFactory to be used.
    2. - *
    3. - * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called - * with the instance of the ConfigurationFactory to be used. This must be called - * before any other calls to Log4j.
    4. + *
    5. An {@link Injector} binding for ConfigurationFactory may be registered.
    6. *
    7. * A ConfigurationFactory implementation can be added to the classpath and configured as a plugin in the - * {@link #CATEGORY ConfigurationFactory} category. The {@link Order} annotation should be used to configure the + * {@link #NAMESPACE ConfigurationFactory} category. The {@link Order} annotation should be used to configure the * factory to be the first one inspected. See * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.
    8. *
    @@ -79,20 +64,28 @@ public ConfigurationFactory() { /** * Allows the ConfigurationFactory class to be specified as a system property. */ - public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory"; + public static final String CONFIGURATION_FACTORY_PROPERTY = Log4jProperties.CONFIG_CONFIGURATION_FACTORY_CLASS_NAME; /** * Allows the location of the configuration file to be specified as a system property. */ - public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile"; + public static final String CONFIGURATION_FILE_PROPERTY = Log4jProperties.CONFIG_LOCATION; + + public static final String LOG4J1_CONFIGURATION_FILE_PROPERTY = Log4jProperties.CONFIG_V1_FILE_NAME; + + public static final String LOG4J1_EXPERIMENTAL = Log4jProperties.CONFIG_V1_COMPATIBILITY_ENABLED; /** - * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin} + * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.plugins.Plugin} * class. * * @since 2.1 */ - public static final String CATEGORY = "ConfigurationFactory"; + public static final String NAMESPACE = "ConfigurationFactory"; + + public static final Key KEY = new Key<>() {}; + + public static final Key PLUGIN_CATEGORY_KEY = new @Namespace(NAMESPACE) Key<>() {}; /** * Allows subclasses access to the status logger without creating another instance. @@ -109,6 +102,9 @@ public ConfigurationFactory() { */ protected static final String DEFAULT_PREFIX = "log4j2"; + protected static final String LOG4J1_VERSION = "1"; + protected static final String LOG4J2_VERSION = "2"; + /** * The name of the classloader URI scheme. */ @@ -119,102 +115,54 @@ public ConfigurationFactory() { */ private static final String CLASS_PATH_SCHEME = "classpath"; - private static volatile List factories = null; - - private static ConfigurationFactory configFactory = new Factory(); - - protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator()); - - private static final Lock LOCK = new ReentrantLock(); + private static final String[] PREFIXES = {"log4j2.", "log4j2.Configuration."}; - /** - * Returns the ConfigurationFactory. - * @return the ConfigurationFactory. - */ + @Deprecated(since = "3.0.0", forRemoval = true) public static ConfigurationFactory getInstance() { - // volatile works in Java 1.6+, so double-checked locking also works properly - //noinspection DoubleCheckedLocking - if (factories == null) { - LOCK.lock(); + return LoggerContext.getContext(false).getInjector().getInstance(KEY); + } + + public static AuthorizationProvider authorizationProvider(final PropertyEnvironment props) { + final String authClass = props.getStringProperty(PREFIXES, "authorizationProvider", null); + AuthorizationProvider provider = null; + if (authClass != null) { try { - if (factories == null) { - final List list = new ArrayList<>(); - final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY); - if (factoryClass != null) { - addFactory(list, factoryClass); - } - final PluginManager manager = new PluginManager(CATEGORY); - manager.collectPlugins(); - final Map> plugins = manager.getPlugins(); - final List> ordered = new ArrayList<>(plugins.size()); - for (final PluginType type : plugins.values()) { - try { - ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class)); - } catch (final Exception ex) { - LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex); - } - } - Collections.sort(ordered, OrderComparator.getInstance()); - for (final Class clazz : ordered) { - addFactory(list, clazz); - } - // see above comments about double-checked locking - //noinspection NonThreadSafeLazyInitialization - factories = Collections.unmodifiableList(list); + final Object obj = LoaderUtil.newInstanceOf(authClass); + if (obj instanceof AuthorizationProvider) { + provider = (AuthorizationProvider) obj; + } else { + LOGGER.warn("{} is not an AuthorizationProvider, using default", obj.getClass().getName()); } - } finally { - LOCK.unlock(); + } catch (final Exception ex) { + LOGGER.warn("Unable to create {}, using default: {}", authClass, ex.getMessage()); } } - - LOGGER.debug("Using configurationFactory {}", configFactory); - return configFactory; - } - - private static void addFactory(final Collection list, final String factoryClass) { - try { - addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class)); - } catch (final Exception ex) { - LOGGER.error("Unable to load class {}", factoryClass, ex); + if (provider == null) { + provider = new BasicAuthorizationProvider(props); } + return provider; } - private static void addFactory(final Collection list, - final Class factoryClass) { - try { - list.add(ReflectionUtil.instantiate(factoryClass)); - } catch (final Exception ex) { - LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex); - } - } + protected StrSubstitutor substitutor; - /** - * Sets the configuration factory. This method is not intended for general use and may not be thread safe. - * @param factory the ConfigurationFactory. - */ - public static void setConfigurationFactory(final ConfigurationFactory factory) { - configFactory = factory; + @Inject + public void setSubstitutor(final StrSubstitutor substitutor) { + this.substitutor = substitutor; } - /** - * Resets the ConfigurationFactory to the default. This method is not intended for general use and may - * not be thread safe. - */ - public static void resetConfigurationFactory() { - configFactory = new Factory(); + protected abstract String[] getSupportedTypes(); + + protected String getTestPrefix() { + return TEST_PREFIX; } - /** - * Removes the ConfigurationFactory. This method is not intended for general use and may not be thread safe. - * @param factory The factory to remove. - */ - public static void removeConfigurationFactory(final ConfigurationFactory factory) { - if (configFactory == factory) { - configFactory = new Factory(); - } + protected String getDefaultPrefix() { + return DEFAULT_PREFIX; } - protected abstract String[] getSupportedTypes(); + protected String getVersion() { + return LOG4J2_VERSION; + } protected boolean isActive() { return true; @@ -284,211 +232,4 @@ static String extractClassLoaderUriPath(final URI uri) { return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart(); } - /** - * Loads the configuration from the location represented by the String. - * @param config The configuration location. - * @param loader The default ClassLoader to use. - * @return The InputSource to use to read the configuration. - */ - protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) { - try { - final URL url = new URL(config); - return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI())); - } catch (final Exception ex) { - final ConfigurationSource source = ConfigurationSource.fromResource(config, loader); - if (source == null) { - try { - final File file = new File(config); - return new ConfigurationSource(new FileInputStream(file), file); - } catch (final FileNotFoundException fnfe) { - // Ignore the exception - LOGGER.catching(Level.DEBUG, fnfe); - } - } - return source; - } - } - - /** - * Default Factory. - */ - private static class Factory extends ConfigurationFactory { - - private static final String ALL_TYPES = "*"; - - /** - * Default Factory Constructor. - * @param name The configuration name. - * @param configLocation The configuration location. - * @return The Configuration. - */ - @Override - public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { - - if (configLocation == null) { - final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties() - .getStringProperty(CONFIGURATION_FILE_PROPERTY)); - if (configLocationStr != null) { - final String[] sources = configLocationStr.split(","); - if (sources.length > 1) { - final List configs = new ArrayList<>(); - for (final String sourceLocation : sources) { - final Configuration config = getConfiguration(loggerContext, sourceLocation.trim()); - if (config != null && config instanceof AbstractConfiguration) { - configs.add((AbstractConfiguration) config); - } else { - LOGGER.error("Failed to created configuration at {}", sourceLocation); - return null; - } - } - return new CompositeConfiguration(configs); - } - return getConfiguration(loggerContext, configLocationStr); - } - for (final ConfigurationFactory factory : getFactories()) { - final String[] types = factory.getSupportedTypes(); - if (types != null) { - for (final String type : types) { - if (type.equals(ALL_TYPES)) { - final Configuration config = factory.getConfiguration(loggerContext, name, configLocation); - if (config != null) { - return config; - } - } - } - } - } - } else { - // configLocation != null - final String configLocationStr = configLocation.toString(); - for (final ConfigurationFactory factory : getFactories()) { - final String[] types = factory.getSupportedTypes(); - if (types != null) { - for (final String type : types) { - if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) { - final Configuration config = factory.getConfiguration(loggerContext, name, configLocation); - if (config != null) { - return config; - } - } - } - } - } - } - - Configuration config = getConfiguration(loggerContext, true, name); - if (config == null) { - config = getConfiguration(loggerContext, true, null); - if (config == null) { - config = getConfiguration(loggerContext, false, name); - if (config == null) { - config = getConfiguration(loggerContext, false, null); - } - } - } - if (config != null) { - return config; - } - LOGGER.error("No Log4j 2 configuration file found. " + - "Using default configuration (logging only errors to the console), " + - "or user programmatically provided configurations. " + - "Set system property 'log4j2.debug' " + - "to show Log4j 2 internal initialization logging. " + - "See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2"); - return new DefaultConfiguration(); - } - - private Configuration getConfiguration(final LoggerContext loggerContext, final String configLocationStr) { - ConfigurationSource source = null; - try { - source = ConfigurationSource.fromUri(NetUtils.toURI(configLocationStr)); - } catch (final Exception ex) { - // Ignore the error and try as a String. - LOGGER.catching(Level.DEBUG, ex); - } - if (source == null) { - final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); - source = getInputFromString(configLocationStr, loader); - } - if (source != null) { - for (final ConfigurationFactory factory : getFactories()) { - final String[] types = factory.getSupportedTypes(); - if (types != null) { - for (final String type : types) { - if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) { - final Configuration config = factory.getConfiguration(loggerContext, source); - if (config != null) { - return config; - } - } - } - } - } - } - return null; - } - - private Configuration getConfiguration(final LoggerContext loggerContext, final boolean isTest, final String name) { - final boolean named = Strings.isNotEmpty(name); - final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); - for (final ConfigurationFactory factory : getFactories()) { - String configName; - final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX; - final String [] types = factory.getSupportedTypes(); - if (types == null) { - continue; - } - - for (final String suffix : types) { - if (suffix.equals(ALL_TYPES)) { - continue; - } - configName = named ? prefix + name + suffix : prefix + suffix; - - final ConfigurationSource source = ConfigurationSource.fromResource(configName, loader); - if (source != null) { - if (!factory.isActive()) { - LOGGER.warn("Found configuration file {} for inactive ConfigurationFactory {}", configName, factory.getClass().getName()); - } - return factory.getConfiguration(loggerContext, source); - } - } - } - return null; - } - - @Override - public String[] getSupportedTypes() { - return null; - } - - @Override - public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) { - if (source != null) { - final String config = source.getLocation(); - for (final ConfigurationFactory factory : getFactories()) { - final String[] types = factory.getSupportedTypes(); - if (types != null) { - for (final String type : types) { - if (type.equals(ALL_TYPES) || config != null && config.endsWith(type)) { - final Configuration c = factory.getConfiguration(loggerContext, source); - if (c != null) { - LOGGER.debug("Loaded configuration from {}", source); - return c; - } - LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config); - return null; - } - } - } - } - } - LOGGER.error("Cannot process configuration, input source is null"); - return null; - } - } - - static List getFactories() { - return factories; - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java new file mode 100644 index 00000000000..c1569fff452 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import java.io.File; +import java.util.List; + +import org.apache.logging.log4j.core.util.AbstractWatcher; +import org.apache.logging.log4j.core.util.FileWatcher; +import org.apache.logging.log4j.core.util.Source; +import org.apache.logging.log4j.core.util.Watcher; + +/** + * Watcher for configuration files. Causes a reconfiguration when a file changes. + */ +public class ConfigurationFileWatcher extends AbstractWatcher implements FileWatcher { + + private File file; + private long lastModifiedMillis; + + public ConfigurationFileWatcher(final Configuration configuration, final Reconfigurable reconfigurable, + final List configurationListeners, final long lastModifiedMillis) { + super(configuration, reconfigurable, configurationListeners); + this.lastModifiedMillis = lastModifiedMillis; + } + + public long getLastModified() { + return file != null ? file.lastModified() : 0; + } + + @Override + public void fileModified(final File file) { + lastModifiedMillis = file.lastModified(); + } + + @Override + public void watching(final Source source) { + file = source.getFile(); + lastModifiedMillis = file.lastModified(); + super.watching(source); + } + + @Override + public boolean isModified() { + return lastModifiedMillis != file.lastModified(); + } + + @Override + public Watcher newWatcher(final Reconfigurable reconfigurable, final List listeners, + final long lastModifiedMillis) { + final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(getConfiguration(), reconfigurable, listeners, + lastModifiedMillis); + if (getSource() != null) { + watcher.watching(getSource()); + } + return watcher; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java index 5341337b8f6..e393d4c124e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java @@ -38,8 +38,8 @@ public class ConfigurationScheduler extends AbstractLifeCycle { private static final Logger LOGGER = StatusLogger.getLogger(); private static final String SIMPLE_NAME = "Log4j2 " + ConfigurationScheduler.class.getSimpleName(); private static final int MAX_SCHEDULED_ITEMS = 5; - - private ScheduledExecutorService executorService; + + private volatile ScheduledExecutorService executorService; private int scheduledItems = 0; private final String name; @@ -193,17 +193,21 @@ public long nextFireInterval(final Date fireDate) { private ScheduledExecutorService getExecutorService() { if (executorService == null) { - if (scheduledItems > 0) { - LOGGER.debug("{} starting {} threads", name, scheduledItems); - scheduledItems = Math.min(scheduledItems, MAX_SCHEDULED_ITEMS); - final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(scheduledItems, - Log4jThreadFactory.createDaemonThreadFactory("Scheduled")); - executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); - executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - this.executorService = executor; + synchronized (this) { + if (executorService == null) { + if (scheduledItems > 0) { + LOGGER.debug("{} starting {} threads", name, scheduledItems); + scheduledItems = Math.min(scheduledItems, MAX_SCHEDULED_ITEMS); + final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(scheduledItems, + Log4jThreadFactory.createDaemonThreadFactory("Scheduled")); + executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); + executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.executorService = executor; - } else { - LOGGER.debug("{}: No scheduled items", name); + } else { + LOGGER.debug("{}: No scheduled items", name); + } + } } } return executorService; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java index 7848d6a08b3..cc8b2752c7d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java @@ -14,11 +14,9 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.config; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -28,41 +26,83 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Objects; +import javax.net.ssl.HttpsURLConnection; -import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory; +import org.apache.logging.log4j.core.util.AuthorizationProvider; import org.apache.logging.log4j.core.util.FileUtils; import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.core.util.Source; import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.PropertiesUtil; /** * Represents the source for the logging configuration. */ public class ConfigurationSource { + /** * ConfigurationSource to use with Configurations that do not require a "real" configuration source. */ - public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]); + public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0], null, 0); + /** + * ConfigurationSource to use with {@link org.apache.logging.log4j.core.config.composite.CompositeConfiguration}. + */ + public static final ConfigurationSource COMPOSITE_SOURCE = new ConfigurationSource(new byte[0], null, 0); + private static final String HTTPS = "https"; + private static final String HTTP = "http"; - private final File file; - private final URL url; - private final String location; private final InputStream stream; - private final byte[] data; + private volatile byte[] data; + private volatile Source source; + private final long lastModified; + // Set when the configuration has been updated so reset can use it for the next lastModified timestamp. + private volatile long modifiedMillis; /** * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified * file. * - * @param stream the input stream + * @param stream the input stream, the caller is responsible for closing this resource. * @param file the file where the input stream originated */ public ConfigurationSource(final InputStream stream, final File file) { this.stream = Objects.requireNonNull(stream, "stream is null"); - this.file = Objects.requireNonNull(file, "file is null"); - this.location = file.getAbsolutePath(); - this.url = null; this.data = null; + this.source = new Source(file); + long modified = 0; + try { + modified = file.lastModified(); + } catch (final Exception ex) { + // There is a problem with the file. It will be handled somewhere else. + } + this.lastModified = modified; + } + + /** + * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified + * path. + * + * @param stream the input stream, the caller is responsible for closing this resource. + * @param path the path where the input stream originated. + */ + public ConfigurationSource(final InputStream stream, final Path path) { + this.stream = Objects.requireNonNull(stream, "stream is null"); + this.data = null; + this.source = new Source(path); + long modified = 0; + try { + modified = Files.getLastModifiedTime(path).toMillis(); + } catch (Exception ex) { + // There is a problem with the file. It will be handled somewhere else. + } + this.lastModified = modified; } /** @@ -74,49 +114,54 @@ public ConfigurationSource(final InputStream stream, final File file) { */ public ConfigurationSource(final InputStream stream, final URL url) { this.stream = Objects.requireNonNull(stream, "stream is null"); - this.url = Objects.requireNonNull(url, "URL is null"); - this.location = url.toString(); - this.file = null; this.data = null; + this.lastModified = 0; + this.source = new Source(url); + } + + /** + * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified + * url. + * + * @param stream the input stream, the caller is responsible for closing this resource. + * @param url the URL where the input stream originated + * @param lastModified when the source was last modified. + */ + public ConfigurationSource(final InputStream stream, final URL url, long lastModified) { + this.stream = Objects.requireNonNull(stream, "stream is null"); + this.data = null; + this.lastModified = lastModified; + this.source = new Source(url); } /** * Constructs a new {@code ConfigurationSource} with the specified input stream. Since the stream is the only source * of data, this constructor makes a copy of the stream contents. * - * @param stream the input stream + * @param stream the input stream, the caller is responsible for closing this resource. * @throws IOException if an exception occurred reading from the specified stream */ public ConfigurationSource(final InputStream stream) throws IOException { - this(toByteArray(stream)); + this(stream.readAllBytes(), null, 0); } - private ConfigurationSource(final byte[] data) { + public ConfigurationSource(final Source source, final byte[] data, final long lastModified) throws IOException { + Objects.requireNonNull(source, "source is null"); this.data = Objects.requireNonNull(data, "data is null"); this.stream = new ByteArrayInputStream(data); - this.file = null; - this.url = null; - this.location = null; + this.lastModified = lastModified; + this.source = source; } - /** - * Returns the contents of the specified {@code InputStream} as a byte array. - * - * @param inputStream the stream to read - * @return the contents of the specified stream - * @throws IOException if a problem occurred reading from the stream - */ - private static byte[] toByteArray(final InputStream inputStream) throws IOException { - final int buffSize = Math.max(4096, inputStream.available()); - final ByteArrayOutputStream contents = new ByteArrayOutputStream(buffSize); - final byte[] buff = new byte[buffSize]; - - int length = inputStream.read(buff); - while (length > 0) { - contents.write(buff, 0, length); - length = inputStream.read(buff); + private ConfigurationSource(final byte[] data, final URL url, final long lastModified) { + this.data = Objects.requireNonNull(data, "data is null"); + this.stream = new ByteArrayInputStream(data); + this.lastModified = lastModified; + if (url == null) { + this.data = data; + } else { + this.source = new Source(url); } - return contents.toByteArray(); } /** @@ -126,7 +171,19 @@ private static byte[] toByteArray(final InputStream inputStream) throws IOExcept * @return the configuration source file, or {@code null} */ public File getFile() { - return file; + return source == null ? null : source.getFile(); + } + + private boolean isFile() { + return source == null ? false : source.getFile() != null; + } + + private boolean isURL() { + return source == null ? false : source.getURI() != null; + } + + private boolean isLocation() { + return source == null ? false : source.getLocation() != null; } /** @@ -136,7 +193,23 @@ public File getFile() { * @return the configuration source URL, or {@code null} */ public URL getURL() { - return url; + return source == null ? null : source.getURL(); + } + + /** + * @deprecated Not used internally, no replacement. TODO remove and make source final. + */ + @Deprecated + public void setSource(Source source) { + this.source = source; + } + + public void setData(final byte[] data) { + this.data = data; + } + + public void setModifiedMillis(final long modifiedMillis) { + this.modifiedMillis = modifiedMillis; } /** @@ -144,30 +217,15 @@ public URL getURL() { * @return The URI. */ public URI getURI() { - URI sourceURI = null; - if (url != null) { - try { - sourceURI = url.toURI(); - } catch (final URISyntaxException ex) { - /* Ignore the exception */ - } - } - if (sourceURI == null && file != null) { - sourceURI = file.toURI(); - } - if (sourceURI == null && location != null) { - try { - sourceURI = new URI(location); - } catch (final URISyntaxException ex) { - // Assume the scheme was missing. - try { - sourceURI = new URI("file://" + location); - } catch (final URISyntaxException uriEx) { - /* Ignore the exception */ - } - } - } - return sourceURI; + return source == null ? null : source.getURI(); + } + + /** + * Returns the time the resource was last modified or 0 if it is not available. + * @return the last modified time of the resource. + */ + public long getLastModified() { + return lastModified; } /** @@ -177,7 +235,7 @@ public URI getURI() { * @return a string describing the configuration source file or URL, or {@code null} */ public String getLocation() { - return location; + return source == null ? null : source.getLocation(); } /** @@ -196,23 +254,32 @@ public InputStream getInputStream() { * @throws IOException if a problem occurred while opening the new input stream */ public ConfigurationSource resetInputStream() throws IOException { - if (file != null) { - return new ConfigurationSource(new FileInputStream(file), file); - } else if (url != null) { - return new ConfigurationSource(url.openStream(), url); - } else { - return new ConfigurationSource(data); + if (source != null && data != null) { + return new ConfigurationSource(source, data, this.lastModified); + } else if (isFile()) { + return new ConfigurationSource(new FileInputStream(getFile()), getFile()); + } else if (isURL() && data != null) { + // Creates a ConfigurationSource without accessing the URL since the data was provided. + return new ConfigurationSource(data, getURL(), modifiedMillis == 0 ? lastModified : modifiedMillis); + } else if (isURL()) { + return fromUri(getURI()); + } else if (data != null) { + return new ConfigurationSource(data, null, lastModified); } + return null; } @Override public String toString() { - if (location != null) { - return location; + if (isLocation()) { + return getLocation(); } if (this == NULL_SOURCE) { return "NULL_SOURCE"; } + if (this == COMPOSITE_SOURCE) { + return "COMPOSITE_SOURCE"; + } final int length = data == null ? -1 : data.length; return "stream (" + length + " bytes, unknown location)"; } @@ -234,21 +301,16 @@ public static ConfigurationSource fromUri(final URI configLocation) { if (ConfigurationFactory.isClassLoaderUri(configLocation)) { final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); final String path = ConfigurationFactory.extractClassLoaderUriPath(configLocation); - final ConfigurationSource source = fromResource(path, loader); - if (source != null) { - return source; - } + return fromResource(path, loader); } if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL() ConfigurationFactory.LOGGER.error("File not found in file system or classpath: {}", configLocation.toString()); return null; } try { - return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL()); + return getConfigurationSource(configLocation.toURL()); } catch (final MalformedURLException ex) { ConfigurationFactory.LOGGER.error("Invalid URL {}", configLocation.toString(), ex); - } catch (final Exception ex) { - ConfigurationFactory.LOGGER.error("Unable to access {}", configLocation.toString(), ex); } return null; } @@ -264,25 +326,40 @@ public static ConfigurationSource fromResource(final String resource, final Clas if (url == null) { return null; } - InputStream is = null; + return getConfigurationSource(url); + } + + private static ConfigurationSource getConfigurationSource(final URL url) { try { - is = url.openStream(); - } catch (final IOException ioe) { - ConfigurationFactory.LOGGER.catching(Level.DEBUG, ioe); - return null; - } - if (is == null) { - return null; - } - - if (FileUtils.isFile(url)) { + final URLConnection urlConnection = url.openConnection(); + // A "jar:" URL file remains open after the stream is closed, so do not cache it. + urlConnection.setUseCaches(false); + final AuthorizationProvider provider = ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties()); + provider.addAuthorization(urlConnection); + if (url.getProtocol().equals(HTTPS)) { + final SslConfiguration sslConfiguration = SslConfigurationFactory.getSslConfiguration(); + if (sslConfiguration != null) { + ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory()); + if (!sslConfiguration.isVerifyHostName()) { + ((HttpsURLConnection) urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE); + } + } + } + final File file = FileUtils.fileFromUri(url.toURI()); try { - return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI())); - } catch (final URISyntaxException ex) { - // Just ignore the exception. - ConfigurationFactory.LOGGER.catching(Level.DEBUG, ex); + if (file != null) { + return new ConfigurationSource(urlConnection.getInputStream(), FileUtils.fileFromUri(url.toURI())); + } else { + return new ConfigurationSource(urlConnection.getInputStream(), url, urlConnection.getLastModified()); + } + } catch (final FileNotFoundException ex) { + ConfigurationFactory.LOGGER.info("Unable to locate file {}, ignoring.", url.toString()); + return null; } + } catch (final IOException | URISyntaxException ex) { + ConfigurationFactory.LOGGER.warn("Error accessing {} due to {}, ignoring.", url.toString(), + ex.getMessage()); + return null; } - return new ConfigurationSource(is, url); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfiguratonFileWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfiguratonFileWatcher.java deleted file mode 100644 index 38491f2fb62..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfiguratonFileWatcher.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config; - -import java.io.File; -import java.util.List; - -import org.apache.logging.log4j.core.util.FileWatcher; -import org.apache.logging.log4j.core.util.Log4jThreadFactory; - -/** - * Watcher for configuration files. Causes a reconfiguration when a file changes. - */ -public class ConfiguratonFileWatcher implements FileWatcher { - - private final Reconfigurable reconfigurable; - private final List configurationListeners; - private final Log4jThreadFactory threadFactory; - - public ConfiguratonFileWatcher(final Reconfigurable reconfigurable, final List configurationListeners) { - this.reconfigurable = reconfigurable; - this.configurationListeners = configurationListeners; - this.threadFactory = Log4jThreadFactory.createDaemonThreadFactory("ConfiguratonFileWatcher"); - } - - public List getListeners() { - return configurationListeners; - } - - - @Override - public void fileModified(final File file) { - for (final ConfigurationListener configurationListener : configurationListeners) { - final Thread thread = threadFactory.newThread(new ReconfigurationRunnable(configurationListener, reconfigurable)); - thread.start(); - } - } - - /** - * Helper class for triggering a reconfiguration in a background thread. - */ - private static class ReconfigurationRunnable implements Runnable { - - private final ConfigurationListener configurationListener; - private final Reconfigurable reconfigurable; - - public ReconfigurationRunnable(final ConfigurationListener configurationListener, final Reconfigurable reconfigurable) { - this.configurationListener = configurationListener; - this.reconfigurable = reconfigurable; - } - - @Override - public void run() { - configurationListener.onChange(reconfigurable); - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configurator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configurator.java index 28dd85fda6e..96e7c1fd4ed 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configurator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configurator.java @@ -17,7 +17,6 @@ package org.apache.logging.log4j.core.config; import java.net.URI; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -30,6 +29,7 @@ import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; /** @@ -46,14 +46,14 @@ private static Log4jContextFactory getFactory() { final LoggerContextFactory factory = LogManager.getFactory(); if (factory instanceof Log4jContextFactory) { return (Log4jContextFactory) factory; - } else if (factory != null) { + } + if (factory != null) { LOGGER.error("LogManager returned an instance of {} which does not implement {}. Unable to initialize Log4j.", factory.getClass().getName(), Log4jContextFactory.class.getName()); - return null; } else { LOGGER.fatal("LogManager did not return a LoggerContextFactory. This indicates something has gone terribly wrong!"); - return null; } + return null; } /** @@ -115,20 +115,8 @@ public static LoggerContext initialize(final String name, final ClassLoader load if (Strings.isBlank(configLocation)) { return initialize(name, loader, (URI) null, externalContext); } - if (configLocation.contains(",")) { - final String[] parts = configLocation.split(","); - String scheme = null; - final List uris = new ArrayList<>(parts.length); - for (final String part : parts) { - final URI uri = NetUtils.toURI(scheme != null ? scheme + ":" + part.trim() : part.trim()); - if (scheme == null && uri.getScheme() != null) { - scheme = uri.getScheme(); - } - uris.add(uri); - } - return initialize(name, loader, uris, externalContext); - } - return initialize(name, loader, NetUtils.toURI(configLocation), externalContext); + return configLocation.contains(",") ? initialize(name, loader, NetUtils.toURIs(configLocation), externalContext) : + initialize(name, loader, NetUtils.toURI(configLocation), externalContext); } /** @@ -164,6 +152,28 @@ public static LoggerContext initialize(final String name, final ClassLoader load return null; } + /** + * Initializes the Logging Context. + * @param name The Context name. + * @param loader The ClassLoader for the Context (or null). + * @param configLocation The configuration for the logging context (or null). + * @param entry The external context entry to be attached to the LoggerContext + * @return The LoggerContext. + */ + public static LoggerContext initialize(final String name, final ClassLoader loader, final URI configLocation, + final Map.Entry entry) { + + try { + final Log4jContextFactory factory = getFactory(); + return factory == null ? null : + factory.getContext(FQCN, loader, entry, false, configLocation, name); + } catch (final Exception ex) { + LOGGER.error("There was a problem initializing the LoggerContext [{}] using configuration at [{}].", + name, configLocation, ex); + } + return null; + } + public static LoggerContext initialize(final String name, final ClassLoader loader, final List configLocations, final Object externalContext) { try { @@ -226,8 +236,62 @@ public static LoggerContext initialize(final ClassLoader loader, final Configura return null; } + /** + * Reconfigure using an already constructed Configuration. + * @param configuration The configuration. + * @since 2.13.0 + */ + public static void reconfigure(final Configuration configuration) { + try { + final Log4jContextFactory factory = getFactory(); + if (factory != null) { + factory.getContext(FQCN, null, null, false) + .reconfigure(configuration); + } + } catch (final Exception ex) { + LOGGER.error("There was a problem initializing the LoggerContext using configuration {}", + configuration.getName(), ex); + } + } + + /** + * Reload the existing reconfiguration. + * @since 2.12.0 + */ + public static void reconfigure() { + try { + final Log4jContextFactory factory = getFactory(); + if (factory != null) { + factory.getSelector().getContext(FQCN, null, false).reconfigure(); + } else { + LOGGER.warn("Unable to reconfigure - Log4j has not been initialized."); + } + } catch (final Exception ex) { + LOGGER.error("Error encountered trying to reconfigure logging", ex); + } + } + + /** + * Reconfigure with a potentially new configuration. + * @param uri The location of the configuration. + * @since 2.12.0 + */ + public static void reconfigure(final URI uri) { + try { + final Log4jContextFactory factory = getFactory(); + if (factory != null) { + factory.getSelector().getContext(FQCN, null, false).setConfigLocation(uri); + } else { + LOGGER.warn("Unable to reconfigure - Log4j has not been initialized."); + } + } catch (final Exception ex) { + LOGGER.error("Error encountered trying to reconfigure logging", ex); + } + } + /** * Sets the levels of parentLogger and all 'child' loggers to the given level. + * * @param parentLogger the parent logger * @param level the new level */ @@ -237,7 +301,7 @@ public static void setAllLevels(final String parentLogger, final Level level) { // 3) set level on logger config // 4) update child logger configs with level // 5) update loggers - final LoggerContext loggerContext = LoggerContext.getContext(false); + final LoggerContext loggerContext = LoggerContext.getContext(StackLocatorUtil.getCallerClassLoader(2), false, null); final Configuration config = loggerContext.getConfiguration(); boolean set = setLevel(parentLogger, level, config); for (final Map.Entry entry : config.getLoggers().entrySet()) { @@ -250,6 +314,34 @@ public static void setAllLevels(final String parentLogger, final Level level) { } } + /** + * Sets a logger's level. + * + * @param logger + * the logger + * @param level + * the new level + * @return the given logger + */ + public static Logger setLevel(final Logger logger, final Level level) { + setLevel(LoggerContext.getContext(StackLocatorUtil.getCallerClassLoader(2), false, null), logger.getName(), level); + return logger; + } + + /** + * Sets a logger's level. + * + * @param clazz + * the logger + * @param level + * the new level + */ + public static void setLevel(final Class clazz, final Level level) { + final String canonicalName = clazz.getCanonicalName(); + setLevel(LoggerContext.getContext(StackLocatorUtil.getCallerClassLoader(2), false, null), + canonicalName != null ? canonicalName : clazz.getName(), level); + } + private static boolean setLevel(final LoggerConfig loggerConfig, final Level level) { final boolean set = !loggerConfig.getLevel().equals(level); if (set) { @@ -258,6 +350,14 @@ private static boolean setLevel(final LoggerConfig loggerConfig, final Level lev return set; } + private static void setLevel(final LoggerContext loggerContext, final String loggerName, final Level level) { + if (Strings.isEmpty(loggerName)) { + setRootLevel(level, loggerContext); + } else if (setLevel(loggerName, level, loggerContext.getConfiguration())) { + loggerContext.updateLoggers(); + } + } + /** * Sets logger levels. * @@ -266,7 +366,7 @@ private static boolean setLevel(final LoggerConfig loggerConfig, final Level lev * Levels. */ public static void setLevel(final Map levelMap) { - final LoggerContext loggerContext = LoggerContext.getContext(false); + final LoggerContext loggerContext = LoggerContext.getContext(StackLocatorUtil.getCallerClassLoader(2), false, null); final Configuration config = loggerContext.getConfiguration(); boolean set = false; for (final Map.Entry entry : levelMap.entrySet()) { @@ -288,18 +388,23 @@ public static void setLevel(final Map levelMap) { * the new level */ public static void setLevel(final String loggerName, final Level level) { - final LoggerContext loggerContext = LoggerContext.getContext(false); - if (Strings.isEmpty(loggerName)) { - setRootLevel(level); - } else { - if (setLevel(loggerName, level, loggerContext.getConfiguration())) { - loggerContext.updateLoggers(); - } - } + setLevel(LoggerContext.getContext(StackLocatorUtil.getCallerClassLoader(2), false, null), loggerName, level); + } + + /** + * Sets a logger's level. + * + * @param loggerName + * the logger name + * @param level + * the new level + */ + public static void setLevel(final String loggerName, final String level) { + setLevel(LoggerContext.getContext(StackLocatorUtil.getCallerClassLoader(2), false, null), loggerName, Level.toLevel(level)); } private static boolean setLevel(final String loggerName, final Level level, final Configuration config) { - boolean set; + final boolean set; LoggerConfig loggerConfig = config.getLoggerConfig(loggerName); if (!loggerName.equals(loggerConfig.getName())) { // TODO Should additivity be inherited? @@ -320,7 +425,10 @@ private static boolean setLevel(final String loggerName, final Level level, fina * the new level */ public static void setRootLevel(final Level level) { - final LoggerContext loggerContext = LoggerContext.getContext(false); + setRootLevel(level, LoggerContext.getContext(StackLocatorUtil.getCallerClassLoader(2), false, null)); + } + + private static void setRootLevel(final Level level, final LoggerContext loggerContext) { final LoggerConfig loggerConfig = loggerContext.getConfiguration().getRootLogger(); if (!loggerConfig.getLevel().equals(level)) { loggerConfig.setLevel(level); @@ -335,7 +443,7 @@ public static void setRootLevel(final Level level) { * rollover thread is done. When this method returns, these tasks' status are undefined, the tasks may be done or * not. *

    - * + * * @param ctx * the logger context to shut down, may be null. */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevelConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevelConfig.java index ca6bd9b5854..96d8c863062 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevelConfig.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevelConfig.java @@ -16,19 +16,20 @@ */ package org.apache.logging.log4j.core.config; -import java.util.Objects; - import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; +import java.util.Objects; + /** * Descriptor of a custom Level object that is created via configuration. */ -@Plugin(name = "CustomLevel", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("CustomLevel") public final class CustomLevelConfig { private final String levelName; @@ -42,7 +43,7 @@ private CustomLevelConfig(final String levelName, final int intLevel) { /** * Creates a CustomLevelConfig object. This also defines the Level object with a call to * {@link Level#forName(String, int)}. - * + * * @param levelName name of the custom level. * @param intLevel the intLevel that determines where this level resides relative to the built-in levels * @return A CustomLevelConfig object. @@ -50,7 +51,7 @@ private CustomLevelConfig(final String levelName, final int intLevel) { @PluginFactory public static CustomLevelConfig createLevel(// @formatter:off @PluginAttribute("name") final String levelName, - @PluginAttribute("intLevel") final int intLevel) { + @PluginAttribute final int intLevel) { // @formatter:on StatusLogger.getLogger().debug("Creating CustomLevel(name='{}', intValue={})", levelName, intLevel); @@ -60,7 +61,7 @@ public static CustomLevelConfig createLevel(// @formatter:off /** * Returns the custom level name. - * + * * @return the custom level name */ public String getLevelName() { @@ -70,7 +71,7 @@ public String getLevelName() { /** * Returns the custom level intLevel that determines the strength of the custom level relative to the built-in * levels. - * + * * @return the custom level intLevel */ public int getIntLevel() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevels.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevels.java index 0b438582e17..173917c1832 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevels.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/CustomLevels.java @@ -17,19 +17,20 @@ package org.apache.logging.log4j.core.config; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Container for CustomLevelConfig objects. */ -@Plugin(name = "CustomLevels", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public final class CustomLevels { private final List customLevels; @@ -40,7 +41,7 @@ private CustomLevels(final CustomLevelConfig[] customLevels) { /** * Create a CustomLevels object to contain all the CustomLevelConfigs. - * + * * @param customLevels An array of CustomLevelConfigs. * @return A CustomLevels object. */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultAdvertiser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultAdvertiser.java index cc0e1bce7f9..00fd2ba49e5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultAdvertiser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultAdvertiser.java @@ -16,15 +16,17 @@ */ package org.apache.logging.log4j.core.config; -import java.util.Map; - -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.net.Advertiser; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; + +import java.util.Map; /** * The default advertiser does not do anything. */ -@Plugin(name = "default", category = Node.CATEGORY, elementType = "advertiser", printObject = false) +@Configurable(elementType = "advertiser") +@Plugin("default") public class DefaultAdvertiser implements Advertiser { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java index cbedf7c1497..ea315e49c0c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfiguration.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.core.config; +import org.apache.logging.log4j.core.impl.Log4jProperties; + /** * The default configuration writes all output to the Console using the default logging level. You configure default * logging level by setting the system property "org.apache.logging.log4j.level" to a level name. If you do not @@ -28,12 +30,12 @@ public class DefaultConfiguration extends AbstractConfiguration { * The name of the default configuration. */ public static final String DEFAULT_NAME = "Default"; - + /** * The System Property used to specify the logging level. */ - public static final String DEFAULT_LEVEL = "org.apache.logging.log4j.level"; - + public static final String DEFAULT_LEVEL = Log4jProperties.CONFIG_DEFAULT_LEVEL; + /** * The default Pattern used for the default Layout. */ @@ -50,4 +52,9 @@ public DefaultConfiguration() { @Override protected void doConfigure() { } + + @Override + public String toString() { + return getClass().getSimpleName(); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfigurationFactory.java new file mode 100644 index 00000000000..9d5e9a5f5e7 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultConfigurationFactory.java @@ -0,0 +1,339 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.spi.LoggingSystemProperties; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; +import org.apache.logging.log4j.util.Strings; + +/** + * Default factory for using a plugin selected based on the configuration source. + */ +public class DefaultConfigurationFactory extends ConfigurationFactory { + + private static final String ALL_TYPES = "*"; + private static final String OVERRIDE_PARAM = "override"; + + private final Lazy> configurationFactories; + + @Inject + public DefaultConfigurationFactory(final Injector injector) { + configurationFactories = Lazy.lazy(() -> loadConfigurationFactories(injector)); + } + + /** + * Default Factory Constructor. + * + * @param name The configuration name. + * @param configLocation The configuration location. + * @return The Configuration. + */ + @Override + public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { + + if (configLocation == null) { + final PropertyEnvironment properties = PropertiesUtil.getProperties(); + final String configLocationStr = substitutor.replace(properties.getStringProperty(CONFIGURATION_FILE_PROPERTY)); + if (configLocationStr != null) { + final String[] sources = parseConfigLocations(configLocationStr); + if (sources.length > 1) { + final List configs = new ArrayList<>(); + for (final String sourceLocation : sources) { + final Configuration config = getConfiguration(loggerContext, sourceLocation.trim()); + if (config != null) { + if (config instanceof AbstractConfiguration) { + configs.add((AbstractConfiguration) config); + } else { + LOGGER.error("Failed to created configuration at {}", sourceLocation); + return null; + } + } else { + LOGGER.warn("Unable to create configuration for {}, ignoring", sourceLocation); + } + } + if (configs.size() > 1) { + return new CompositeConfiguration(configs); + } else if (configs.size() == 1) { + return configs.get(0); + } + } + return getConfiguration(loggerContext, configLocationStr); + } else { + final String log4j1ConfigStr = + substitutor.replace(properties.getStringProperty(LOG4J1_CONFIGURATION_FILE_PROPERTY)); + if (log4j1ConfigStr != null) { + System.setProperty(LOG4J1_EXPERIMENTAL, "true"); + return getConfiguration(LOG4J1_VERSION, loggerContext, log4j1ConfigStr); + } + } + for (final ConfigurationFactory factory : configurationFactories.value()) { + final String[] types = factory.getSupportedTypes(); + if (types != null) { + for (final String type : types) { + if (type.equals(ALL_TYPES)) { + final Configuration config = factory.getConfiguration(loggerContext, name, null); + if (config != null) { + return config; + } + } + } + } + } + } else { + // configLocation != null + final String[] sources = parseConfigLocations(configLocation); + if (sources.length > 1) { + final List configs = new ArrayList<>(); + for (final String sourceLocation : sources) { + final Configuration config = getConfiguration(loggerContext, sourceLocation.trim()); + if (config instanceof AbstractConfiguration) { + configs.add((AbstractConfiguration) config); + } else { + LOGGER.error("Failed to created configuration at {}", sourceLocation); + return null; + } + } + return new CompositeConfiguration(configs); + } + final String configLocationStr = configLocation.toString(); + for (final ConfigurationFactory factory : configurationFactories.value()) { + final String[] types = factory.getSupportedTypes(); + if (types != null) { + for (final String type : types) { + if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) { + final Configuration config = factory.getConfiguration(loggerContext, name, configLocation); + if (config != null) { + return config; + } + } + } + } + } + } + + Configuration config = getConfiguration(loggerContext, true, name); + if (config == null) { + config = getConfiguration(loggerContext, true, null); + if (config == null) { + config = getConfiguration(loggerContext, false, name); + if (config == null) { + config = getConfiguration(loggerContext, false, null); + } + } + } + if (config != null) { + return config; + } + LOGGER.warn("No Log4j 2 configuration file found. " + + "Using default configuration (logging only errors to the console), " + + "or user programmatically provided configurations. " + + "Set system property 'log4j2.*.{}' " + + "to show Log4j 2 internal initialization logging. " + + "See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2", + LoggingSystemProperties.SYSTEM_DEBUG); + return new DefaultConfiguration(); + } + + private Configuration getConfiguration(final LoggerContext loggerContext, final String configLocationStr) { + return getConfiguration(null, loggerContext, configLocationStr); + } + + private Configuration getConfiguration( + final String requiredVersion, final LoggerContext loggerContext, + final String configLocationStr) { + ConfigurationSource source = null; + try { + source = ConfigurationSource.fromUri(NetUtils.toURI(configLocationStr)); + } catch (final Exception ex) { + // Ignore the error and try as a String. + LOGGER.catching(Level.DEBUG, ex); + } + if (source != null) { + for (final ConfigurationFactory factory : configurationFactories.value()) { + if (requiredVersion != null && !factory.getVersion().equals(requiredVersion)) { + continue; + } + final String[] types = factory.getSupportedTypes(); + if (types != null) { + for (final String type : types) { + if (type.equals(ALL_TYPES) || configLocationStr.endsWith(type)) { + final Configuration config = factory.getConfiguration(loggerContext, source); + if (config != null) { + return config; + } + } + } + } + } + } + return null; + } + + private Configuration getConfiguration(final LoggerContext loggerContext, final boolean isTest, final String name) { + final boolean named = Strings.isNotEmpty(name); + final ClassLoader loader = LoaderUtil.getThreadContextClassLoader(); + for (final ConfigurationFactory factory : configurationFactories.value()) { + String configName; + final String prefix = isTest ? factory.getTestPrefix() : factory.getDefaultPrefix(); + final String[] types = factory.getSupportedTypes(); + if (types == null) { + continue; + } + + for (final String suffix : types) { + if (suffix.equals(ALL_TYPES)) { + continue; + } + configName = named ? prefix + name + suffix : prefix + suffix; + + final ConfigurationSource source = ConfigurationSource.fromResource(configName, loader); + if (source != null) { + if (!factory.isActive()) { + LOGGER.warn("Found configuration file {} for inactive ConfigurationFactory {}", configName, + factory.getClass().getName()); + } + return factory.getConfiguration(loggerContext, source); + } + } + } + return null; + } + + @Override + public String[] getSupportedTypes() { + return null; + } + + @Override + public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) { + if (source != null) { + final String config = source.getLocation(); + for (final ConfigurationFactory factory : configurationFactories.value()) { + final String[] types = factory.getSupportedTypes(); + if (types != null) { + for (final String type : types) { + if (type.equals(ALL_TYPES) || config != null && config.endsWith(type)) { + final Configuration c = factory.getConfiguration(loggerContext, source); + if (c != null) { + LOGGER.debug("Loaded configuration from {}", source); + return c; + } + LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config); + return null; + } + } + } + } + } + LOGGER.error("Cannot process configuration, input source is null"); + return null; + } + + private String[] parseConfigLocations(final URI configLocations) { + final String[] uris = configLocations.toString().split("\\?"); + final List locations = new ArrayList<>(); + if (uris.length > 1) { + locations.add(uris[0]); + final String[] pairs = configLocations.getQuery().split("&"); + for (final String pair : pairs) { + final int idx = pair.indexOf("="); + final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8) : pair; + if (key.equalsIgnoreCase(OVERRIDE_PARAM)) { + locations.add(URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8)); + } + } + return locations.toArray(new String[0]); + } + return new String[] { uris[0] }; + } + + private String[] parseConfigLocations(final String configLocations) { + final String[] uris = configLocations.split(","); + if (uris.length > 1) { + return uris; + } + try { + return parseConfigLocations(new URI(configLocations)); + } catch (final URISyntaxException ex) { + LOGGER.warn("Error parsing URI {}", configLocations); + } + return new String[] { configLocations }; + } + + private static List loadConfigurationFactories(final Injector injector) { + final List factories = new ArrayList<>(); + + Optional.ofNullable(PropertiesUtil.getProperties().getStringProperty(Log4jProperties.CONFIG_CONFIGURATION_FACTORY_CLASS_NAME)) + .flatMap(DefaultConfigurationFactory::tryLoadFactoryClass) + .map(clazz -> { + try { + return injector.getInstance(clazz); + } catch (final Exception ex) { + LOGGER.error("Unable to create instance of {}", clazz, ex); + return null; + } + }) + .ifPresent(factories::add); + + final List> configurationFactoryPluginClasses = new ArrayList<>(); + injector.getInstance(PLUGIN_CATEGORY_KEY).forEach(type -> { + try { + configurationFactoryPluginClasses.add(type.getPluginClass().asSubclass(ConfigurationFactory.class)); + } catch (final Exception ex) { + LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex); + } + }); + configurationFactoryPluginClasses.sort(OrderComparator.getInstance()); + configurationFactoryPluginClasses.forEach(clazz -> { + try { + factories.add(injector.getInstance(clazz)); + } catch (final Exception ex) { + LOGGER.error("Unable to create instance of {}", clazz, ex); + } + }); + + return factories; + } + + private static Optional> tryLoadFactoryClass(final String factoryClass) { + try { + return Optional.of(Loader.loadClass(factoryClass).asSubclass(ConfigurationFactory.class)); + } catch (final Exception ex) { + LOGGER.error("Unable to load ConfigurationFactory class {}", factoryClass, ex); + return Optional.empty(); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultReliabilityStrategy.java index 18fcae48822..da089143685 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultReliabilityStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/DefaultReliabilityStrategy.java @@ -38,7 +38,7 @@ public DefaultReliabilityStrategy(final LoggerConfig loggerConfig) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level, * org.apache.logging.log4j.message.Message, java.lang.Throwable) @@ -51,7 +51,21 @@ public void log(final Supplier reconfigured, final String loggerNa /* * (non-Javadoc) - * + * + * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, + * java.lang.String, java.lang.String, java.lang.StackTraceElement, org.apache.logging.log4j.Marker, + * org.apache.logging.log4j.Level, org.apache.logging.log4j.message.Message, java.lang.Throwable) + */ + @Override + public void log(final Supplier reconfigured, final String loggerName, final String fqcn, + final StackTraceElement location, final Marker marker, final Level level, final Message data, + final Throwable t) { + loggerConfig.log(loggerName, fqcn, location, marker, level, data, t); + } + + /* + * (non-Javadoc) + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * org.apache.logging.log4j.core.LogEvent) */ @@ -62,7 +76,7 @@ public void log(final Supplier reconfigured, final LogEvent event) /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config. * LoggerConfig, org.apache.logging.log4j.util.Supplier) @@ -74,7 +88,7 @@ public LoggerConfig getActiveLoggerConfig(final Supplier next) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#afterLogEvent() */ @Override @@ -84,7 +98,7 @@ public void afterLogEvent() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -94,7 +108,7 @@ public void beforeStopAppenders() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java new file mode 100644 index 00000000000..7d29dd4f6fe --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory; +import org.apache.logging.log4j.core.util.AbstractWatcher; +import org.apache.logging.log4j.core.util.AuthorizationProvider; +import org.apache.logging.log4j.core.util.Source; +import org.apache.logging.log4j.core.util.Watcher; +import org.apache.logging.log4j.core.util.internal.HttpInputStreamUtil; +import org.apache.logging.log4j.core.util.internal.LastModifiedSource; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * + */ +@Namespace(Watcher.CATEGORY) +@Plugin("http") +@PluginAliases("https") +public class HttpWatcher extends AbstractWatcher { + + private final Logger LOGGER = StatusLogger.getLogger(); + + private final SslConfiguration sslConfiguration; + private AuthorizationProvider authorizationProvider; + private URL url; + private volatile long lastModifiedMillis; + private static final String HTTP = "http"; + private static final String HTTPS = "https"; + + public HttpWatcher(final Configuration configuration, final Reconfigurable reconfigurable, + final List configurationListeners, final long lastModifiedMillis) { + super(configuration, reconfigurable, configurationListeners); + sslConfiguration = SslConfigurationFactory.getSslConfiguration(); + this.lastModifiedMillis = lastModifiedMillis; + } + + @Override + public long getLastModified() { + return lastModifiedMillis; + } + + @Override + public boolean isModified() { + return refreshConfiguration(); + } + + @Override + public void watching(final Source source) { + if (!source.getURI().getScheme().equals(HTTP) && !source.getURI().getScheme().equals(HTTPS)) { + throw new IllegalArgumentException( + "HttpWatcher requires a url using the HTTP or HTTPS protocol, not " + source.getURI().getScheme()); + } + try { + url = source.getURI().toURL(); + authorizationProvider = ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties()); + } catch (final MalformedURLException ex) { + throw new IllegalArgumentException("Invalid URL for HttpWatcher " + source.getURI(), ex); + } + super.watching(source); + } + + @Override + public Watcher newWatcher(final Reconfigurable reconfigurable, final List listeners, + final long lastModifiedMillis) { + final HttpWatcher watcher = new HttpWatcher(getConfiguration(), reconfigurable, listeners, lastModifiedMillis); + if (getSource() != null) { + watcher.watching(getSource()); + } + return watcher; + } + + private boolean refreshConfiguration() { + try { + final LastModifiedSource source = new LastModifiedSource(url.toURI(), lastModifiedMillis); + final HttpInputStreamUtil.Result result = HttpInputStreamUtil.getInputStream(source, authorizationProvider); + switch (result.getStatus()) { + case NOT_MODIFIED: { + LOGGER.debug("Configuration Not Modified"); + return false; + } + case SUCCESS: { + final ConfigurationSource configSource = getConfiguration().getConfigurationSource(); + try { + configSource.setData(HttpInputStreamUtil.readStream(result.getInputStream())); + configSource.setModifiedMillis(source.getLastModified()); + LOGGER.debug("Content was modified for {}", url.toString()); + return true; + } catch (final IOException e) { + LOGGER.error("Error accessing configuration at {}: {}", url, e.getMessage()); + return false; + } + } + case NOT_FOUND: { + LOGGER.info("Unable to locate configuration at {}", url.toString()); + return false; + } + default: { + LOGGER.warn("Unexpected error accessing configuration at {}", url.toString()); + return false; + } + } + } catch(final URISyntaxException ex) { + LOGGER.error("Bad configuration URL: {}, {}", url.toString(), ex.getMessage()); + return false; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LockingReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LockingReliabilityStrategy.java index c930f8b3174..21e355772f1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LockingReliabilityStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LockingReliabilityStrategy.java @@ -33,7 +33,7 @@ public class LockingReliabilityStrategy implements ReliabilityStrategy { private final LoggerConfig loggerConfig; private final ReadWriteLock reconfigureLock = new ReentrantReadWriteLock(); - private volatile boolean isStopping = false; + private volatile boolean isStopping; public LockingReliabilityStrategy(final LoggerConfig loggerConfig) { this.loggerConfig = Objects.requireNonNull(loggerConfig, "loggerConfig was null"); @@ -41,7 +41,7 @@ public LockingReliabilityStrategy(final LoggerConfig loggerConfig) { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level, * org.apache.logging.log4j.message.Message, java.lang.Throwable) @@ -60,7 +60,26 @@ public void log(final Supplier reconfigured, final String loggerNa /* * (non-Javadoc) - * + * + * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, + * java.lang.String, java.lang.String, java.lang.StackTraceElement, org.apache.logging.log4j.Marker, + * org.apache.logging.log4j.Level, org.apache.logging.log4j.message.Message, java.lang.Throwable) + */ + @Override + public void log(final Supplier reconfigured, final String loggerName, final String fqcn, + final StackTraceElement location, final Marker marker, final Level level, final Message data, + final Throwable t) { + final LoggerConfig config = getActiveLoggerConfig(reconfigured); + try { + config.log(loggerName, fqcn, location, marker, level, data, t); + } finally { + config.getReliabilityStrategy().afterLogEvent(); + } + } + + /* + * (non-Javadoc) + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier, * org.apache.logging.log4j.core.LogEvent) */ @@ -76,7 +95,7 @@ public void log(final Supplier reconfigured, final LogEvent event) /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config. * LoggerConfig, org.apache.logging.log4j.util.Supplier) @@ -86,7 +105,7 @@ public LoggerConfig getActiveLoggerConfig(final Supplier next) { LoggerConfig result = this.loggerConfig; if (!beforeLogEvent()) { result = next.get(); - return result.getReliabilityStrategy().getActiveLoggerConfig(next); + return result == this.loggerConfig ? result : result.getReliabilityStrategy().getActiveLoggerConfig(next); } return result; } @@ -107,7 +126,7 @@ public void afterLogEvent() { /* * (non-Javadoc) - * + * * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders() */ @Override @@ -122,7 +141,7 @@ public void beforeStopAppenders() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core * .config.Configuration) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java index 6c4401472ce..aeaf73c00b7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java @@ -18,8 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,16 +25,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Core; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.async.AsyncLoggerConfig; +import org.apache.logging.log4j.core.async.AsyncLoggerContext; import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.config.properties.PropertiesConfiguration; import org.apache.logging.log4j.core.filter.AbstractFilterable; import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; import org.apache.logging.log4j.core.impl.Log4jLogEvent; @@ -44,21 +40,27 @@ import org.apache.logging.log4j.core.impl.ReusableLogEventFactory; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.util.Booleans; -import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.util.LoaderUtil; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.util.PerformanceSensitive; -import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; /** * Logger object that is created via configuration. */ -@Plugin(name = "logger", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin("logger") public class LoggerConfig extends AbstractFilterable { public static final String ROOT = "root"; - private static LogEventFactory LOG_EVENT_FACTORY = null; private List appenderRefs = new ArrayList<>(); private final AppenderControlArraySet appenders = new AppenderControlArraySet(); @@ -74,22 +76,136 @@ public class LoggerConfig extends AbstractFilterable { private final Configuration config; private final ReliabilityStrategy reliabilityStrategy; - static { - final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY); - if (factory != null) { - try { - final Class clazz = LoaderUtil.loadClass(factory); - if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) { - LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance(); - } - } catch (final Exception ex) { - LOGGER.error("Unable to create LogEventFactory {}", factory, ex); - } + @PluginFactory + public static > B newBuilder() { + return new Builder().asBuilder(); + } + + /** + * Builds LoggerConfig instances. + * + * @param + * The type to build + */ + public static class Builder> + implements org.apache.logging.log4j.core.util.Builder { + + @PluginBuilderAttribute + private Boolean additivity; + private Level level; + private String levelAndRefs; + private String loggerName; + private String includeLocation; + private AppenderRef[] refs; + private Property[] properties; + private Configuration config; + private Filter filter; + private LogEventFactory logEventFactory; + + public boolean isAdditivity() { + return additivity == null || additivity; + } + + public B withAdditivity(boolean additivity) { + this.additivity = additivity; + return asBuilder(); + } + + public Level getLevel() { + return level; + } + + public B withLevel(@PluginAttribute Level level) { + this.level = level; + return asBuilder(); + } + + public String getLevelAndRefs() { + return levelAndRefs; + } + + public B withLevelAndRefs(@PluginAttribute String levelAndRefs) { + this.levelAndRefs = levelAndRefs; + return asBuilder(); + } + + public String getLoggerName() { + return loggerName; + } + + public B withLoggerName( + @Required(message = "Loggers cannot be configured without a name") @PluginAttribute String name) { + this.loggerName = name; + return asBuilder(); + } + + public String getIncludeLocation() { + return includeLocation; + } + + public B withIncludeLocation(@PluginAttribute String includeLocation) { + this.includeLocation = includeLocation; + return asBuilder(); + } + + public AppenderRef[] getRefs() { + return refs; } - if (LOG_EVENT_FACTORY == null) { - LOG_EVENT_FACTORY = Constants.ENABLE_THREADLOCALS - ? new ReusableLogEventFactory() - : new DefaultLogEventFactory(); + + public B withRefs(@PluginElement AppenderRef[] refs) { + this.refs = refs; + return asBuilder(); + } + + public Property[] getProperties() { + return properties; + } + + public B withProperties(@PluginElement Property[] properties) { + this.properties = properties; + return asBuilder(); + } + + public Configuration getConfig() { + return config; + } + + public B withConfig(@PluginConfiguration Configuration config) { + this.config = config; + return asBuilder(); + } + + public Filter getFilter() { + return filter; + } + + public B withFilter(@PluginElement Filter filter) { + this.filter = filter; + return asBuilder(); + } + + public LogEventFactory getLogEventFactory() { + return logEventFactory; + } + + @Inject + public B setLogEventFactory(LogEventFactory logEventFactory) { + this.logEventFactory = logEventFactory; + return asBuilder(); + } + + @Override + public LoggerConfig build() { + final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; + LevelAndRefs container = LoggerConfig.getLevelAndRefs(level, refs, levelAndRefs, config); + boolean useLocation = includeLocation(includeLocation, config); + return new LoggerConfig(name, container.refs, filter, container.level, isAdditivity(), properties, config, + useLocation, logEventFactory); + } + + @SuppressWarnings("unchecked") + public B asBuilder() { + return (B) this; } } @@ -97,7 +213,7 @@ public class LoggerConfig extends AbstractFilterable { * Default constructor. */ public LoggerConfig() { - this.logEventFactory = LOG_EVENT_FACTORY; + this.logEventFactory = DefaultLogEventFactory.newInstance(); this.level = Level.ERROR; this.name = Strings.EMPTY; this.properties = null; @@ -114,7 +230,7 @@ public LoggerConfig() { * @param additive true if the Logger is additive, false otherwise. */ public LoggerConfig(final String name, final Level level, final boolean additive) { - this.logEventFactory = LOG_EVENT_FACTORY; + this.logEventFactory = DefaultLogEventFactory.newInstance(); this.name = name; this.level = level; this.additive = additive; @@ -124,11 +240,12 @@ public LoggerConfig(final String name, final Level level, final boolean additive this.reliabilityStrategy = new DefaultReliabilityStrategy(this); } - protected LoggerConfig(final String name, final List appenders, final Filter filter, + protected LoggerConfig( + final String name, final List appenders, final Filter filter, final Level level, final boolean additive, final Property[] properties, final Configuration config, - final boolean includeLocation) { - super(filter); - this.logEventFactory = LOG_EVENT_FACTORY; + final boolean includeLocation, final LogEventFactory logEventFactory) { + super(filter, null); + this.logEventFactory = logEventFactory; this.name = name; this.appenderRefs = appenders; this.level = level; @@ -136,8 +253,7 @@ protected LoggerConfig(final String name, final List appenders, fin this.includeLocation = includeLocation; this.config = config; if (properties != null && properties.length > 0) { - this.properties = Collections.unmodifiableList(Arrays.asList(Arrays.copyOf( - properties, properties.length))); + this.properties = List.of(properties.clone()); } else { this.properties = null; } @@ -265,7 +381,15 @@ public void setLevel(final Level level) { * @return the logging Level. */ public Level getLevel() { - return level == null ? parent.getLevel() : level; + return level == null ? parent == null ? Level.ERROR : parent.getLevel() : level; + } + + /** + * Allows callers to determine the Level assigned to this LoggerConfig. + * @return the Level associated with this LoggerConfig or null if none is set. + */ + public Level getExplicitLevel() { + return level; } /** @@ -314,34 +438,6 @@ public boolean isIncludeLocation() { return includeLocation; } - /** - * Returns an unmodifiable map with the configuration properties, or {@code null} if this {@code LoggerConfig} does - * not have any configuration properties. - *

    - * For each {@code Property} key in the map, the value is {@code true} if the property value has a variable that - * needs to be substituted. - * - * @return an unmodifiable map with the configuration properties, or {@code null} - * @see Configuration#getStrSubstitutor() - * @see StrSubstitutor - * @deprecated use {@link #getPropertyList()} instead - */ - // LOG4J2-157 - @Deprecated - public Map getProperties() { - if (properties == null) { - return null; - } - if (propertiesMap == null) { // lazily initialize: only used by user custom code, not by Log4j any more - final Map result = new HashMap<>(properties.size() * 2); - for (int i = 0; i < properties.size(); i++) { - result.put(properties.get(i), Boolean.valueOf(properties.get(i).isValueNeedsLookup())); - } - propertiesMap = Collections.unmodifiableMap(result); - } - return propertiesMap; - } - /** * Returns an unmodifiable list with the configuration properties, or {@code null} if this {@code LoggerConfig} does * not have any configuration properties. @@ -375,46 +471,106 @@ public boolean isPropertiesRequireLookup() { @PerformanceSensitive("allocation") public void log(final String loggerName, final String fqcn, final Marker marker, final Level level, final Message data, final Throwable t) { - List props = null; - if (!propertiesRequireLookup) { - props = properties; - } else { - if (properties != null) { - props = new ArrayList<>(properties.size()); - final LogEvent event = Log4jLogEvent.newBuilder() - .setMessage(data) - .setMarker(marker) - .setLevel(level) - .setLoggerName(loggerName) - .setLoggerFqcn(fqcn) - .setThrown(t) - .build(); - for (int i = 0; i < properties.size(); i++) { - final Property prop = properties.get(i); - final String value = prop.isValueNeedsLookup() // since LOG4J2-1575 - ? config.getStrSubstitutor().replace(event, prop.getValue()) // - : prop.getValue(); - props.add(Property.createProperty(prop.getName(), value)); - } - } + final List props = getProperties(loggerName, fqcn, marker, level, data, t); + final LogEvent logEvent = logEventFactory.createEvent( + loggerName, marker, fqcn, location(fqcn), level, data, props, t); + try { + log(logEvent, LoggerConfigPredicate.ALL); + } finally { + // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) + ReusableLogEventFactory.release(logEvent); } - final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t); + } + + private StackTraceElement location(String fqcn) { + return requiresLocation() ? + StackLocatorUtil.calcLocation(fqcn) : null; + } + + /** + * Logs an event. + * + * @param loggerName The name of the Logger. + * @param fqcn The fully qualified class name of the caller. + * @param location the location of the caller. + * @param marker A Marker or null if none is present. + * @param level The event Level. + * @param data The Message. + * @param t A Throwable or null. + */ + @PerformanceSensitive("allocation") + public void log(final String loggerName, final String fqcn, final StackTraceElement location, final Marker marker, + final Level level, final Message data, final Throwable t) { + final List props = getProperties(loggerName, fqcn, marker, level, data, t); + final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, location, level, data, props, t); try { - log(logEvent); + log(logEvent, LoggerConfigPredicate.ALL); } finally { // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) ReusableLogEventFactory.release(logEvent); } } + private List getProperties( + final String loggerName, + final String fqcn, + final Marker marker, + final Level level, + final Message data, + final Throwable t) { + List snapshot = properties; + if (snapshot == null || !propertiesRequireLookup) { + return snapshot; + } + return getPropertiesWithLookups(loggerName, fqcn, marker, level, data, t, snapshot); + } + + private List getPropertiesWithLookups( + final String loggerName, + final String fqcn, + final Marker marker, + final Level level, + final Message data, + final Throwable t, + final List props) { + List results = new ArrayList<>(props.size()); + final LogEvent event = Log4jLogEvent.newBuilder() + .setMessage(data) + .setMarker(marker) + .setLevel(level) + .setLoggerName(loggerName) + .setLoggerFqcn(fqcn) + .setThrown(t) + .build(); + for (int i = 0; i < props.size(); i++) { + final Property prop = props.get(i); + final String value = prop.isValueNeedsLookup() // since LOG4J2-1575 + ? config.getStrSubstitutor().replace(event, prop.getValue()) // + : prop.getValue(); + results.add(Property.createProperty(prop.getName(), value)); + } + return results; + } + /** * Logs an event. * * @param event The log event. */ public void log(final LogEvent event) { + log(event, LoggerConfigPredicate.ALL); + } + + /** + * Logs an event. + * + * @param event The log event. + * @param predicate predicate for which LoggerConfig instances to append to. + * A null value is equivalent to a true predicate. + */ + protected void log(final LogEvent event, final LoggerConfigPredicate predicate) { if (!isFiltered(event)) { - processLogEvent(event); + processLogEvent(event, predicate); } } @@ -428,15 +584,41 @@ public ReliabilityStrategy getReliabilityStrategy() { return reliabilityStrategy; } - private void processLogEvent(final LogEvent event) { + private void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) { event.setIncludeLocation(isIncludeLocation()); - callAppenders(event); - logParent(event); + if (predicate.allow(this)) { + callAppenders(event); + } + logParent(event, predicate); } - private void logParent(final LogEvent event) { + public boolean requiresLocation() { + if (!includeLocation) { + return false; + } + AppenderControl[] controls = appenders.get(); + LoggerConfig loggerConfig = this; + while (loggerConfig != null) { + for (final AppenderControl control : controls) { + if (control.getAppender().requiresLocation()) { + return true; + } + } + if (loggerConfig.additive) { + loggerConfig = loggerConfig.parent; + if (loggerConfig != null) { + controls = loggerConfig.appenders.get(); + } + } else { + break; + } + } + return false; + } + + private void logParent(final LogEvent event, final LoggerConfigPredicate predicate) { if (additive && parent != null) { - parent.log(event); + parent.log(event, predicate); } } @@ -454,44 +636,6 @@ public String toString() { return Strings.isEmpty(name) ? ROOT : name; } - /** - * Factory method to create a LoggerConfig. - * - * @param additivity True if additive, false otherwise. - * @param level The Level to be associated with the Logger. - * @param loggerName The name of the Logger. - * @param includeLocation whether location should be passed downstream - * @param refs An array of Appender names. - * @param properties Properties to pass to the Logger. - * @param config The Configuration. - * @param filter A Filter. - * @return A new LoggerConfig. - * @deprecated Deprecated in 2.7; use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)} - */ - @Deprecated - public static LoggerConfig createLogger(final String additivity, - // @formatter:off - final Level level, - @PluginAttribute("name") final String loggerName, - final String includeLocation, - final AppenderRef[] refs, - final Property[] properties, - @PluginConfiguration final Configuration config, - final Filter filter) { - // @formatter:on - if (loggerName == null) { - LOGGER.error("Loggers cannot be configured without a name"); - return null; - } - - final List appenderRefs = Arrays.asList(refs); - final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; - final boolean additive = Booleans.parseBoolean(additivity, true); - - return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config, - includeLocation(includeLocation)); - } - /** * Factory method to create a LoggerConfig. * @@ -506,58 +650,233 @@ public static LoggerConfig createLogger(final String additivity, * @return A new LoggerConfig. * @since 2.6 */ - @PluginFactory + @Deprecated public static LoggerConfig createLogger( - // @formatter:off - @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity, - @PluginAttribute("level") final Level level, - @Required(message = "Loggers cannot be configured without a name") @PluginAttribute("name") final String loggerName, - @PluginAttribute("includeLocation") final String includeLocation, - @PluginElement("AppenderRef") final AppenderRef[] refs, - @PluginElement("Properties") final Property[] properties, - @PluginConfiguration final Configuration config, - @PluginElement("Filter") final Filter filter - // @formatter:on - ) { + final boolean additivity, final Level level, final String loggerName, final String includeLocation, + final AppenderRef[] refs, final Property[] properties, final Configuration config, final Filter filter) { final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, - includeLocation(includeLocation)); + includeLocation(includeLocation, config), config.getComponent(LogEventFactory.KEY)); } // Note: for asynchronous loggers, includeLocation default is FALSE, // for synchronous loggers, includeLocation default is TRUE. - protected static boolean includeLocation(final String includeLocationConfigValue) { + protected static boolean includeLocation(final String includeLocationConfigValue, final Configuration configuration) { if (includeLocationConfigValue == null) { - final boolean sync = !AsyncLoggerContextSelector.isSelected(); - return sync; + LoggerContext context = null; + if (configuration != null) { + context = configuration.getLoggerContext(); + } + if (context != null) { + return !(context instanceof AsyncLoggerContext); + } else { + return !AsyncLoggerContextSelector.isSelected(); + } } return Boolean.parseBoolean(includeLocationConfigValue); } + protected final boolean hasAppenders() { + return !appenders.isEmpty(); + } + /** * The root Logger. */ - @Plugin(name = ROOT, category = Core.CATEGORY_NAME, printObject = true) + @Configurable(printObject = true) + @Plugin(ROOT) public static class RootLogger extends LoggerConfig { @PluginFactory + public static > B newRootBuilder() { + return new Builder().asBuilder(); + } + + /** + * Builds LoggerConfig instances. + * + * @param + * The type to build + */ + public static class Builder> + implements org.apache.logging.log4j.core.util.Builder { + + private boolean additivity; + private Level level; + private String levelAndRefs; + private String includeLocation; + private AppenderRef[] refs; + private Property[] properties; + private Configuration config; + private Filter filter; + private LogEventFactory logEventFactory; + + public boolean isAdditivity() { + return additivity; + } + + public B withAdditivity(@PluginAttribute boolean additivity) { + this.additivity = additivity; + return asBuilder(); + } + + public Level getLevel() { + return level; + } + + public B withLevel(@PluginAttribute Level level) { + this.level = level; + return asBuilder(); + } + + public String getLevelAndRefs() { + return levelAndRefs; + } + + public B withLevelAndRefs(@PluginAttribute String levelAndRefs) { + this.levelAndRefs = levelAndRefs; + return asBuilder(); + } + + public String getIncludeLocation() { + return includeLocation; + } + + public B withIncludeLocation(@PluginAttribute String includeLocation) { + this.includeLocation = includeLocation; + return asBuilder(); + } + + public AppenderRef[] getRefs() { + return refs; + } + + public B withRefs(@PluginElement AppenderRef[] refs) { + this.refs = refs; + return asBuilder(); + } + + public Property[] getProperties() { + return properties; + } + + public B withProperties(@PluginElement Property[] properties) { + this.properties = properties; + return asBuilder(); + } + + public Configuration getConfig() { + return config; + } + + public B withConfig(@PluginConfiguration Configuration config) { + this.config = config; + return asBuilder(); + } + + public Filter getFilter() { + return filter; + } + + public B withFilter(@PluginElement Filter filter) { + this.filter = filter; + return asBuilder(); + } + + public LogEventFactory getLogEventFactory() { + return logEventFactory; + } + + @Inject + public B withLogEventFactory(final LogEventFactory logEventFactory) { + this.logEventFactory = logEventFactory; + return asBuilder(); + } + + @Override + public LoggerConfig build() { + LevelAndRefs container = LoggerConfig.getLevelAndRefs(level, refs, levelAndRefs, config); + return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, container.refs, filter, container.level, + additivity, properties, config, includeLocation(includeLocation, config), logEventFactory); + } + + @SuppressWarnings("unchecked") + public B asBuilder() { + return (B) this; + } + } + + + @Deprecated public static LoggerConfig createLogger( - // @formatter:off - @PluginAttribute("additivity") final String additivity, - @PluginAttribute("level") final Level level, - @PluginAttribute("includeLocation") final String includeLocation, - @PluginElement("AppenderRef") final AppenderRef[] refs, - @PluginElement("Properties") final Property[] properties, - @PluginConfiguration final Configuration config, - @PluginElement("Filter") final Filter filter) { - // @formatter:on + final String additivity, final Level level, final String includeLocation, final AppenderRef[] refs, + final Property[] properties, final Configuration config, final Filter filter) { final List appenderRefs = Arrays.asList(refs); final Level actualLevel = level == null ? Level.ERROR : level; final boolean additive = Booleans.parseBoolean(additivity, true); return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, - properties, config, includeLocation(includeLocation)); + properties, config, includeLocation(includeLocation, config), + config.getComponent(LogEventFactory.KEY)); } } + protected static LevelAndRefs getLevelAndRefs(Level level, AppenderRef[] refs, String levelAndRefs, + Configuration config) { + LevelAndRefs result = new LevelAndRefs(); + if (levelAndRefs != null) { + if (config instanceof PropertiesConfiguration) { + if (level != null) { + LOGGER.warn("Level is ignored when levelAndRefs syntax is used."); + } + if (refs != null && refs.length > 0) { + LOGGER.warn("Appender references are ignored when levelAndRefs syntax is used"); + } + final String[] parts = Strings.splitList(levelAndRefs); + result.level = Level.getLevel(parts[0]); + if (parts.length > 1) { + List refList = new ArrayList<>(); + Arrays.stream(parts).skip(1).forEach((ref) -> + refList.add(AppenderRef.createAppenderRef(ref, null, null))); + result.refs = refList; + } + } else { + LOGGER.warn("levelAndRefs are only allowed in a properties configuration. The value is ignored."); + result.level = level; + result.refs = refs != null ? Arrays.asList(refs) : new ArrayList<>(); + } + } else { + result.level = level; + result.refs = refs != null ? Arrays.asList(refs) : new ArrayList<>(); + } + return result; + } + + protected static class LevelAndRefs { + public Level level; + public List refs; + } + + protected enum LoggerConfigPredicate { + ALL() { + @Override + boolean allow(final LoggerConfig config) { + return true; + } + }, + ASYNCHRONOUS_ONLY() { + @Override + boolean allow(final LoggerConfig config) { + return config instanceof AsyncLoggerConfig; + } + }, + SYNCHRONOUS_ONLY() { + @Override + boolean allow(final LoggerConfig config) { + return !ASYNCHRONOUS_ONLY.allow(config); + } + }; + + abstract boolean allow(LoggerConfig config); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerContextAware.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerContextAware.java new file mode 100644 index 00000000000..e68af4c2773 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerContextAware.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config; + +import org.apache.logging.log4j.core.LoggerContext; + +/** + * Indicates that a class requests the current LoggerContext to be injected. + * + * @since 2.19.0 + */ +public interface LoggerContextAware { + + /** + * Injects the current LoggerContext into this object. + * + * @param loggerContext the current LoggerContext + */ + void setLoggerContext(LoggerContext loggerContext); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggersPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggersPlugin.java index a9fb94c40fa..6f6ec067307 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggersPlugin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggersPlugin.java @@ -16,17 +16,19 @@ */ package org.apache.logging.log4j.core.config; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Container of Logger objects. */ -@Plugin(name = "loggers", category = Node.CATEGORY) +@Configurable +@Plugin("loggers") public final class LoggersPlugin { private LoggersPlugin() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Node.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Node.java deleted file mode 100644 index c92c9048ab7..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Node.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.logging.log4j.core.config.plugins.util.PluginType; - -/** - * A Configuration node. - */ -public class Node { - - /** - * Main plugin category for plugins which are represented as a configuration node. Such plugins tend to be - * available as XML elements in a configuration file. - * - * @since 2.1 - */ - public static final String CATEGORY = "Core"; - - private final Node parent; - private final String name; - private String value; - private final PluginType type; - private final Map attributes = new HashMap<>(); - private final List children = new ArrayList<>(); - private Object object; - - - /** - * Creates a new instance of {@code Node} and initializes it - * with a name and the corresponding XML element. - * - * @param parent the node's parent. - * @param name the node's name. - * @param type The Plugin Type associated with the node. - */ - public Node(final Node parent, final String name, final PluginType type) { - this.parent = parent; - this.name = name; - this.type = type; - } - - public Node() { - this.parent = null; - this.name = null; - this.type = null; - } - - public Node(final Node node) { - this.parent = node.parent; - this.name = node.name; - this.type = node.type; - this.attributes.putAll(node.getAttributes()); - this.value = node.getValue(); - for (final Node child : node.getChildren()) { - this.children.add(new Node(child)); - } - this.object = node.object; - } - - public Map getAttributes() { - return attributes; - } - - public List getChildren() { - return children; - } - - public boolean hasChildren() { - return !children.isEmpty(); - } - - public String getValue() { - return value; - } - - public void setValue(final String value) { - this.value = value; - } - - public Node getParent() { - return parent; - } - - public String getName() { - return name; - } - - public boolean isRoot() { - return parent == null; - } - - public void setObject(final Object obj) { - object = obj; - } - - @SuppressWarnings("unchecked") - public T getObject() { - return (T) object; - } - - /** - * Returns this node's object cast to the given class. - * - * @param clazz the class to cast this node's object to. - * @param the type to cast to. - * @return this node's object. - * @since 2.1 - */ - public T getObject(final Class clazz) { - return clazz.cast(object); - } - - /** - * Determines if this node's object is an instance of the given class. - * - * @param clazz the class to check. - * @return {@code true} if this node's object is an instance of the given class. - * @since 2.1 - */ - public boolean isInstanceOf(final Class clazz) { - return clazz.isInstance(object); - } - - public PluginType getType() { - return type; - } - - @Override - public String toString() { - if (object == null) { - return "null"; - } - return type.isObjectPrintable() ? object.toString() : - type.getPluginClass().getName() + " with name " + name; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java index f0ed3588970..72e78b25eef 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java @@ -16,21 +16,24 @@ */ package org.apache.logging.log4j.core.config; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.lookup.Interpolator; -import org.apache.logging.log4j.core.lookup.MapLookup; +import org.apache.logging.log4j.core.lookup.InterpolatorFactory; +import org.apache.logging.log4j.core.lookup.PropertiesLookup; import org.apache.logging.log4j.core.lookup.StrLookup; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.di.Key; + +import java.util.HashMap; +import java.util.Map; /** * Handles properties defined in the configuration. */ -@Plugin(name = "properties", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin("properties") public final class PropertiesPlugin { private PropertiesPlugin() { @@ -45,15 +48,16 @@ private PropertiesPlugin() { @PluginFactory public static StrLookup configureSubstitutor(@PluginElement("Properties") final Property[] properties, @PluginConfiguration final Configuration config) { + final Map map; if (properties == null) { - return new Interpolator(config.getProperties()); - } - final Map map = new HashMap<>(config.getProperties()); + map = config.getProperties(); + } else { + map = new HashMap<>(config.getProperties()); - for (final Property prop : properties) { - map.put(prop.getName(), prop.getValue()); + for (final Property prop : properties) { + map.put(prop.getName(), prop.getValue()); + } } - - return new Interpolator(new MapLookup(map), config.getPluginPackages()); + return config.getComponent(Key.forClass(InterpolatorFactory.class)).newInterpolator(new PropertiesLookup(map)); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java index 09b07a5cd76..b9910871dae 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Property.java @@ -16,23 +16,27 @@ */ package org.apache.logging.log4j.core.config; -import java.util.Objects; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.PluginValue; -import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.PluginValue; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.util.Strings; +import java.util.Objects; + /** * Represents a key/value pair in the configuration. */ -@Plugin(name = "property", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin public final class Property { - private static final Logger LOGGER = StatusLogger.getLogger(); + /** + * @since 2.11.2 + */ + public static final Property[] EMPTY_ARRAY = new Property[0]; private final String name; private final String value; @@ -77,11 +81,8 @@ public boolean isValueNeedsLookup() { */ @PluginFactory public static Property createProperty( - @PluginAttribute("name") final String name, - @PluginValue("value") final String value) { - if (name == null) { - LOGGER.error("Property name cannot be null"); - } + @PluginAttribute @Required(message = "Property name cannot be null") final String name, + @PluginValue final String value) { return new Property(name, value); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java index f21a92d57ab..1ccb74a6b11 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java @@ -42,6 +42,22 @@ public interface ReliabilityStrategy { */ void log(Supplier reconfigured, String loggerName, String fqcn, Marker marker, Level level, Message data, Throwable t); + /** + * Logs an event. + * + * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active + * @param loggerName The name of the Logger. + * @param fqcn The fully qualified class name of the caller. + * @param location The location of the caller or null. + * @param marker A Marker or null if none is present. + * @param level The event Level. + * @param data The Message. + * @param t A Throwable or null. + * @since 3.0 + */ + default void log(final Supplier reconfigured, final String loggerName, final String fqcn, final StackTraceElement location, + final Marker marker, final Level level, final Message data, final Throwable t) { + } /** * Logs an event. @@ -53,7 +69,7 @@ void log(Supplier reconfigured, String loggerName, String fqcn, Ma /** * For internal use by the ReliabilityStrategy; returns the LoggerConfig to use. - * + * * @param next supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active * @return the currently active LoggerConfig */ @@ -71,7 +87,7 @@ void log(Supplier reconfigured, String loggerName, String fqcn, Ma /** * Called before the configuration is stopped. - * + * * @param configuration the configuration that will be stopped */ void beforeStopConfiguration(Configuration configuration); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategyFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategyFactory.java index 1e9fc05797d..957f107e2cc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategyFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategyFactory.java @@ -14,11 +14,11 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.config; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; /** @@ -39,14 +39,14 @@ private ReliabilityStrategyFactory() { *

    * Users may also use this system property to specify the fully qualified class name of a class that implements the * {@code ReliabilityStrategy} and has a constructor that accepts a single {@code LoggerConfig} argument. - * + * * @param loggerConfig the LoggerConfig the resulting {@code ReliabilityStrategy} is associated with * @return a ReliabilityStrategy that helps the specified LoggerConfig to log events reliably during or after a * configuration change */ public static ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfig) { - final String strategy = PropertiesUtil.getProperties().getStringProperty("log4j.ReliabilityStrategy", + final String strategy = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.CONFIG_RELIABILITY_STRATEGY, "AwaitCompletion"); if ("AwaitCompletion".equals(strategy)) { return new AwaitCompletionReliabilityStrategy(loggerConfig); @@ -58,7 +58,7 @@ public static ReliabilityStrategy getReliabilityStrategy(final LoggerConfig logg return new LockingReliabilityStrategy(loggerConfig); } try { - final Class cls = LoaderUtil.loadClass(strategy).asSubclass( + final Class cls = Loader.loadClass(strategy).asSubclass( ReliabilityStrategy.class); return cls.getConstructor(LoggerConfig.class).newInstance(loggerConfig); } catch (final Exception dynamicFailed) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/Arbiter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/Arbiter.java new file mode 100644 index 00000000000..ceb24ea62ab --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/Arbiter.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.arbiters; + +/** + * Interface used to check for portions of the configuration that may be optionally included. + */ +public interface Arbiter { + + String ELEMENT_TYPE = "Arbiter"; + + boolean isCondition(); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/ClassArbiter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/ClassArbiter.java new file mode 100644 index 00000000000..8033b6eb430 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/ClassArbiter.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.arbiters; + +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.util.LoaderUtil; + +/** + * Conditional that determines if the specified class is present. + */ +@Configurable(elementType = Arbiter.ELEMENT_TYPE, printObject = true, deferChildren = true) +@Plugin +public class ClassArbiter implements Arbiter { + + private final String className; + + private ClassArbiter(final String className) { + this.className = className; + } + + @Override + public boolean isCondition() { + return LoaderUtil.isClassAvailable(className); + } + + @PluginBuilderFactory + public static ClassArbiter.Builder newBuilder() { + return new ClassArbiter.Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + public static final String ATTR_CLASS_NAME = "className"; + + @PluginBuilderAttribute(ATTR_CLASS_NAME) + private String className; + + + /** + * Sets the Class name. + * @param className the class name. + * @return this + */ + public Builder setClassName(final String className) { + this.className = className; + return asBuilder(); + } + + public Builder asBuilder() { + return this; + } + + public ClassArbiter build() { + return new ClassArbiter(className); + } + + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/DefaultArbiter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/DefaultArbiter.java new file mode 100644 index 00000000000..ae9e948eb36 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/DefaultArbiter.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.arbiters; + +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; + +/** + * Default Condition for a Select Condition. + */ +@Configurable(elementType = Arbiter.ELEMENT_TYPE, printObject = true, deferChildren = true) +@Plugin +public class DefaultArbiter implements Arbiter { + + /** + * Always returns true since it is the default. + */ + @Override + public boolean isCondition() { + return true; + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + public Builder asBuilder() { + return this; + } + + public DefaultArbiter build() { + return new DefaultArbiter(); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SelectArbiter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SelectArbiter.java new file mode 100644 index 00000000000..bd088e98f03 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SelectArbiter.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.arbiters; + +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; + +import java.util.List; +import java.util.Optional; + +/** + * Class Description goes here. + */ +@Configurable(elementType = Arbiter.ELEMENT_TYPE, printObject = true, deferChildren = true) +@Plugin("Select") +public class SelectArbiter { + + public Arbiter evaluateConditions(final List conditions) { + final Optional opt = conditions.stream().filter((c) -> c instanceof DefaultArbiter) + .reduce((a, b) -> { + throw new IllegalStateException("Multiple elements: " + a + ", " + b); + }); + for (final Arbiter condition : conditions) { + if (condition instanceof DefaultArbiter) { + continue; + } + if (condition.isCondition()) { + return condition; + } + } + return opt.orElse(null); + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + public Builder asBuilder() { + return this; + } + + public SelectArbiter build() { + return new SelectArbiter(); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SystemPropertyArbiter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SystemPropertyArbiter.java new file mode 100644 index 00000000000..f72207a2d9c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/arbiters/SystemPropertyArbiter.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.arbiters; + +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; + +/** + * Condition that determines if the specified property is set. + */ +@Configurable(elementType = Arbiter.ELEMENT_TYPE, printObject = true, deferChildren = true) +@Plugin +public class SystemPropertyArbiter implements Arbiter { + + private final String propertyName; + private final String propertyValue; + + private SystemPropertyArbiter(final String propertyName, final String propertyValue) { + this.propertyName = propertyName; + this.propertyValue = propertyValue; + } + + + /** + * Returns true if either the property name is defined (it has any value) or the property value + * matches the requested value. + */ + @Override + public boolean isCondition() { + final String value = System.getProperty(propertyName); + return value != null && (propertyValue == null || value.equals(propertyValue)); + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + public static final String ATTR_PROPERTY_NAME = "propertyName"; + public static final String ATTR_PROPERTY_VALUE = "propertyValue"; + + @PluginBuilderAttribute(ATTR_PROPERTY_NAME) + private String propertyName; + + @PluginBuilderAttribute(ATTR_PROPERTY_VALUE) + private String propertyValue; + /** + * Sets the Property Name. + * @param propertyName the property name. + * @return this + */ + public Builder setPropertyName(final String propertyName) { + this.propertyName = propertyName; + return asBuilder(); + } + + /** + * Sets the Property Value. + * @param propertyValue the property value. + * @return this + */ + public Builder setPropertyValue(final String propertyValue) { + this.propertyValue = propertyValue; + return asBuilder(); + } + + public Builder asBuilder() { + return this; + } + + public SystemPropertyArbiter build() { + return new SystemPropertyArbiter(propertyName, propertyValue); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ComponentBuilder.java index 5ad7babcb20..55090a23c14 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ComponentBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ComponentBuilder.java @@ -18,7 +18,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.util.Builder; +import org.apache.logging.log4j.plugins.util.Builder; /** * Builds arbitrary components and is the base type for the provided components. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java index 1cbf6cc6e75..60d21442f5a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java @@ -25,7 +25,7 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.util.Builder; +import org.apache.logging.log4j.plugins.util.Builder; /** * Interface for building logging configurations. @@ -94,7 +94,7 @@ public interface ConfigurationBuilder extends Builder extends Builder extends Builder 0) { - final ConfigurationSource configSource = getConfigurationSource(); - if (configSource != null) { - final File configFile = configSource.getFile(); - if (intervalSeconds > 0) { - getWatchManager().setIntervalSeconds(intervalSeconds); - if (configFile != null) { - final FileWatcher watcher = new ConfiguratonFileWatcher((Reconfigurable) this, listeners); - getWatchManager().watchFile(configFile, watcher); - } - } - } + initializeWatchers((Reconfigurable) this, getConfigurationSource(), intervalSeconds); } } - @Override - public PluginManager getPluginManager() { - return pluginManager; - } - protected Node convertToNode(final Node parent, final Component component) { final String name = component.getPluginType(); - final PluginType pluginType = pluginManager.getPluginType(name); + final PluginType pluginType = corePlugins.get(name); final Node node = new Node(parent, name, pluginType); node.getAttributes().putAll(component.getAttributes()); node.setValue(component.getValue()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java index 27d8710427d..3cfc608249e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.util.List; @@ -26,6 +27,16 @@ import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; @@ -57,15 +68,14 @@ public class DefaultConfigurationBuilder implements ConfigurationBuilder { private static final String INDENT = " "; - private static final String EOL = System.lineSeparator(); - + private final Component root = new Component(); - private Component loggers; - private Component appenders; - private Component filters; - private Component properties; - private Component customLevels; - private Component scripts; + private final Component loggers; + private final Component appenders; + private final Component filters; + private final Component properties; + private final Component customLevels; + private final Component scripts; private final Class clazz; private ConfigurationSource source; private int monitorInterval; @@ -79,6 +89,14 @@ public class DefaultConfigurationBuilder implement private LoggerContext loggerContext; private String name; + public static void formatXml(final Source source, final Result result) + throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException { + final Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(INDENT.length())); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.transform(source, result); + } + @SuppressWarnings("unchecked") public DefaultConfigurationBuilder() { this((Class) BuiltConfiguration.class); @@ -163,29 +181,25 @@ public T build() { @Override public T build(final boolean initialize) { - T configuration; + final T configuration; try { if (source == null) { source = ConfigurationSource.NULL_SOURCE; } final Constructor constructor = clazz.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class); configuration = constructor.newInstance(loggerContext, source, root); - configuration.setMonitorInterval(monitorInterval); configuration.getRootNode().getAttributes().putAll(root.getAttributes()); if (name != null) { configuration.setName(name); } if (level != null) { - configuration.getStatusConfiguration().withStatus(level); + configuration.getStatusConfiguration().setStatus(level); } if (verbosity != null) { - configuration.getStatusConfiguration().withVerbosity(verbosity); + configuration.getStatusConfiguration().setVerbosity(verbosity); } if (destination != null) { - configuration.getStatusConfiguration().withDestination(destination); - } - if (packages != null) { - configuration.setPluginPackages(packages); + configuration.getStatusConfiguration().setDestination(destination); } if (shutdownFlag != null) { configuration.setShutdownHook(shutdownFlag); @@ -196,6 +210,7 @@ public T build(final boolean initialize) { if (advertiser != null) { configuration.createAdvertiser(advertiser, source); } + configuration.setMonitorInterval(monitorInterval); } catch (final Exception ex) { throw new IllegalArgumentException("Invalid Configuration class specified", ex); } @@ -214,7 +229,7 @@ public void writeXmlConfiguration(final OutputStream output) throws IOException xmlWriter.close(); } catch (final XMLStreamException e) { if (e.getNestedException() instanceof IOException) { - throw (IOException)e.getNestedException(); + throw (IOException) e.getNestedException(); } Throwables.rethrow(e); } @@ -222,20 +237,27 @@ public void writeXmlConfiguration(final OutputStream output) throws IOException @Override public String toXmlConfiguration() { - final StringWriter sw = new StringWriter(); + final StringWriter writer = new StringWriter(); try { - final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(sw); + final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer); writeXmlConfiguration(xmlWriter); xmlWriter.close(); - } catch (final XMLStreamException e) { + return formatXml(writer.toString()); + } catch (final XMLStreamException | TransformerException e) { Throwables.rethrow(e); } - return sw.toString(); + return writer.toString(); + } + + private String formatXml(final String xml) + throws TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError { + final StringWriter writer = new StringWriter(); + formatXml(new StreamSource(new StringReader(xml)), new StreamResult(writer)); + return writer.toString(); } private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLStreamException { xmlWriter.writeStartDocument(); - xmlWriter.writeCharacters(EOL); xmlWriter.writeStartElement("Configuration"); if (name != null) { @@ -266,13 +288,11 @@ private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLSt xmlWriter.writeAttribute("monitorInterval", String.valueOf(monitorInterval)); } - xmlWriter.writeCharacters(EOL); - writeXmlSection(xmlWriter, properties); writeXmlSection(xmlWriter, scripts); writeXmlSection(xmlWriter, customLevels); if (filters.getComponents().size() == 1) { - writeXmlComponent(xmlWriter, filters.getComponents().get(0), 1); + writeXmlComponent(xmlWriter, filters.getComponents().get(0)); } else if (filters.getComponents().size() > 1) { writeXmlSection(xmlWriter, filters); } @@ -280,47 +300,30 @@ private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLSt writeXmlSection(xmlWriter, loggers); xmlWriter.writeEndElement(); // "Configuration" - xmlWriter.writeCharacters(EOL); - xmlWriter.writeEndDocument(); } private void writeXmlSection(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException { if (!component.getAttributes().isEmpty() || !component.getComponents().isEmpty() || component.getValue() != null) { - writeXmlComponent(xmlWriter, component, 1); + writeXmlComponent(xmlWriter, component); } } - private void writeXmlComponent(final XMLStreamWriter xmlWriter, final Component component, final int nesting) throws XMLStreamException { + private void writeXmlComponent(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException { if (!component.getComponents().isEmpty() || component.getValue() != null) { - writeXmlIndent(xmlWriter, nesting); xmlWriter.writeStartElement(component.getPluginType()); writeXmlAttributes(xmlWriter, component); - if (!component.getComponents().isEmpty()) { - xmlWriter.writeCharacters(EOL); - } for (final Component subComponent : component.getComponents()) { - writeXmlComponent(xmlWriter, subComponent, nesting + 1); + writeXmlComponent(xmlWriter, subComponent); } if (component.getValue() != null) { xmlWriter.writeCharacters(component.getValue()); } - if (!component.getComponents().isEmpty()) { - writeXmlIndent(xmlWriter, nesting); - } xmlWriter.writeEndElement(); } else { - writeXmlIndent(xmlWriter, nesting); xmlWriter.writeEmptyElement(component.getPluginType()); writeXmlAttributes(xmlWriter, component); } - xmlWriter.writeCharacters(EOL); - } - - private void writeXmlIndent(final XMLStreamWriter xmlWriter, final int nesting) throws XMLStreamException { - for (int i = 0; i < nesting; i++) { - xmlWriter.writeCharacters(INDENT); - } } private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException { @@ -356,12 +359,12 @@ public AppenderRefComponentBuilder newAppenderRef(final String ref) { } @Override - public LoggerComponentBuilder newAsyncLogger(String name) { + public LoggerComponentBuilder newAsyncLogger(final String name) { return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger"); } @Override - public LoggerComponentBuilder newAsyncLogger(String name, boolean includeLocation) { + public LoggerComponentBuilder newAsyncLogger(final String name, final boolean includeLocation) { return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger", includeLocation); } @@ -382,7 +385,7 @@ public LoggerComponentBuilder newAsyncLogger(final String name, final String lev @Override public LoggerComponentBuilder newAsyncLogger(final String name, final String level, final boolean includeLocation) { - return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger"); + return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger", includeLocation); } @Override @@ -391,7 +394,7 @@ public RootLoggerComponentBuilder newAsyncRootLogger() { } @Override - public RootLoggerComponentBuilder newAsyncRootLogger(boolean includeLocation) { + public RootLoggerComponentBuilder newAsyncRootLogger(final boolean includeLocation) { return new DefaultRootLoggerComponentBuilder(this, null, "AsyncRoot", includeLocation); } @@ -464,12 +467,12 @@ public LayoutComponentBuilder newLayout(final String type) { } @Override - public LoggerComponentBuilder newLogger(String name) { + public LoggerComponentBuilder newLogger(final String name) { return new DefaultLoggerComponentBuilder(this, name, null); } @Override - public LoggerComponentBuilder newLogger(String name, boolean includeLocation) { + public LoggerComponentBuilder newLogger(final String name, final boolean includeLocation) { return new DefaultLoggerComponentBuilder(this, name, null, includeLocation); } @@ -499,7 +502,7 @@ public RootLoggerComponentBuilder newRootLogger() { } @Override - public RootLoggerComponentBuilder newRootLogger(boolean includeLocation) { + public RootLoggerComponentBuilder newRootLogger(final boolean includeLocation) { return new DefaultRootLoggerComponentBuilder(this, null, includeLocation); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java index aee02816bab..880a426e672 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core.config.composite; -import java.io.File; -import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -29,70 +27,56 @@ import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher; -import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Reconfigurable; -import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil; import org.apache.logging.log4j.core.config.status.StatusConfiguration; -import org.apache.logging.log4j.core.util.FileWatcher; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.core.util.Source; import org.apache.logging.log4j.core.util.WatchManager; -import org.apache.logging.log4j.util.LoaderUtil; -import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.core.util.Watcher; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.util.ResolverUtil; /** * A Composite Configuration. */ public class CompositeConfiguration extends AbstractConfiguration implements Reconfigurable { - /** - * Allow the ConfigurationFactory class to be specified as a system property. - */ - public static final String MERGE_STRATEGY_PROPERTY = "log4j.mergeStrategy"; - private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; private final List configurations; - private MergeStrategy mergeStrategy; + private final MergeStrategy mergeStrategy; /** - * Construct the ComponsiteConfiguration. + * Construct the CompositeConfiguration. * * @param configurations The List of Configurations to merge. */ public CompositeConfiguration(final List configurations) { - super(configurations.get(0).getLoggerContext(), ConfigurationSource.NULL_SOURCE); + super(configurations.get(0).getLoggerContext(), ConfigurationSource.COMPOSITE_SOURCE); rootNode = configurations.get(0).getRootNode(); this.configurations = configurations; - final String mergeStrategyClassName = PropertiesUtil.getProperties().getStringProperty(MERGE_STRATEGY_PROPERTY, - DefaultMergeStrategy.class.getName()); - try { - mergeStrategy = LoaderUtil.newInstanceOf(mergeStrategyClassName); - } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException | - InstantiationException ex) { - mergeStrategy = new DefaultMergeStrategy(); - } + mergeStrategy = getComponent(MergeStrategy.KEY); for (final AbstractConfiguration config : configurations) { mergeStrategy.mergeRootProperties(rootNode, config); } - final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES) - .withStatus(getDefaultStatus()); + final StatusConfiguration statusConfig = new StatusConfiguration().setVerboseClasses(VERBOSE_CLASSES) + .setStatus(getDefaultStatus()); for (final Map.Entry entry : rootNode.getAttributes().entrySet()) { final String key = entry.getKey(); - final String value = getStrSubstitutor().replace(entry.getValue()); + final String value = getConfigurationStrSubstitutor().replace(entry.getValue()); if ("status".equalsIgnoreCase(key)) { - statusConfig.withStatus(value.toUpperCase()); + statusConfig.setStatus(value.toUpperCase()); } else if ("dest".equalsIgnoreCase(key)) { - statusConfig.withDestination(value); + statusConfig.setDestination(value); } else if ("shutdownHook".equalsIgnoreCase(key)) { isShutdownHookEnabled = !"disable".equalsIgnoreCase(value); } else if ("shutdownTimeout".equalsIgnoreCase(key)) { shutdownTimeoutMillis = Long.parseLong(value); } else if ("verbose".equalsIgnoreCase(key)) { - statusConfig.withVerbosity(value); + statusConfig.setVerbosity(value); } else if ("packages".equalsIgnoreCase(key)) { - pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); + LOGGER.warn("The packages attribute is no longer supported"); } else if ("name".equalsIgnoreCase(key)) { setName(value); } @@ -106,20 +90,18 @@ public void setup() { staffChildConfiguration(targetConfiguration); final WatchManager watchManager = getWatchManager(); final WatchManager targetWatchManager = targetConfiguration.getWatchManager(); - final FileWatcher fileWatcher = new ConfiguratonFileWatcher(this, listeners); if (targetWatchManager.getIntervalSeconds() > 0) { watchManager.setIntervalSeconds(targetWatchManager.getIntervalSeconds()); - final Map watchers = targetWatchManager.getWatchers(); - for (final Map.Entry entry : watchers.entrySet()) { - if (entry.getValue() instanceof ConfiguratonFileWatcher) { - watchManager.watchFile(entry.getKey(), fileWatcher); - } + final Map watchers = targetWatchManager.getConfigurationWatchers(); + for (final Map.Entry entry : watchers.entrySet()) { + watchManager.watch(entry.getKey(), entry.getValue().newWatcher(this, listeners, + entry.getValue().getLastModified())); } } for (final AbstractConfiguration sourceConfiguration : configurations.subList(1, configurations.size())) { staffChildConfiguration(sourceConfiguration); final Node sourceRoot = sourceConfiguration.getRootNode(); - mergeStrategy.mergConfigurations(rootNode, sourceRoot, getPluginManager()); + mergeStrategy.mergeConfigurations(rootNode, sourceRoot, corePlugins); if (LOGGER.isEnabled(Level.ALL)) { final StringBuilder sb = new StringBuilder(); printNodes("", rootNode, sb); @@ -132,11 +114,10 @@ public void setup() { watchManager.setIntervalSeconds(monitorInterval); } final WatchManager sourceWatchManager = sourceConfiguration.getWatchManager(); - final Map watchers = sourceWatchManager.getWatchers(); - for (final Map.Entry entry : watchers.entrySet()) { - if (entry.getValue() instanceof ConfiguratonFileWatcher) { - watchManager.watchFile(entry.getKey(), fileWatcher); - } + final Map watchers = sourceWatchManager.getConfigurationWatchers(); + for (final Map.Entry entry : watchers.entrySet()) { + watchManager.watch(entry.getKey(), entry.getValue().newWatcher(this, listeners, + entry.getValue().getLastModified())); } } } @@ -146,7 +127,7 @@ public void setup() { public Configuration reconfigure() { LOGGER.debug("Reconfiguring composite configuration"); final List configs = new ArrayList<>(); - final ConfigurationFactory factory = ConfigurationFactory.getInstance(); + final ConfigurationFactory factory = injector.getInstance(ConfigurationFactory.KEY); for (final AbstractConfiguration config : configurations) { final ConfigurationSource source = config.getConfigurationSource(); final URI sourceURI = source.getURI(); @@ -168,7 +149,7 @@ public Configuration reconfigure() { } private void staffChildConfiguration(final AbstractConfiguration childConfiguration) { - childConfiguration.setPluginManager(pluginManager); + childConfiguration.setCorePlugins(corePlugins); childConfiguration.setScriptManager(scriptManager); childConfiguration.setup(); } @@ -185,7 +166,7 @@ private void printNodes(final String indent, final Node node, final StringBuilde public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()) + " [configurations=" + configurations + ", mergeStrategy=" + mergeStrategy + ", rootNode=" + rootNode + ", listeners=" + listeners - + ", pluginPackages=" + pluginPackages + ", pluginManager=" + pluginManager + ", isShutdownHookEnabled=" + + ", corePlugins=" + corePlugins + ", isShutdownHookEnabled=" + isShutdownHookEnabled + ", shutdownTimeoutMillis=" + shutdownTimeoutMillis + ", scriptManager=" + scriptManager + "]"; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java index 7b9a21df853..fe8c13d0dff 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java @@ -24,10 +24,10 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.config.AbstractConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.core.filter.CompositeFilter; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; /** * The default merge strategy for composite configurations. @@ -41,7 +41,7 @@ * configurations. *

  • Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named * duplicates may be present.
  • - *
  • Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in previous + *
  • Scripts and ScriptFile references are aggregated. Duplicate definitions replace those in previous * configurations.
  • *
  • Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including * all of the Appender's subcomponents.
  • @@ -108,19 +108,19 @@ public void mergeRootProperties(final Node rootNode, final AbstractConfiguration /** * Merge the source Configuration into the target Configuration. * - * @param target The target node to merge into. - * @param source The source node. - * @param pluginManager The PluginManager. + * @param target The target node to merge into. + * @param source The source node. + * @param corePlugins The Core plugins to use. */ @Override - public void mergConfigurations(final Node target, final Node source, final PluginManager pluginManager) { + public void mergeConfigurations(final Node target, final Node source, final PluginNamespace corePlugins) { for (final Node sourceChildNode : source.getChildren()) { final boolean isFilter = isFilterNode(sourceChildNode); boolean isMerged = false; for (final Node targetChildNode : target.getChildren()) { if (isFilter) { if (isFilterNode(targetChildNode)) { - updateFilterNode(target, targetChildNode, sourceChildNode, pluginManager); + updateFilterNode(target, targetChildNode, sourceChildNode, corePlugins); isMerged = true; break; } @@ -163,7 +163,7 @@ public void mergConfigurations(final Node target, final Node source, final Plugi for (final Node targetChild : targetNode.getChildren()) { if (isFilterNode(targetChild)) { updateFilterNode(loggerNode, targetChild, sourceLoggerChild, - pluginManager); + corePlugins); foundFilter = true; break; } @@ -240,14 +240,14 @@ private Node getLoggerNode(final Node parentNode, final String name) { } private void updateFilterNode(final Node target, final Node targetChildNode, final Node sourceChildNode, - final PluginManager pluginManager) { + final PluginNamespace corePlugins) { if (CompositeFilter.class.isAssignableFrom(targetChildNode.getType().getPluginClass())) { final Node node = new Node(targetChildNode, sourceChildNode.getName(), sourceChildNode.getType()); node.getChildren().addAll(sourceChildNode.getChildren()); node.getAttributes().putAll(sourceChildNode.getAttributes()); targetChildNode.getChildren().add(node); } else { - final PluginType pluginType = pluginManager.getPluginType(FILTERS); + final PluginType pluginType = corePlugins.get(FILTERS); final Node filtersNode = new Node(targetChildNode, FILTERS, pluginType); final Node node = new Node(filtersNode, sourceChildNode.getName(), sourceChildNode.getType()); node.getAttributes().putAll(sourceChildNode.getAttributes()); @@ -266,11 +266,11 @@ private boolean isFilterNode(final Node node) { private boolean isSameName(final Node node1, final Node node2) { final String value = node1.getAttributes().get(NAME); - return value != null && value.toLowerCase().equals(node2.getAttributes().get(NAME).toLowerCase()); + return value != null && value.equalsIgnoreCase(node2.getAttributes().get(NAME)); } private boolean isSameReference(final Node node1, final Node node2) { final String value = node1.getAttributes().get(REF); - return value != null && value.toLowerCase().equals(node2.getAttributes().get(REF).toLowerCase()); + return value != null && value.equalsIgnoreCase(node2.getAttributes().get(REF)); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java index a01f6a3777b..cd7971f476f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java @@ -17,14 +17,17 @@ package org.apache.logging.log4j.core.config.composite; import org.apache.logging.log4j.core.config.AbstractConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.model.PluginNamespace; /** * Merges two configurations together */ public interface MergeStrategy { + Key KEY = new Key<>() {}; + /** * Merge the root node properties into the configuration. * @param rootNode The composite root node. @@ -33,9 +36,11 @@ public interface MergeStrategy { void mergeRootProperties(Node rootNode, AbstractConfiguration configuration); /** - * Merge the soure node tree into the target node tree. - * @param target The target Node tree. - * @param source The source Node tree. + * Merge the source node tree into the target node tree. + * + * @param target The target Node tree. + * @param source The source Node tree. + * @param corePlugins The Core plugins to merge. */ - void mergConfigurations(Node target, Node source, PluginManager pluginManager); + void mergeConfigurations(Node target, Node source, PluginNamespace corePlugins); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java index fd920fa47b5..0f289b6ad1d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java @@ -18,4 +18,4 @@ /** * Support for composite configurations. */ -package org.apache.logging.log4j.core.config.composite; \ No newline at end of file +package org.apache.logging.log4j.core.config.composite; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfiguration.java new file mode 100644 index 00000000000..18f1cf26042 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfiguration.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.jason; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.AbstractConfiguration; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.apache.logging.log4j.core.config.status.StatusConfiguration; +import org.apache.logging.log4j.core.util.JsonReader; +import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.plugins.util.ResolverUtil; +import org.apache.logging.log4j.util.Cast; + +public class JsonConfiguration extends AbstractConfiguration implements Reconfigurable { + private static final String[] VERBOSE_CLASSES = new String[] { ResolverUtil.class.getName() }; + + private final List statuses = new ArrayList<>(); + private Map root; + + public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationSource configurationSource) { + super(loggerContext, configurationSource); + try { + final byte[] bytes; + try (final var configStream = configurationSource.getInputStream()) { + bytes = configStream.readAllBytes(); + root = Cast.cast(JsonReader.read(new String(bytes, StandardCharsets.UTF_8))); + } + if (root.size() == 1) { + for (final Object value : root.values()) { + root = Cast.cast(value); + } + } + processAttributes(rootNode, root); + final StatusConfiguration statusConfig = new StatusConfiguration().setVerboseClasses(VERBOSE_CLASSES) + .setStatus(getDefaultStatus()); + final AtomicInteger monitorIntervalSeconds = new AtomicInteger(); + + rootNode.getAttributes().forEach((key, value) -> { + if ("status".equalsIgnoreCase(key)) { + statusConfig.setStatus(value); + } else if ("dest".equalsIgnoreCase(key)) { + statusConfig.setDestination(value); + } else if ("shutdownHook".equalsIgnoreCase(key)) { + isShutdownHookEnabled = !"disable".equalsIgnoreCase(value); + } else if ("shutdownTimeout".equalsIgnoreCase(key)) { + shutdownTimeoutMillis = Long.parseLong(value); + } else if ("verbose".equalsIgnoreCase(key)) { + statusConfig.setVerbosity(value); + } else if ("packages".equalsIgnoreCase(key)) { + LOGGER.warn("The packages attribute is no longer supported"); + } else if ("name".equalsIgnoreCase(key)) { + setName(value); + } else if ("monitorInterval".equalsIgnoreCase(key)) { + monitorIntervalSeconds.setOpaque(Integer.parseInt(value)); + } else if ("advertiser".equalsIgnoreCase(key)) { + createAdvertiser(value, configurationSource, bytes, "application/json"); + } + }); + initializeWatchers(this, configurationSource, monitorIntervalSeconds.getOpaque()); + statusConfig.initialize(); + if (getName() == null) { + setName(configurationSource.getLocation()); + } + } catch (final Exception e) { + LOGGER.error("Error parsing {}", configurationSource.getLocation(), e); + } + } + + @Override + public void setup() { + final List children = rootNode.getChildren(); + root.forEach((key, value) -> { + if (value instanceof Map) { + LOGGER.debug("Processing node for object {}", key); + children.add(constructNode(key, rootNode, Cast.cast(value))); + } + }); + LOGGER.debug("Completed parsing configuration"); + if (statuses.size() > 0) { + for (final var s : statuses) { + LOGGER.error("Error processing element {}: {}", s.name, s.errorType); + } + } + } + + private Node constructNode(final String key, final Node parent, final Map value) { + final PluginType pluginType = corePlugins.get(key); + final Node node = new Node(parent, key, pluginType); + processAttributes(node, value); + final List children = node.getChildren(); + + value.forEach((k, v) -> { + if (isValueType(v)) { + LOGGER.debug("Node {} is of type {}", k, v != null ? v.getClass() : null); + return; + } + if (pluginType == null) { + statuses.add(new Status(v, k, ErrorType.CLASS_NOT_FOUND)); + return; + } + if (v instanceof List) { + LOGGER.debug("Processing node for array {}", k); + ((List) v).forEach(object -> { + if (object instanceof Map) { + final Map map = Cast.cast(object); + final String type = getType(map).orElse(k); + final PluginType entryType = corePlugins.get(type); + final Node child = new Node(node, k, entryType); + processAttributes(child, map); + if (type.equalsIgnoreCase(k)) { + LOGGER.debug("Processing {}[{}]", k, children.size()); + } else { + LOGGER.debug("Processing {} {}[{}]", type, k, children.size()); + } + final List grandchildren = child.getChildren(); + map.forEach((itemKey, itemValue) -> { + if (itemValue instanceof Map) { + LOGGER.debug("Processing node for object {}", itemKey); + grandchildren.add(constructNode(itemKey, child, Cast.cast(itemValue))); + } else if (itemValue instanceof List) { + final List list = (List) itemValue; + LOGGER.debug("Processing array for object {}", itemKey); + list.forEach(subValue -> grandchildren.add( + constructNode(itemKey, child, Cast.cast(subValue)))); + } + }); + children.add(child); + } + }); + } else { + LOGGER.debug("Processing node for object {}", k); + children.add(constructNode(k, node, Cast.cast(v))); + } + }); + + final String t; + if (pluginType == null) { + t = "null"; + } else { + t = pluginType.getElementType() + ':' + pluginType.getPluginClass(); + } + + final String p = node.getParent() == null ? "null" + : node.getParent().getName() == null ? LoggerConfig.ROOT : node.getParent().getName(); + LOGGER.debug("Returning {} with parent {} of type {}", node.getName(), p, t); + return node; + } + + @Override + public Configuration reconfigure() { + try { + final ConfigurationSource configurationSource = getConfigurationSource().resetInputStream(); + if (configurationSource == null) { + return null; + } + return new JsonConfiguration(getLoggerContext(), configurationSource); + } catch (final IOException e) { + LOGGER.error("Cannot locate file {}", getConfigurationSource(), e); + } + return null; + } + + private static boolean isValueType(final Object value) { + return !(value instanceof Map || value instanceof List); + } + + private static void processAttributes(final Node parent, final Map node) { + final Map attributes = parent.getAttributes(); + node.forEach((key, value) -> { + if (!key.equalsIgnoreCase("type") && isValueType(value)) { + attributes.put(key, String.valueOf(value)); + } + }); + } + + private static Optional getType(final Map node) { + for (final Map.Entry entry : node.entrySet()) { + if (entry.getKey().equalsIgnoreCase("type")) { + final Object value = entry.getValue(); + if (isValueType(value)) { + return Optional.of(String.valueOf(value)); + } + } + } + return Optional.empty(); + } + + /** + * The error that occurred. + */ + private enum ErrorType { + CLASS_NOT_FOUND + } + + /** + * Status for recording errors. + */ + private static class Status { + private final Object node; + private final String name; + private final ErrorType errorType; + + private Status(final Object node, final String name, final ErrorType errorType) { + this.node = node; + this.name = name; + this.errorType = errorType; + } + + @Override + public String toString() { + return "Status{" + + "node=" + node + + ", name='" + name + '\'' + + ", errorType=" + errorType + + '}'; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfigurationFactory.java new file mode 100644 index 00000000000..cb568dacae1 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/jason/JsonConfigurationFactory.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.jason; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Order; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; + +@Namespace(ConfigurationFactory.NAMESPACE) +@Plugin("JsonConfigurationFactory") +@Order(6) +public class JsonConfigurationFactory extends ConfigurationFactory { + + /** + * The file extensions supported by this factory. + */ + private static final String[] SUFFIXES = new String[] {".json", ".jsn"}; + + @Override + protected String[] getSupportedTypes() { + return SUFFIXES; + } + + @Override + public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) { + return new JsonConfiguration(loggerContext, source); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java index 5b38dfbc53f..813c42e53c5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java @@ -17,7 +17,6 @@ package org.apache.logging.log4j.core.config.json; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -26,22 +25,21 @@ import java.util.List; import java.util.Map; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher; import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Reconfigurable; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; -import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil; import org.apache.logging.log4j.core.config.status.StatusConfiguration; -import org.apache.logging.log4j.core.util.FileWatcher; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.plugins.util.ResolverUtil; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; /** * Creates a Node hierarchy from a JSON file. @@ -54,11 +52,10 @@ public class JsonConfiguration extends AbstractConfiguration implements Reconfig public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationSource configSource) { super(loggerContext, configSource); - final File configFile = configSource.getFile(); - byte[] buffer; + final byte[] buffer; try { try (final InputStream configStream = configSource.getInputStream()) { - buffer = toByteArray(configStream); + buffer = configStream.readAllBytes(); } final InputStream is = new ByteArrayInputStream(buffer); root = getObjectMapper().readTree(is); @@ -68,39 +65,34 @@ public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationS } } processAttributes(rootNode, root); - final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES) - .withStatus(getDefaultStatus()); + final StatusConfiguration statusConfig = new StatusConfiguration().setVerboseClasses(VERBOSE_CLASSES) + .setStatus(getDefaultStatus()); + int monitorIntervalSeconds = 0; for (final Map.Entry entry : rootNode.getAttributes().entrySet()) { final String key = entry.getKey(); - final String value = getStrSubstitutor().replace(entry.getValue()); + final String value = getConfigurationStrSubstitutor().replace(entry.getValue()); // TODO: this duplicates a lot of the XmlConfiguration constructor if ("status".equalsIgnoreCase(key)) { - statusConfig.withStatus(value); + statusConfig.setStatus(value); } else if ("dest".equalsIgnoreCase(key)) { - statusConfig.withDestination(value); + statusConfig.setDestination(value); } else if ("shutdownHook".equalsIgnoreCase(key)) { isShutdownHookEnabled = !"disable".equalsIgnoreCase(value); } else if ("shutdownTimeout".equalsIgnoreCase(key)) { shutdownTimeoutMillis = Long.parseLong(value); } else if ("verbose".equalsIgnoreCase(entry.getKey())) { - statusConfig.withVerbosity(value); + statusConfig.setVerbosity(value); } else if ("packages".equalsIgnoreCase(key)) { - pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); + LOGGER.warn("The packages attribute is no longer supported"); } else if ("name".equalsIgnoreCase(key)) { setName(value); } else if ("monitorInterval".equalsIgnoreCase(key)) { - final int intervalSeconds = Integer.parseInt(value); - if (intervalSeconds > 0) { - getWatchManager().setIntervalSeconds(intervalSeconds); - if (configFile != null) { - final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners); - getWatchManager().watchFile(configFile, watcher); - } - } + monitorIntervalSeconds = Integer.parseInt(value); } else if ("advertiser".equalsIgnoreCase(key)) { createAdvertiser(value, configSource, buffer, "application/json"); } } + initializeWatchers(this, configSource, monitorIntervalSeconds); statusConfig.initialize(); if (getName() == null) { setName(configSource.getLocation()); @@ -151,7 +143,7 @@ public Configuration reconfigure() { } private Node constructNode(final String name, final Node parent, final JsonNode jsonNode) { - final PluginType type = pluginManager.getPluginType(name); + final PluginType type = corePlugins.get(name); final Node node = new Node(parent, name, type); processAttributes(node, jsonNode); final Iterator> iter = jsonNode.fields(); @@ -167,7 +159,7 @@ private Node constructNode(final String name, final Node parent, final JsonNode LOGGER.debug("Processing node for array {}", entry.getKey()); for (int i = 0; i < n.size(); ++i) { final String pluginType = getType(n.get(i), entry.getKey()); - final PluginType entryType = pluginManager.getPluginType(pluginType); + final PluginType entryType = corePlugins.get(pluginType); final Node item = new Node(node, entry.getKey(), entryType); processAttributes(item, n.get(i)); if (pluginType.equals(entry.getKey())) { @@ -203,11 +195,11 @@ private Node constructNode(final String name, final Node parent, final JsonNode } } - String t; + final String t; if (type == null) { t = "null"; } else { - t = type.getElementName() + ':' + type.getPluginClass(); + t = type.getElementType() + ':' + type.getPluginClass(); } final String p = node.getParent() == null ? "null" diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfigurationFactory.java index 20c957cee1a..56f3375a5af 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfigurationFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfigurationFactory.java @@ -20,12 +20,8 @@ import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.config.Order; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.util.Loader; -@Plugin(name = "JsonConfigurationFactory", category = ConfigurationFactory.CATEGORY) -@Order(6) public class JsonConfigurationFactory extends ConfigurationFactory { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/Plugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/Plugin.java deleted file mode 100644 index 8aaf11753b3..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/Plugin.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config.plugins; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apache.logging.log4j.util.Strings; - -/** - * Annotation that identifies a Class as a Plugin. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Plugin { - - /** - * Value of the elementType when none is specified. - */ - String EMPTY = Strings.EMPTY; - - /** - * Name of the plugin. Note that this name is case-insensitive. - */ - String name(); - - /** - * Category to place the plugin under. Category names are case-sensitive. - */ - String category(); - - /** - * Name of the corresponding category of elements this plugin belongs under. For example, {@code appender} would - * indicate an {@link org.apache.logging.log4j.core.Appender} plugin which would be in the - * {@code } element of a {@link org.apache.logging.log4j.core.config.Configuration}. - */ - String elementType() default EMPTY; - - /** - * Indicates if the plugin class implements a useful {@link Object#toString()} method for use in log messages. - */ - boolean printObject() default false; - - boolean deferChildren() default false; -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java index 7be3deabfcf..0781a08eaba 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java @@ -16,6 +16,9 @@ */ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.core.config.plugins.util.PluginAliasesProvider; +import org.apache.logging.log4j.plugins.name.AliasesProvider; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -23,11 +26,14 @@ import java.lang.annotation.Target; /** - * Identifies a list of aliases for a {@link Plugin}, {@link PluginAttribute}, or {@link PluginBuilderAttribute}. + * Identifies a list of aliases for a Plugin, PluginAttribute, or PluginBuilderAttribute. + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD}) +@AliasesProvider(PluginAliasesProvider.class) +@Deprecated(since = "3.0.0") public @interface PluginAliases { String[] value(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java index bd8822086b5..abd6848a4ea 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java @@ -16,26 +16,34 @@ */ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.core.config.plugins.util.PluginAttributeNameProvider; +import org.apache.logging.log4j.core.config.plugins.visit.PluginAttributeVisitor; +import org.apache.logging.log4j.plugins.QualifierType; +import org.apache.logging.log4j.plugins.name.NameProvider; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.util.Strings; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor; -import org.apache.logging.log4j.util.Strings; - /** * Identifies a Plugin Attribute and its default value. Note that only one of the defaultFoo attributes will be * used based on the type this annotation is attached to. Thus, for primitive types, the defaultType * attribute will be used for some Type. However, for more complex types (including enums), the default * string value is used instead and should correspond to the string that would correctly convert to the appropriate * enum value using {@link Enum#valueOf(Class, String) Enum.valueOf}. + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) -@PluginVisitorStrategy(PluginAttributeVisitor.class) +@NodeVisitor.Kind(PluginAttributeVisitor.class) +@NameProvider(PluginAttributeNameProvider.class) +@QualifierType +@Deprecated(since = "3.0.0") public @interface PluginAttribute { /** @@ -88,7 +96,6 @@ */ String defaultString() default Strings.EMPTY; - // TODO: could we allow a blank value and infer the attribute name through reflection? /** * Specifies the name of the attribute (case-insensitive) this annotation corresponds to. */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java index 675d78f3cd1..96ff7fa6767 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java @@ -17,22 +17,30 @@ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.core.config.plugins.util.PluginBuilderAttributeNameProvider; +import org.apache.logging.log4j.core.config.plugins.visit.PluginBuilderAttributeVisitor; +import org.apache.logging.log4j.plugins.QualifierType; +import org.apache.logging.log4j.plugins.name.NameProvider; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.util.Strings; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor; -import org.apache.logging.log4j.util.Strings; - /** * Marks a field as a Plugin Attribute. + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) -@PluginVisitorStrategy(PluginBuilderAttributeVisitor.class) +@NodeVisitor.Kind(PluginBuilderAttributeVisitor.class) +@NameProvider(PluginBuilderAttributeNameProvider.class) +@QualifierType +@Deprecated(since = "3.0.0") public @interface PluginBuilderAttribute { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java index 4e69262f166..b23e812dc56 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java @@ -17,6 +17,8 @@ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.plugins.FactoryType; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -25,10 +27,13 @@ /** * Marks a method as a factory for custom Plugin builders. + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@FactoryType +@Deprecated(since = "3.0.0") public @interface PluginBuilderFactory { // empty } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java index ac7eb74415d..2b6c1294db8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java @@ -16,22 +16,26 @@ */ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.core.config.plugins.visit.PluginConfigurationVisitor; +import org.apache.logging.log4j.plugins.QualifierType; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginConfigurationVisitor; - /** - * Identifies a parameter or field as a Configuration. - * @see org.apache.logging.log4j.core.config.Configuration + * Identifies the current {@link org.apache.logging.log4j.core.config.Configuration}. This can be injected as a + * parameter to a static {@linkplain org.apache.logging.log4j.plugins.PluginFactory factory method}, or as a field + * or single-parameter method in a plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}. */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER, ElementType.FIELD}) -@PluginVisitorStrategy(PluginConfigurationVisitor.class) +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD}) +@NodeVisitor.Kind(PluginConfigurationVisitor.class) +@QualifierType public @interface PluginConfiguration { // empty } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java index 7ea358bffc4..4b76ab3b8fc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java @@ -16,21 +16,29 @@ */ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.core.config.plugins.util.PluginElementNameProvider; +import org.apache.logging.log4j.plugins.QualifierType; +import org.apache.logging.log4j.plugins.name.NameProvider; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.plugins.visit.PluginElementVisitor; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor; - /** * Identifies a parameter as a Plugin and corresponds with an XML element (or equivalent) in configuration files. + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) -@PluginVisitorStrategy(PluginElementVisitor.class) +@NodeVisitor.Kind(PluginElementVisitor.class) +@NameProvider(PluginElementNameProvider.class) +@QualifierType +@Deprecated(since = "3.0.0") public @interface PluginElement { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java index 1c041061138..b674e3ae540 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java @@ -16,6 +16,8 @@ */ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.plugins.FactoryType; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -28,10 +30,13 @@ *

    * There can only be one factory method per class. *

    + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@FactoryType +@Deprecated(since = "3.0.0") public @interface PluginFactory { // empty } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginLoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginLoggerContext.java new file mode 100644 index 00000000000..2e680017052 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginLoggerContext.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.logging.log4j.core.config.plugins.visit.PluginLoggerContextVisitor; +import org.apache.logging.log4j.plugins.QualifierType; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; + +/** + * Identifies a parameter or field as a LoggerContext. + * @see org.apache.logging.log4j.core.LoggerContext + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD}) +@NodeVisitor.Kind(PluginLoggerContextVisitor.class) +@QualifierType +public @interface PluginLoggerContext { + // empty +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java index d60f1b5e96a..cba55505b0c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java @@ -16,21 +16,26 @@ */ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.plugins.QualifierType; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.plugins.visit.PluginNodeVisitor; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginNodeVisitor; - /** * Identifies a Plugin configuration Node. + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) -@PluginVisitorStrategy(PluginNodeVisitor.class) +@NodeVisitor.Kind(PluginNodeVisitor.class) +@QualifierType +@Deprecated(since = "3.0.0") public @interface PluginNode { // empty } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java index 9c20cc2e05b..e31cd2188fc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java @@ -16,24 +16,32 @@ */ package org.apache.logging.log4j.core.config.plugins; +import org.apache.logging.log4j.core.config.plugins.util.PluginValueNameProvider; +import org.apache.logging.log4j.plugins.QualifierType; +import org.apache.logging.log4j.plugins.name.NameProvider; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.plugins.visit.PluginValueVisitor; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginValueVisitor; - /** * Identifies a parameter as a value. These correspond with property values generally, but are meant as values to be * used as a placeholder value somewhere. * * @see org.apache.logging.log4j.core.config.PropertiesPlugin + * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins. */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) -@PluginVisitorStrategy(PluginValueVisitor.class) +@NodeVisitor.Kind(PluginValueVisitor.class) +@NameProvider(PluginValueNameProvider.class) +@QualifierType +@Deprecated(since = "3.0.0") public @interface PluginValue { String value(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginVisitorStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginVisitorStrategy.java deleted file mode 100644 index 65fcb767bcf..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginVisitorStrategy.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins; - -import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor; - -/** - * Meta-annotation to denote the class name to use that implements - * {@link org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor} for the annotated annotation. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.ANNOTATION_TYPE) -public @interface PluginVisitorStrategy { - - /** - * The class to use that implements {@link org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor} - * for the given annotation. The generic type in {@code PluginVisitor} should match the annotation this annotation - * is applied to. - */ - Class> value(); -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/Base64Converter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/Base64Converter.java deleted file mode 100644 index f4e421fe1ac..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/Base64Converter.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config.plugins.convert; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; - -/** - * @Since 2.9 - */ -public class Base64Converter { - - private static final Logger LOGGER = StatusLogger.getLogger(); - private static Method method = null; - private static Object decoder = null; - - static { - try { - // Base64 is available in Java 8 and up. - Class clazz = LoaderUtil.loadClass("java.util.Base64"); - final Method getDecoder = clazz.getMethod("getDecoder", (Class[]) null); - decoder = getDecoder.invoke(null, (Object[]) null); - clazz = decoder.getClass(); - method = clazz.getMethod("decode", String.class); - } catch (final ClassNotFoundException ex) { - - } catch (final NoSuchMethodException ex) { - - } catch (final IllegalAccessException ex) { - - } catch (final InvocationTargetException ex) { - - } - if (method == null) { - try { - // DatatypeConverter is not in the default module in Java 9. - final Class clazz = LoaderUtil.loadClass("javax.xml.bind.DatatypeConverter"); - method = clazz.getMethod("parseBase64Binary", String.class); - } catch (final ClassNotFoundException ex) { - LOGGER.error("No Base64 Converter is available"); - } catch (final NoSuchMethodException ex) { - - } - } - } - - public static byte[] parseBase64Binary(final String encoded) { - if (method == null) { - LOGGER.error("No base64 converter"); - } else { - try { - return (byte[]) method.invoke(decoder, encoded); - } catch (final IllegalAccessException ex) { - LOGGER.error("Error decoding string - " + ex.getMessage()); - } catch (final InvocationTargetException ex) { - LOGGER.error("Error decoding string - " + ex.getMessage()); - } - } - return new byte[0]; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java new file mode 100644 index 00000000000..7be995ca9ff --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java @@ -0,0 +1,314 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.convert; + +import java.io.File; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.Provider; +import java.security.Security; +import java.time.ZoneId; +import java.util.Base64; +import java.util.UUID; +import java.util.regex.Pattern; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.appender.rolling.action.Duration; +import org.apache.logging.log4j.core.util.CronExpression; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.convert.TypeConverter; +import org.apache.logging.log4j.plugins.convert.TypeConverters; +import org.apache.logging.log4j.util.LoaderUtil; + +/** + * General {@link TypeConverter} implementations. + * + * @since 2.1 Moved to the {@code convert} package. + */ +public final class CoreTypeConverters { + + private static final Base64.Decoder decoder = Base64.getDecoder(); + + /** + * Parses a {@link String} into a {@link BigDecimal}. + */ + @TypeConverters + @Plugin + public static class BigDecimalConverter implements TypeConverter { + @Override + public BigDecimal convert(final String s) { + return new BigDecimal(s); + } + } + + /** + * Parses a {@link String} into a {@link BigInteger}. + */ + @TypeConverters + @Plugin + public static class BigIntegerConverter implements TypeConverter { + @Override + public BigInteger convert(final String s) { + return new BigInteger(s); + } + } + + /** + * Converts a {@link String} into a {@code byte[]}. + *

    + * The supported formats are: + *

      + *
    • 0x0123456789ABCDEF
    • + *
    • Base64:ABase64String
    • + *
    • String using {@link Charset#defaultCharset()} [TODO Should this be UTF-8 instead?]
    • + *
    + */ + @TypeConverters + @Plugin + public static class ByteArrayConverter implements TypeConverter { + + private static final String PREFIX_0x = "0x"; + private static final String PREFIX_BASE64 = "Base64:"; + + @Override + public byte[] convert(final String value) { + byte[] bytes; + if (value == null || value.isEmpty()) { + bytes = new byte[0]; + } else if (value.startsWith(PREFIX_BASE64)) { + final String lexicalXSDBase64Binary = value.substring(PREFIX_BASE64.length()); + bytes = decoder.decode(lexicalXSDBase64Binary); + } else if (value.startsWith(PREFIX_0x)) { + final String lexicalXSDHexBinary = value.substring(PREFIX_0x.length()); + bytes = HexConverter.parseHexBinary(lexicalXSDHexBinary); + } else { + bytes = value.getBytes(Charset.defaultCharset()); + } + return bytes; + } + } + + /** + * Converts a {@link String} into a {@code char[]}. + */ + @TypeConverters + @Plugin + public static class CharArrayConverter implements TypeConverter { + @Override + public char[] convert(final String s) { + return s.toCharArray(); + } + } + + /** + * Converts a {@link String} into a {@link Charset}. + */ + @TypeConverters + @Plugin + public static class CharsetConverter implements TypeConverter { + @Override + public Charset convert(final String s) { + return Charset.forName(s); + } + } + + /** + * Converts a {@link String} into a {@link Class}. + */ + @TypeConverters + @Plugin + public static class ClassConverter implements TypeConverter> { + @Override + public Class convert(final String s) throws ClassNotFoundException { + switch (s.toLowerCase()) { + case "boolean": + return boolean.class; + case "byte": + return byte.class; + case "char": + return char.class; + case "double": + return double.class; + case "float": + return float.class; + case "int": + return int.class; + case "long": + return long.class; + case "short": + return short.class; + case "void": + return void.class; + default: + return LoaderUtil.loadClass(s); + } + + } + } + + @TypeConverters + @Plugin + public static class CronExpressionConverter implements TypeConverter { + @Override + public CronExpression convert(final String s) throws Exception { + return new CronExpression(s); + } + } + + /** + * Converts a {@link String} into a {@link Duration}. + * + * @since 2.5 + */ + @TypeConverters + @Plugin + public static class DurationConverter implements TypeConverter { + @Override + public Duration convert(final String s) { + return Duration.parse(s); + } + } + + /** + * Converts a {@link String} into a {@link File}. + */ + @TypeConverters + @Plugin + public static class FileConverter implements TypeConverter { + @Override + public File convert(final String s) { + return new File(s); + } + } + + /** + * Converts a {@link String} into an {@link InetAddress}. + */ + @TypeConverters + @Plugin + public static class InetAddressConverter implements TypeConverter { + @Override + public InetAddress convert(final String s) throws Exception { + return InetAddress.getByName(s); + } + } + + /** + * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names. + */ + @TypeConverters + @Plugin + public static class LevelConverter implements TypeConverter { + @Override + public Level convert(final String s) { + return Level.valueOf(s); + } + } + + /** + * Converts a {@link String} into a {@link Path}. + * + * @since 2.8 + */ + @TypeConverters + @Plugin + public static class PathConverter implements TypeConverter { + @Override + public Path convert(final String s) throws Exception { + return Paths.get(s); + } + } + + /** + * Converts a {@link String} into a {@link Pattern}. + */ + @TypeConverters + @Plugin + public static class PatternConverter implements TypeConverter { + @Override + public Pattern convert(final String s) { + return Pattern.compile(s); + } + } + + /** + * Converts a {@link String} into a {@link Provider}. + */ + @TypeConverters + @Plugin + public static class SecurityProviderConverter implements TypeConverter { + @Override + public Provider convert(final String s) { + return Security.getProvider(s); + } + } + + /** + * Converts a {@link String} into a {@link URI}. + */ + @TypeConverters + @Plugin + public static class UriConverter implements TypeConverter { + @Override + public URI convert(final String s) throws URISyntaxException { + return new URI(s); + } + } + + /** + * Converts a {@link String} into a {@link URL}. + */ + @TypeConverters + @Plugin + public static class UrlConverter implements TypeConverter { + @Override + public URL convert(final String s) throws MalformedURLException { + return new URL(s); + } + } + + /** + * Converts a {@link String} into a {@link UUID}. + * + * @since 2.8 + */ + @TypeConverters + @Plugin + public static class UuidConverter implements TypeConverter { + @Override + public UUID convert(final String s) throws Exception { + return UUID.fromString(s); + } + } + + @TypeConverters + @Plugin + public static class ZoneIdConverter implements TypeConverter { + @Override + public ZoneId convert(final String s) throws Exception { + return ZoneId.of(s); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/HexConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/HexConverter.java index e6296572ace..fb6fa55b3b3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/HexConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/HexConverter.java @@ -20,7 +20,7 @@ * Converts Strings to hex. This is used in place of java.xml.bind.DataTypeConverter which is not available by * default in Java 9. * - * @Since 2.9 + * @since 2.9 */ public class HexConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverter.java deleted file mode 100644 index e67e2134429..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.convert; - -/** - * Interface for doing automatic String conversion to a specific type. - * - * @param Converts Strings into the given type {@code T}. - * @since 2.1 Moved to the {@code convert} package. - */ -public interface TypeConverter { - - /** - * Converts a String to a given type. - * - * @param s the String to convert. Cannot be {@code null}. - * @return the converted object. - * @throws Exception thrown when a conversion error occurs - */ - T convert(String s) throws Exception; -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java deleted file mode 100644 index 5088f1503e6..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config.plugins.convert; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.UnknownFormatConversionException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; -import org.apache.logging.log4j.core.util.ReflectionUtil; -import org.apache.logging.log4j.core.util.TypeUtil; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * Registry for {@link TypeConverter} plugins. - * - * @since 2.1 - */ -public class TypeConverterRegistry { - - private static final Logger LOGGER = StatusLogger.getLogger(); - private static volatile TypeConverterRegistry INSTANCE; - private static final Object INSTANCE_LOCK = new Object(); - - private final ConcurrentMap> registry = new ConcurrentHashMap<>(); - - /** - * Gets the singleton instance of the TypeConverterRegistry. - * - * @return the singleton instance. - */ - public static TypeConverterRegistry getInstance() { - TypeConverterRegistry result = INSTANCE; - if (result == null) { - synchronized (INSTANCE_LOCK) { - result = INSTANCE; - if (result == null) { - INSTANCE = result = new TypeConverterRegistry(); - } - } - } - return result; - } - - /** - * Finds a {@link TypeConverter} for the given {@link Type}, falling back to an assignment-compatible TypeConverter - * if none exist for the given type. That is, if the given Type does not have a TypeConverter, but another Type - * which can be assigned to the given Type does have a TypeConverter, then that TypeConverter will be - * used and registered. - * - * @param type the Type to find a TypeConverter for (must not be {@code null}). - * @return a TypeConverter for the given Type. - * @throws UnknownFormatConversionException if no TypeConverter can be found for the given type. - */ - public TypeConverter findCompatibleConverter(final Type type) { - Objects.requireNonNull(type, "No type was provided"); - final TypeConverter primary = registry.get(type); - // cached type converters - if (primary != null) { - return primary; - } - // dynamic enum support - if (type instanceof Class) { - final Class clazz = (Class) type; - if (clazz.isEnum()) { - @SuppressWarnings({"unchecked","rawtypes"}) - final EnumConverter converter = new EnumConverter(clazz.asSubclass(Enum.class)); - registry.putIfAbsent(type, converter); - return converter; - } - } - // look for compatible converters - for (final Map.Entry> entry : registry.entrySet()) { - final Type key = entry.getKey(); - if (TypeUtil.isAssignable(type, key)) { - LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type); - final TypeConverter value = entry.getValue(); - registry.putIfAbsent(type, value); - return value; - } - } - throw new UnknownFormatConversionException(type.toString()); - } - - private TypeConverterRegistry() { - LOGGER.trace("TypeConverterRegistry initializing."); - final PluginManager manager = new PluginManager(TypeConverters.CATEGORY); - manager.collectPlugins(); - loadKnownTypeConverters(manager.getPlugins().values()); - registerPrimitiveTypes(); - } - - private void loadKnownTypeConverters(final Collection> knownTypes) { - for (final PluginType knownType : knownTypes) { - final Class clazz = knownType.getPluginClass(); - if (TypeConverter.class.isAssignableFrom(clazz)) { - @SuppressWarnings("rawtypes") - final Class pluginClass = clazz.asSubclass(TypeConverter.class); - final Type conversionType = getTypeConverterSupportedType(pluginClass); - final TypeConverter converter = ReflectionUtil.instantiate(pluginClass); - if (registry.putIfAbsent(conversionType, converter) != null) { - LOGGER.warn("Found a TypeConverter [{}] for type [{}] that already exists.", converter, - conversionType); - } - } - } - } - - private static Type getTypeConverterSupportedType(@SuppressWarnings("rawtypes") final Class typeConverterClass) { - for (final Type type : typeConverterClass.getGenericInterfaces()) { - if (type instanceof ParameterizedType) { - final ParameterizedType pType = (ParameterizedType) type; - if (TypeConverter.class.equals(pType.getRawType())) { - // TypeConverter has only one type argument (T), so return that - return pType.getActualTypeArguments()[0]; - } - } - } - return Void.TYPE; - } - - private void registerPrimitiveTypes() { - registerTypeAlias(Boolean.class, Boolean.TYPE); - registerTypeAlias(Byte.class, Byte.TYPE); - registerTypeAlias(Character.class, Character.TYPE); - registerTypeAlias(Double.class, Double.TYPE); - registerTypeAlias(Float.class, Float.TYPE); - registerTypeAlias(Integer.class, Integer.TYPE); - registerTypeAlias(Long.class, Long.TYPE); - registerTypeAlias(Short.class, Short.TYPE); - } - - private void registerTypeAlias(final Type knownType, final Type aliasType) { - registry.putIfAbsent(aliasType, registry.get(knownType)); - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java deleted file mode 100644 index dc833f03061..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.convert; - -import java.io.File; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.Provider; -import java.security.Security; -import java.util.UUID; -import java.util.regex.Pattern; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.appender.rolling.action.Duration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.util.CronExpression; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; - -/** - * Collection of basic TypeConverter implementations. May be used to register additional TypeConverters or find - * registered TypeConverters. - * - * @since 2.1 Moved to the {@code convert} package. - */ -public final class TypeConverters { - - /** - * The {@link Plugin#category() Plugin Category} to use for {@link TypeConverter} plugins. - * - * @since 2.1 - */ - public static final String CATEGORY = "TypeConverter"; - - /** - * Parses a {@link String} into a {@link BigDecimal}. - */ - @Plugin(name = "BigDecimal", category = CATEGORY) - public static class BigDecimalConverter implements TypeConverter { - @Override - public BigDecimal convert(final String s) { - return new BigDecimal(s); - } - } - - /** - * Parses a {@link String} into a {@link BigInteger}. - */ - @Plugin(name = "BigInteger", category = CATEGORY) - public static class BigIntegerConverter implements TypeConverter { - @Override - public BigInteger convert(final String s) { - return new BigInteger(s); - } - } - - /** - * Converts a {@link String} into a {@link Boolean}. - */ - @Plugin(name = "Boolean", category = CATEGORY) - public static class BooleanConverter implements TypeConverter { - @Override - public Boolean convert(final String s) { - return Boolean.valueOf(s); - } - } - - /** - * Converts a {@link String} into a {@code byte[]}. - * - * The supported formats are: - *
      - *
    • 0x0123456789ABCDEF
    • - *
    • Base64:ABase64String
    • - *
    • String using {@link Charset#defaultCharset()} [TODO Should this be UTF-8 instead?]
    • - *
    - */ - @Plugin(name = "ByteArray", category = CATEGORY) - public static class ByteArrayConverter implements TypeConverter { - - private static final String PREFIX_0x = "0x"; - private static final String PREFIX_BASE64 = "Base64:"; - - @Override - public byte[] convert(final String value) { - byte[] bytes; - if (value == null || value.isEmpty()) { - bytes = new byte[0]; - } else if (value.startsWith(PREFIX_BASE64)) { - final String lexicalXSDBase64Binary = value.substring(PREFIX_BASE64.length()); - bytes = Base64Converter.parseBase64Binary(lexicalXSDBase64Binary); - } else if (value.startsWith(PREFIX_0x)) { - final String lexicalXSDHexBinary = value.substring(PREFIX_0x.length()); - bytes = HexConverter.parseHexBinary(lexicalXSDHexBinary); - } else { - bytes = value.getBytes(Charset.defaultCharset()); - } - return bytes; - } - } - - /** - * Converts a {@link String} into a {@link Byte}. - */ - @Plugin(name = "Byte", category = CATEGORY) - public static class ByteConverter implements TypeConverter { - @Override - public Byte convert(final String s) { - return Byte.valueOf(s); - } - } - - /** - * Converts a {@link String} into a {@link Character}. - */ - @Plugin(name = "Character", category = CATEGORY) - public static class CharacterConverter implements TypeConverter { - @Override - public Character convert(final String s) { - if (s.length() != 1) { - throw new IllegalArgumentException("Character string must be of length 1: " + s); - } - return Character.valueOf(s.toCharArray()[0]); - } - } - - /** - * Converts a {@link String} into a {@code char[]}. - */ - @Plugin(name = "CharacterArray", category = CATEGORY) - public static class CharArrayConverter implements TypeConverter { - @Override - public char[] convert(final String s) { - return s.toCharArray(); - } - } - - /** - * Converts a {@link String} into a {@link Charset}. - */ - @Plugin(name = "Charset", category = CATEGORY) - public static class CharsetConverter implements TypeConverter { - @Override - public Charset convert(final String s) { - return Charset.forName(s); - } - } - - /** - * Converts a {@link String} into a {@link Class}. - */ - @Plugin(name = "Class", category = CATEGORY) - public static class ClassConverter implements TypeConverter> { - @Override - public Class convert(final String s) throws ClassNotFoundException { - switch (s.toLowerCase()) { - case "boolean": - return boolean.class; - case "byte": - return byte.class; - case "char": - return char.class; - case "double": - return double.class; - case "float": - return float.class; - case "int": - return int.class; - case "long": - return long.class; - case "short": - return short.class; - case "void": - return void.class; - default: - return LoaderUtil.loadClass(s); - } - - } - } - - @Plugin(name = "CronExpression", category = CATEGORY) - public static class CronExpressionConverter implements TypeConverter { - @Override - public CronExpression convert(final String s) throws Exception { - return new CronExpression(s); - } - } - - /** - * Converts a {@link String} into a {@link Double}. - */ - @Plugin(name = "Double", category = CATEGORY) - public static class DoubleConverter implements TypeConverter { - @Override - public Double convert(final String s) { - return Double.valueOf(s); - } - } - - /** - * Converts a {@link String} into a {@link Duration}. - * @since 2.5 - */ - @Plugin(name = "Duration", category = CATEGORY) - public static class DurationConverter implements TypeConverter { - @Override - public Duration convert(final String s) { - return Duration.parse(s); - } - } - - /** - * Converts a {@link String} into a {@link File}. - */ - @Plugin(name = "File", category = CATEGORY) - public static class FileConverter implements TypeConverter { - @Override - public File convert(final String s) { - return new File(s); - } - } - - /** - * Converts a {@link String} into a {@link Float}. - */ - @Plugin(name = "Float", category = CATEGORY) - public static class FloatConverter implements TypeConverter { - @Override - public Float convert(final String s) { - return Float.valueOf(s); - } - } - - /** - * Converts a {@link String} into an {@link InetAddress}. - */ - @Plugin(name = "InetAddress", category = CATEGORY) - public static class InetAddressConverter implements TypeConverter { - @Override - public InetAddress convert(final String s) throws Exception { - return InetAddress.getByName(s); - } - } - - /** - * Converts a {@link String} into a {@link Integer}. - */ - @Plugin(name = "Integer", category = CATEGORY) - public static class IntegerConverter implements TypeConverter { - @Override - public Integer convert(final String s) { - return Integer.valueOf(s); - } - } - - /** - * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names. - */ - @Plugin(name = "Level", category = CATEGORY) - public static class LevelConverter implements TypeConverter { - @Override - public Level convert(final String s) { - return Level.valueOf(s); - } - } - - /** - * Converts a {@link String} into a {@link Long}. - */ - @Plugin(name = "Long", category = CATEGORY) - public static class LongConverter implements TypeConverter { - @Override - public Long convert(final String s) { - return Long.valueOf(s); - } - } - - /** - * Converts a {@link String} into a {@link Path}. - * @since 2.8 - */ - @Plugin(name = "Path", category = CATEGORY) - public static class PathConverter implements TypeConverter { - @Override - public Path convert(final String s) throws Exception { - return Paths.get(s); - } - } - - /** - * Converts a {@link String} into a {@link Pattern}. - */ - @Plugin(name = "Pattern", category = CATEGORY) - public static class PatternConverter implements TypeConverter { - @Override - public Pattern convert(final String s) { - return Pattern.compile(s); - } - } - - /** - * Converts a {@link String} into a {@link Provider}. - */ - @Plugin(name = "SecurityProvider", category = CATEGORY) - public static class SecurityProviderConverter implements TypeConverter { - @Override - public Provider convert(final String s) { - return Security.getProvider(s); - } - } - - /** - * Converts a {@link String} into a {@link Short}. - */ - @Plugin(name = "Short", category = CATEGORY) - public static class ShortConverter implements TypeConverter { - @Override - public Short convert(final String s) { - return Short.valueOf(s); - } - } - - /** - * Returns the given {@link String}, no conversion takes place. - */ - @Plugin(name = "String", category = CATEGORY) - public static class StringConverter implements TypeConverter { - @Override - public String convert(final String s) { - return s; - } - } - - /** - * Converts a {@link String} into a {@link URI}. - */ - @Plugin(name = "URI", category = CATEGORY) - public static class UriConverter implements TypeConverter { - @Override - public URI convert(final String s) throws URISyntaxException { - return new URI(s); - } - } - - /** - * Converts a {@link String} into a {@link URL}. - */ - @Plugin(name = "URL", category = CATEGORY) - public static class UrlConverter implements TypeConverter { - @Override - public URL convert(final String s) throws MalformedURLException { - return new URL(s); - } - } - - /** - * Converts a {@link String} into a {@link UUID}. - * @since 2.8 - */ - @Plugin(name = "UUID", category = CATEGORY) - public static class UuidConverter implements TypeConverter { - @Override - public UUID convert(final String s) throws Exception { - return UUID.fromString(s); - } - } - - /** - * Converts a String to a given class if a TypeConverter is available for that class. Falls back to the provided - * default value if the conversion is unsuccessful. However, if the default value is also invalid, then - * {@code null} is returned (along with a nasty status log message). - * - * @param s - * the string to convert - * @param clazz - * the class to try to convert the string to - * @param defaultValue - * the fallback object to use if the conversion is unsuccessful - * @return the converted object which may be {@code null} if the string is invalid for the given type - * @throws NullPointerException - * if {@code clazz} is {@code null} - * @throws IllegalArgumentException - * if no TypeConverter exists for the given class - */ - public static T convert(final String s, final Class clazz, final Object defaultValue) { - @SuppressWarnings("unchecked") - final TypeConverter converter = (TypeConverter) TypeConverterRegistry.getInstance().findCompatibleConverter(clazz); - if (s == null) { - // don't debug print here, resulting output is hard to understand - // LOGGER.debug("Null string given to convert. Using default [{}].", defaultValue); - return parseDefaultValue(converter, defaultValue); - } - try { - return converter.convert(s); - } catch (final Exception e) { - LOGGER.warn("Error while converting string [{}] to type [{}]. Using default value [{}].", s, clazz, - defaultValue, e); - return parseDefaultValue(converter, defaultValue); - } - } - - @SuppressWarnings("unchecked") - private static T parseDefaultValue(final TypeConverter converter, final Object defaultValue) { - if (defaultValue == null) { - return null; - } - if (!(defaultValue instanceof String)) { - return (T) defaultValue; - } - try { - return converter.convert((String) defaultValue); - } catch (final Exception e) { - LOGGER.debug("Can't parse default value [{}] for type [{}].", defaultValue, converter.getClass(), e); - return null; - } - } - - private static final Logger LOGGER = StatusLogger.getLogger(); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java index 5e749150046..0224a3e153a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java @@ -18,4 +18,4 @@ /** * Annotations for Log4j 2 plugins. */ -package org.apache.logging.log4j.core.config.plugins; \ No newline at end of file +package org.apache.logging.log4j.core.config.plugins; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java deleted file mode 100644 index 2fd4160ca80..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.processor; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.net.URL; -import java.util.Enumeration; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * - */ -public class PluginCache { - private final Map> categories = - new LinkedHashMap<>(); - - /** - * Returns all categories of plugins in this cache. - * - * @return all categories of plugins in this cache. - * @since 2.1 - */ - public Map> getAllCategories() { - return categories; - } - - /** - * Gets or creates a category of plugins. - * - * @param category name of category to look up. - * @return plugin mapping of names to plugin entries. - */ - public Map getCategory(final String category) { - final String key = category.toLowerCase(); - if (!categories.containsKey(key)) { - categories.put(key, new LinkedHashMap()); - } - return categories.get(key); - } - - /** - * Stores the plugin cache to a given OutputStream. - * - * @param os destination to save cache to. - * @throws IOException if an I/O exception occurs. - */ - // NOTE: if this file format is to be changed, the filename should change and this format should still be readable - public void writeCache(final OutputStream os) throws IOException { - try (final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(os))) { - // See PluginManager.readFromCacheFiles for the corresponding decoder. Format may not be changed - // without breaking existing Log4j2Plugins.dat files. - out.writeInt(categories.size()); - for (final Map.Entry> category : categories.entrySet()) { - out.writeUTF(category.getKey()); - final Map m = category.getValue(); - out.writeInt(m.size()); - for (final Map.Entry entry : m.entrySet()) { - final PluginEntry plugin = entry.getValue(); - out.writeUTF(plugin.getKey()); - out.writeUTF(plugin.getClassName()); - out.writeUTF(plugin.getName()); - out.writeBoolean(plugin.isPrintable()); - out.writeBoolean(plugin.isDefer()); - } - } - } - } - - /** - * Loads and merges all the Log4j plugin cache files specified. Usually, this is obtained via a ClassLoader. - * - * @param resources URLs to all the desired plugin cache files to load. - * @throws IOException if an I/O exception occurs. - */ - public void loadCacheFiles(final Enumeration resources) throws IOException { - categories.clear(); - while (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - try (final DataInputStream in = new DataInputStream(new BufferedInputStream(url.openStream()))) { - final int count = in.readInt(); - for (int i = 0; i < count; i++) { - final String category = in.readUTF(); - final Map m = getCategory(category); - final int entries = in.readInt(); - for (int j = 0; j < entries; j++) { - final PluginEntry entry = new PluginEntry(); - entry.setKey(in.readUTF()); - entry.setClassName(in.readUTF()); - entry.setName(in.readUTF()); - entry.setPrintable(in.readBoolean()); - entry.setDefer(in.readBoolean()); - entry.setCategory(category); - if (!m.containsKey(entry.getKey())) { - m.put(entry.getKey(), entry); - } - } - } - } - } - } - - /** - * Gets the number of plugin categories registered. - * - * @return number of plugin categories in cache. - */ - public int size() { - return categories.size(); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginEntry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginEntry.java deleted file mode 100644 index dd43601077d..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginEntry.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.processor; - -import java.io.Serializable; - -/** - * Memento object for storing a plugin entry to a cache file. - */ -public class PluginEntry implements Serializable { - private static final long serialVersionUID = 1L; - - private String key; - private String className; - private String name; - private boolean printable; - private boolean defer; - private transient String category; - - public String getKey() { - return key; - } - - public void setKey(final String key) { - this.key = key; - } - - public String getClassName() { - return className; - } - - public void setClassName(final String className) { - this.className = className; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public boolean isPrintable() { - return printable; - } - - public void setPrintable(final boolean printable) { - this.printable = printable; - } - - public boolean isDefer() { - return defer; - } - - public void setDefer(final boolean defer) { - this.defer = defer; - } - - public String getCategory() { - return category; - } - - public void setCategory(final String category) { - this.category = category; - } - - @Override - public String toString() { - return "PluginEntry [key=" + key + ", className=" + className + ", name=" + name + ", printable=" + printable - + ", defer=" + defer + ", category=" + category + "]"; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java deleted file mode 100644 index 2f3b53f58cb..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.processor; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementVisitor; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor7; -import javax.tools.Diagnostic.Kind; -import javax.tools.FileObject; -import javax.tools.StandardLocation; - -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.util.Strings; - -/** - * Annotation processor for pre-scanning Log4j 2 plugins. - */ -@SupportedAnnotationTypes("org.apache.logging.log4j.core.config.plugins.*") -public class PluginProcessor extends AbstractProcessor { - - // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing - - /** - * The location of the plugin cache data file. This file is written to by this processor, and read from by - * {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}. - */ - public static final String PLUGIN_CACHE_FILE = - "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat"; - - private final PluginCache pluginCache = new PluginCache(); - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } - - @Override - public boolean process(final Set annotations, final RoundEnvironment roundEnv) { - System.out.println("Processing annotations"); - try { - final Set elements = roundEnv.getElementsAnnotatedWith(Plugin.class); - if (elements.isEmpty()) { - System.out.println("No elements to process"); - return false; - } - collectPlugins(elements); - writeCacheFile(elements.toArray(new Element[elements.size()])); - System.out.println("Annotations processed"); - return true; - } catch (final IOException e) { - e.printStackTrace(); - error(e.getMessage()); - return false; - } catch (final Exception ex) { - ex.printStackTrace(); - error(ex.getMessage()); - return false; - } - } - - private void error(final CharSequence message) { - processingEnv.getMessager().printMessage(Kind.ERROR, message); - } - - private void collectPlugins(final Iterable elements) { - final Elements elementUtils = processingEnv.getElementUtils(); - final ElementVisitor pluginVisitor = new PluginElementVisitor(elementUtils); - final ElementVisitor, Plugin> pluginAliasesVisitor = new PluginAliasesElementVisitor( - elementUtils); - for (final Element element : elements) { - final Plugin plugin = element.getAnnotation(Plugin.class); - if (plugin == null) { - continue; - } - final PluginEntry entry = element.accept(pluginVisitor, plugin); - final Map category = pluginCache.getCategory(entry.getCategory()); - category.put(entry.getKey(), entry); - final Collection entries = element.accept(pluginAliasesVisitor, plugin); - for (final PluginEntry pluginEntry : entries) { - category.put(pluginEntry.getKey(), pluginEntry); - } - } - } - - private void writeCacheFile(final Element... elements) throws IOException { - final FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, Strings.EMPTY, - PLUGIN_CACHE_FILE, elements); - try (final OutputStream out = fileObject.openOutputStream()) { - pluginCache.writeCache(out); - } - } - - /** - * ElementVisitor to scan the Plugin annotation. - */ - private static class PluginElementVisitor extends SimpleElementVisitor7 { - - private final Elements elements; - - private PluginElementVisitor(final Elements elements) { - this.elements = elements; - } - - @Override - public PluginEntry visitType(final TypeElement e, final Plugin plugin) { - Objects.requireNonNull(plugin, "Plugin annotation is null."); - final PluginEntry entry = new PluginEntry(); - entry.setKey(plugin.name().toLowerCase(Locale.US)); - entry.setClassName(elements.getBinaryName(e).toString()); - entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? plugin.name() : plugin.elementType()); - entry.setPrintable(plugin.printObject()); - entry.setDefer(plugin.deferChildren()); - entry.setCategory(plugin.category()); - return entry; - } - } - - /** - * ElementVisitor to scan the PluginAliases annotation. - */ - private static class PluginAliasesElementVisitor extends SimpleElementVisitor7, Plugin> { - - private final Elements elements; - - private PluginAliasesElementVisitor(final Elements elements) { - super(Collections. emptyList()); - this.elements = elements; - } - - @Override - public Collection visitType(final TypeElement e, final Plugin plugin) { - final PluginAliases aliases = e.getAnnotation(PluginAliases.class); - if (aliases == null) { - return DEFAULT_VALUE; - } - final Collection entries = new ArrayList<>(aliases.value().length); - for (final String alias : aliases.value()) { - final PluginEntry entry = new PluginEntry(); - entry.setKey(alias.toLowerCase(Locale.US)); - entry.setClassName(elements.getBinaryName(e).toString()); - entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? alias : plugin.elementType()); - entry.setPrintable(plugin.printObject()); - entry.setDefer(plugin.deferChildren()); - entry.setCategory(plugin.category()); - entries.add(entry); - } - return entries; - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java deleted file mode 100644 index 4f6ddda0b1f..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * Java annotation processor for pre-scanning Log4j 2 plugins. This is provided as an alternative to using the - * executable {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager} class in your build process. - */ -package org.apache.logging.log4j.core.config.plugins.processor; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginAliasesProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginAliasesProvider.java new file mode 100644 index 00000000000..6ca1276dcea --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginAliasesProvider.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.plugins.util; + +import org.apache.logging.log4j.core.config.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider; + +import java.util.Collection; +import java.util.List; + +public class PluginAliasesProvider implements AnnotatedElementAliasesProvider { + @Override + public Collection getAliases(final PluginAliases annotation) { + return List.of(annotation.value()); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginAttributeNameProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginAttributeNameProvider.java new file mode 100644 index 00000000000..33e3b8945d6 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginAttributeNameProvider.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.plugins.util; + +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider; +import org.apache.logging.log4j.util.Strings; + +import java.util.Optional; + +public class PluginAttributeNameProvider implements AnnotatedElementNameProvider { + @Override + public Optional getSpecifiedName(final PluginAttribute annotation) { + return Strings.trimToOptional(annotation.value()); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java deleted file mode 100644 index ebaabe90155..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.util; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.ConfigurationException; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator; -import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor; -import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors; -import org.apache.logging.log4j.core.util.Builder; -import org.apache.logging.log4j.core.util.ReflectionUtil; -import org.apache.logging.log4j.core.util.TypeUtil; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.StringBuilders; - -/** - * Builder class to instantiate and configure a Plugin object using a PluginFactory method or PluginBuilderFactory - * builder class. - */ -public class PluginBuilder implements Builder { - - private static final Logger LOGGER = StatusLogger.getLogger(); - - private final PluginType pluginType; - private final Class clazz; - - private Configuration configuration; - private Node node; - private LogEvent event; - - /** - * Constructs a PluginBuilder for a given PluginType. - * - * @param pluginType type of plugin to configure - */ - public PluginBuilder(final PluginType pluginType) { - this.pluginType = pluginType; - this.clazz = pluginType.getPluginClass(); - } - - /** - * Specifies the Configuration to use for constructing the plugin instance. - * - * @param configuration the configuration to use. - * @return {@code this} - */ - public PluginBuilder withConfiguration(final Configuration configuration) { - this.configuration = configuration; - return this; - } - - /** - * Specifies the Node corresponding to the plugin object that will be created. - * - * @param node the plugin configuration node to use. - * @return {@code this} - */ - public PluginBuilder withConfigurationNode(final Node node) { - this.node = node; - return this; - } - - /** - * Specifies the LogEvent that may be used to provide extra context for string substitutions. - * - * @param event the event to use for extra information. - * @return {@code this} - */ - public PluginBuilder forLogEvent(final LogEvent event) { - this.event = event; - return this; - } - - /** - * Builds the plugin object. - * - * @return the plugin object or {@code null} if there was a problem creating it. - */ - @Override - public Object build() { - verify(); - // first try to use a builder class if one is available - try { - LOGGER.debug("Building Plugin[name={}, class={}].", pluginType.getElementName(), - pluginType.getPluginClass().getName()); - final Builder builder = createBuilder(this.clazz); - if (builder != null) { - injectFields(builder); - return builder.build(); - } - } catch (final ConfigurationException e) { // LOG4J2-1908 - LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e); - return null; // no point in trying the factory method - } catch (final Exception e) { - LOGGER.error("Could not create plugin of type {} for element {}: {}", - this.clazz, node.getName(), - (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e); - } - // or fall back to factory method if no builder class is available - try { - final Method factory = findFactoryMethod(this.clazz); - final Object[] params = generateParameters(factory); - return factory.invoke(null, params); - } catch (final Exception e) { - LOGGER.error("Unable to invoke factory method in {} for element {}: {}", - this.clazz, this.node.getName(), - (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e); - return null; - } - } - - private void verify() { - Objects.requireNonNull(this.configuration, "No Configuration object was set."); - Objects.requireNonNull(this.node, "No Node object was set."); - } - - private static Builder createBuilder(final Class clazz) - throws InvocationTargetException, IllegalAccessException { - for (final Method method : clazz.getDeclaredMethods()) { - if (method.isAnnotationPresent(PluginBuilderFactory.class) && - Modifier.isStatic(method.getModifiers()) && - TypeUtil.isAssignable(Builder.class, method.getReturnType())) { - ReflectionUtil.makeAccessible(method); - return (Builder) method.invoke(null); - } - } - return null; - } - - private void injectFields(final Builder builder) throws IllegalAccessException { - final List fields = TypeUtil.getAllDeclaredFields(builder.getClass()); - AccessibleObject.setAccessible(fields.toArray(new Field[] {}), true); - final StringBuilder log = new StringBuilder(); - boolean invalid = false; - for (final Field field : fields) { - log.append(log.length() == 0 ? simpleName(builder) + "(" : ", "); - final Annotation[] annotations = field.getDeclaredAnnotations(); - final String[] aliases = extractPluginAliases(annotations); - for (final Annotation a : annotations) { - if (a instanceof PluginAliases) { - continue; // already processed - } - final PluginVisitor visitor = - PluginVisitors.findVisitor(a.annotationType()); - if (visitor != null) { - final Object value = visitor.setAliases(aliases) - .setAnnotation(a) - .setConversionType(field.getType()) - .setStrSubstitutor(configuration.getStrSubstitutor()) - .setMember(field) - .visit(configuration, node, event, log); - // don't overwrite default values if the visitor gives us no value to inject - if (value != null) { - field.set(builder, value); - } - } - } - final Collection> validators = - ConstraintValidators.findValidators(annotations); - final Object value = field.get(builder); - for (final ConstraintValidator validator : validators) { - if (!validator.isValid(field.getName(), value)) { - invalid = true; - } - } - } - log.append(log.length() == 0 ? builder.getClass().getSimpleName() + "()" : ")"); - LOGGER.debug(log.toString()); - if (invalid) { - throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid"); - } - checkForRemainingAttributes(); - verifyNodeChildrenUsed(); - } - - /** - * {@code object.getClass().getSimpleName()} returns {@code Builder}, when we want {@code PatternLayout$Builder}. - */ - private static String simpleName(final Object object) { - if (object == null) { - return "null"; - } - final String cls = object.getClass().getName(); - final int index = cls.lastIndexOf('.'); - return index < 0 ? cls : cls.substring(index + 1); - } - - private static Method findFactoryMethod(final Class clazz) { - for (final Method method : clazz.getDeclaredMethods()) { - if (method.isAnnotationPresent(PluginFactory.class) && - Modifier.isStatic(method.getModifiers())) { - ReflectionUtil.makeAccessible(method); - return method; - } - } - throw new IllegalStateException("No factory method found for class " + clazz.getName()); - } - - private Object[] generateParameters(final Method factory) { - final StringBuilder log = new StringBuilder(); - final Class[] types = factory.getParameterTypes(); - final Annotation[][] annotations = factory.getParameterAnnotations(); - final Object[] args = new Object[annotations.length]; - boolean invalid = false; - for (int i = 0; i < annotations.length; i++) { - log.append(log.length() == 0 ? factory.getName() + "(" : ", "); - final String[] aliases = extractPluginAliases(annotations[i]); - for (final Annotation a : annotations[i]) { - if (a instanceof PluginAliases) { - continue; // already processed - } - final PluginVisitor visitor = PluginVisitors.findVisitor( - a.annotationType()); - if (visitor != null) { - final Object value = visitor.setAliases(aliases) - .setAnnotation(a) - .setConversionType(types[i]) - .setStrSubstitutor(configuration.getStrSubstitutor()) - .setMember(factory) - .visit(configuration, node, event, log); - // don't overwrite existing values if the visitor gives us no value to inject - if (value != null) { - args[i] = value; - } - } - } - final Collection> validators = - ConstraintValidators.findValidators(annotations[i]); - final Object value = args[i]; - final String argName = "arg[" + i + "](" + simpleName(value) + ")"; - for (final ConstraintValidator validator : validators) { - if (!validator.isValid(argName, value)) { - invalid = true; - } - } - } - log.append(log.length() == 0 ? factory.getName() + "()" : ")"); - checkForRemainingAttributes(); - verifyNodeChildrenUsed(); - LOGGER.debug(log.toString()); - if (invalid) { - throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid"); - } - return args; - } - - private static String[] extractPluginAliases(final Annotation... parmTypes) { - String[] aliases = null; - for (final Annotation a : parmTypes) { - if (a instanceof PluginAliases) { - aliases = ((PluginAliases) a).value(); - } - } - return aliases; - } - - private void checkForRemainingAttributes() { - final Map attrs = node.getAttributes(); - if (!attrs.isEmpty()) { - final StringBuilder sb = new StringBuilder(); - for (final String key : attrs.keySet()) { - if (sb.length() == 0) { - sb.append(node.getName()); - sb.append(" contains "); - if (attrs.size() == 1) { - sb.append("an invalid element or attribute "); - } else { - sb.append("invalid attributes "); - } - } else { - sb.append(", "); - } - StringBuilders.appendDqValue(sb, key); - } - LOGGER.error(sb.toString()); - } - } - - private void verifyNodeChildrenUsed() { - final List children = node.getChildren(); - if (!(pluginType.isDeferChildren() || children.isEmpty())) { - for (final Node child : children) { - final String nodeType = node.getType().getElementName(); - final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + ' ' + node.getName(); - LOGGER.error("{} has no parameter that matches element {}", start, child.getName()); - } - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilderAttributeNameProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilderAttributeNameProvider.java new file mode 100644 index 00000000000..9c372329424 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilderAttributeNameProvider.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.plugins.util; + +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider; +import org.apache.logging.log4j.util.Strings; + +import java.util.Optional; + +public class PluginBuilderAttributeNameProvider implements AnnotatedElementNameProvider { + @Override + public Optional getSpecifiedName(final PluginBuilderAttribute annotation) { + return Strings.trimToOptional(annotation.value()); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginElementNameProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginElementNameProvider.java new file mode 100644 index 00000000000..e450895b9b1 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginElementNameProvider.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.plugins.util; + +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider; +import org.apache.logging.log4j.util.Strings; + +import java.util.Optional; + +public class PluginElementNameProvider implements AnnotatedElementNameProvider { + @Override + public Optional getSpecifiedName(final PluginElement annotation) { + return Strings.trimToOptional(annotation.value()); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java deleted file mode 100644 index 704ffd12cf2..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.util; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Strings; - -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * Loads and manages all the plugins. - */ -public class PluginManager { - - private static final CopyOnWriteArrayList PACKAGES = new CopyOnWriteArrayList<>(); - private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core"; - - private static final Logger LOGGER = StatusLogger.getLogger(); - - private Map> plugins = new HashMap<>(); - private final String category; - - /** - * Constructs a PluginManager for the plugin category name given. - * - * @param category The plugin category name. - */ - public PluginManager(final String category) { - this.category = category; - } - - /** - * Process annotated plugins. - * - * @deprecated Use {@link org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor} instead. To do so, - * simply include {@code log4j-core} in your dependencies and make sure annotation processing is not - * disabled. By default, supported Java compilers will automatically use that plugin processor provided - * {@code log4j-core} is on the classpath. - */ - @Deprecated - // use PluginProcessor instead - public static void main(final String[] args) { - System.err.println("ERROR: this tool is superseded by the annotation processor included in log4j-core."); - System.err.println("If the annotation processor does not work for you, please see the manual page:"); - System.err.println("http://logging.apache.org/log4j/2.x/manual/configuration.html#ConfigurationSyntax"); - System.exit(-1); - } - - /** - * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected. - * - * @param p The package name. Ignored if {@code null} or empty. - */ - public static void addPackage(final String p) { - if (Strings.isBlank(p)) { - return; - } - PACKAGES.addIfAbsent(p); - } - - /** - * Adds a list of package names to be scanned for plugins. Convenience method for {@link #addPackage(String)}. - * - * @param packages collection of package names to add. Empty and null package names are ignored. - */ - public static void addPackages(final Collection packages) { - for (final String pkg : packages) { - if (Strings.isNotBlank(pkg)) { - PACKAGES.addIfAbsent(pkg); - } - } - } - - /** - * Returns the type of a specified plugin. - * - * @param name The name of the plugin. - * @return The plugin's type. - */ - public PluginType getPluginType(final String name) { - return plugins.get(name.toLowerCase()); - } - - /** - * Returns all the matching plugins. - * - * @return A Map containing the name of the plugin and its type. - */ - public Map> getPlugins() { - return plugins; - } - - /** - * Locates all the plugins. - */ - public void collectPlugins() { - collectPlugins(null); - } - - /** - * Locates all the plugins including search of specific packages. Warns about name collisions. - * - * @param packages the list of packages to scan for plugins - * @since 2.1 - */ - public void collectPlugins(final List packages) { - final String categoryLowerCase = category.toLowerCase(); - final Map> newPlugins = new LinkedHashMap<>(); - - // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH - Map>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader(); - if (builtInPlugins.isEmpty()) { - // If we didn't find any plugins above, someone must have messed with the log4j-core.jar. - // Search the standard package in the hopes we can find our core plugins. - builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES); - } - mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase)); - - // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles - for (final Map>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) { - mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase)); - } - - // Next iterate any packages passed to the static addPackage method. - for (final String pkg : PACKAGES) { - mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); - } - // Finally iterate any packages provided in the configuration (note these can be changed at runtime). - if (packages != null) { - for (final String pkg : packages) { - mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase)); - } - } - - LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size()); - - plugins = newPlugins; - } - - private static void mergeByName(final Map> newPlugins, final List> plugins) { - if (plugins == null) { - return; - } - for (final PluginType pluginType : plugins) { - final String key = pluginType.getKey(); - final PluginType existing = newPlugins.get(key); - if (existing == null) { - newPlugins.put(key, pluginType); - } else if (!existing.getPluginClass().equals(pluginType.getPluginClass())) { - LOGGER.warn("Plugin [{}] is already mapped to {}, ignoring {}", - key, existing.getPluginClass(), pluginType.getPluginClass()); - } - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java deleted file mode 100644 index a657f1affe6..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.util; - -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.processor.PluginCache; -import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; -import org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor; -import org.apache.logging.log4j.core.util.Loader; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Strings; - -/** - * Registry singleton for PluginType maps partitioned by source type and then by category names. - */ -public class PluginRegistry { - - private static final Logger LOGGER = StatusLogger.getLogger(); - - private static volatile PluginRegistry INSTANCE; - private static final Object INSTANCE_LOCK = new Object(); - - /** - * Contains plugins found in Log4j2Plugins.dat cache files in the main CLASSPATH. - */ - private final AtomicReference>>> pluginsByCategoryRef = - new AtomicReference<>(); - - /** - * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles. - */ - private final ConcurrentMap>>> pluginsByCategoryByBundleId = - new ConcurrentHashMap<>(); - - /** - * Contains plugins found by searching for annotated classes at runtime. - */ - private final ConcurrentMap>>> pluginsByCategoryByPackage = - new ConcurrentHashMap<>(); - - private PluginRegistry() { - } - - /** - * Returns the global PluginRegistry instance. - * - * @return the global PluginRegistry instance. - * @since 2.1 - */ - public static PluginRegistry getInstance() { - PluginRegistry result = INSTANCE; - if (result == null) { - synchronized (INSTANCE_LOCK) { - result = INSTANCE; - if (result == null) { - INSTANCE = result = new PluginRegistry(); - } - } - } - return result; - } - - /** - * Resets the registry to an empty state. - */ - public void clear() { - pluginsByCategoryRef.set(null); - pluginsByCategoryByPackage.clear(); - pluginsByCategoryByBundleId.clear(); - } - - /** - * @since 2.1 - */ - public Map>>> getPluginsByCategoryByBundleId() { - return pluginsByCategoryByBundleId; - } - - /** - * @since 2.1 - */ - public Map>> loadFromMainClassLoader() { - final Map>> existing = pluginsByCategoryRef.get(); - if (existing != null) { - // already loaded - return existing; - } - final Map>> newPluginsByCategory = decodeCacheFiles(Loader.getClassLoader()); - - // Note multiple threads could be calling this method concurrently. Both will do the work, - // but only one will be allowed to store the result in the AtomicReference. - // Return the map produced by whichever thread won the race, so all callers will get the same result. - if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) { - return newPluginsByCategory; - } - return pluginsByCategoryRef.get(); - } - - /** - * @since 2.1 - */ - public void clearBundlePlugins(final long bundleId) { - pluginsByCategoryByBundleId.remove(bundleId); - } - - /** - * @since 2.1 - */ - public Map>> loadFromBundle(final long bundleId, final ClassLoader loader) { - Map>> existing = pluginsByCategoryByBundleId.get(bundleId); - if (existing != null) { - // already loaded from this classloader - return existing; - } - final Map>> newPluginsByCategory = decodeCacheFiles(loader); - - // Note multiple threads could be calling this method concurrently. Both will do the work, - // but only one will be allowed to store the result in the outer map. - // Return the inner map produced by whichever thread won the race, so all callers will get the same result. - existing = pluginsByCategoryByBundleId.putIfAbsent(bundleId, newPluginsByCategory); - if (existing != null) { - return existing; - } - return newPluginsByCategory; - } - - private Map>> decodeCacheFiles(final ClassLoader loader) { - final long startTime = System.nanoTime(); - final PluginCache cache = new PluginCache(); - try { - final Enumeration resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE); - if (resources == null) { - LOGGER.info("Plugin preloads not available from class loader {}", loader); - } else { - cache.loadCacheFiles(resources); - } - } catch (final IOException ioe) { - LOGGER.warn("Unable to preload plugins", ioe); - } - final Map>> newPluginsByCategory = new HashMap<>(); - int pluginCount = 0; - for (final Map.Entry> outer : cache.getAllCategories().entrySet()) { - final String categoryLowerCase = outer.getKey(); - final List> types = new ArrayList<>(outer.getValue().size()); - newPluginsByCategory.put(categoryLowerCase, types); - for (final Map.Entry inner : outer.getValue().entrySet()) { - final PluginEntry entry = inner.getValue(); - final String className = entry.getClassName(); - try { - final Class clazz = loader.loadClass(className); - final PluginType type = new PluginType<>(entry, clazz, entry.getName()); - types.add(type); - ++pluginCount; - } catch (final ClassNotFoundException e) { - LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e); - } catch (final VerifyError e) { - LOGGER.info("Plugin [{}] could not be loaded due to verification error.", className, e); - } - } - } - - final long endTime = System.nanoTime(); - final DecimalFormat numFormat = new DecimalFormat("#0.000000"); - final double seconds = (endTime - startTime) * 1e-9; - LOGGER.debug("Took {} seconds to load {} plugins from {}", - numFormat.format(seconds), pluginCount, loader); - return newPluginsByCategory; - } - - /** - * @since 2.1 - */ - public Map>> loadFromPackage(final String pkg) { - if (Strings.isBlank(pkg)) { - // happens when splitting an empty string - return Collections.emptyMap(); - } - Map>> existing = pluginsByCategoryByPackage.get(pkg); - if (existing != null) { - // already loaded this package - return existing; - } - - final long startTime = System.nanoTime(); - final ResolverUtil resolver = new ResolverUtil(); - final ClassLoader classLoader = Loader.getClassLoader(); - if (classLoader != null) { - resolver.setClassLoader(classLoader); - } - resolver.findInPackage(new PluginTest(), pkg); - - final Map>> newPluginsByCategory = new HashMap<>(); - for (final Class clazz : resolver.getClasses()) { - final Plugin plugin = clazz.getAnnotation(Plugin.class); - final String categoryLowerCase = plugin.category().toLowerCase(); - List> list = newPluginsByCategory.get(categoryLowerCase); - if (list == null) { - newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<>()); - } - final PluginEntry mainEntry = new PluginEntry(); - final String mainElementName = plugin.elementType().equals( - Plugin.EMPTY) ? plugin.name() : plugin.elementType(); - mainEntry.setKey(plugin.name().toLowerCase()); - mainEntry.setName(plugin.name()); - mainEntry.setCategory(plugin.category()); - mainEntry.setClassName(clazz.getName()); - mainEntry.setPrintable(plugin.printObject()); - mainEntry.setDefer(plugin.deferChildren()); - final PluginType mainType = new PluginType<>(mainEntry, clazz, mainElementName); - list.add(mainType); - final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class); - if (pluginAliases != null) { - for (final String alias : pluginAliases.value()) { - final PluginEntry aliasEntry = new PluginEntry(); - final String aliasElementName = plugin.elementType().equals( - Plugin.EMPTY) ? alias.trim() : plugin.elementType(); - aliasEntry.setKey(alias.trim().toLowerCase()); - aliasEntry.setName(plugin.name()); - aliasEntry.setCategory(plugin.category()); - aliasEntry.setClassName(clazz.getName()); - aliasEntry.setPrintable(plugin.printObject()); - aliasEntry.setDefer(plugin.deferChildren()); - final PluginType aliasType = new PluginType<>(aliasEntry, clazz, aliasElementName); - list.add(aliasType); - } - } - } - - final long endTime = System.nanoTime(); - final DecimalFormat numFormat = new DecimalFormat("#0.000000"); - final double seconds = (endTime - startTime) * 1e-9; - LOGGER.debug("Took {} seconds to load {} plugins from package {}", - numFormat.format(seconds), resolver.getClasses().size(), pkg); - - // Note multiple threads could be calling this method concurrently. Both will do the work, - // but only one will be allowed to store the result in the outer map. - // Return the inner map produced by whichever thread won the race, so all callers will get the same result. - existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory); - if (existing != null) { - return existing; - } - return newPluginsByCategory; - } - - /** - * A Test that checks to see if each class is annotated with the 'Plugin' annotation. If it - * is, then the test returns true, otherwise false. - * - * @since 2.1 - */ - public static class PluginTest implements ResolverUtil.Test { - @Override - public boolean matches(final Class type) { - return type != null && type.isAnnotationPresent(Plugin.class); - } - - @Override - public String toString() { - return "annotated with @" + Plugin.class.getSimpleName(); - } - - @Override - public boolean matches(final URI resource) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean doesMatchClass() { - return true; - } - - @Override - public boolean doesMatchResource() { - return false; - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java deleted file mode 100644 index cc6bc66ffbe..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config.plugins.util; - - -import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry; - -/** - * Plugin Descriptor. This is a memento object for Plugin annotations paired to their annotated classes. - * - * @param The plug-in class, which can be any kind of class. - * @see org.apache.logging.log4j.core.config.plugins.Plugin - */ -public class PluginType { - - private final PluginEntry pluginEntry; - private final Class pluginClass; - private final String elementName; - - /** - * @since 2.1 - */ - public PluginType(final PluginEntry pluginEntry, final Class pluginClass, final String elementName) { - this.pluginEntry = pluginEntry; - this.pluginClass = pluginClass; - this.elementName = elementName; - } - - public Class getPluginClass() { - return this.pluginClass; - } - - public String getElementName() { - return this.elementName; - } - - /** - * @since 2.1 - */ - public String getKey() { - return this.pluginEntry.getKey(); - } - - public boolean isObjectPrintable() { - return this.pluginEntry.isPrintable(); - } - - public boolean isDeferChildren() { - return this.pluginEntry.isDefer(); - } - - /** - * @since 2.1 - */ - public String getCategory() { - return this.pluginEntry.getCategory(); - } - - @Override - public String toString() { - return "PluginType [pluginClass=" + pluginClass + - ", key=" + pluginEntry.getKey() + - ", elementName=" + pluginEntry.getName() + - ", isObjectPrintable=" + pluginEntry.isPrintable() + - ", isDeferChildren==" + pluginEntry.isDefer() + - ", category=" + pluginEntry.getCategory() + - "]"; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginValueNameProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginValueNameProvider.java new file mode 100644 index 00000000000..d62959fc42b --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginValueNameProvider.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.plugins.util; + +import org.apache.logging.log4j.core.config.plugins.PluginValue; +import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider; +import org.apache.logging.log4j.util.Strings; + +import java.util.Optional; + +@SuppressWarnings("deprecation") +public class PluginValueNameProvider implements AnnotatedElementNameProvider { + @Override + public Optional getSpecifiedName(final PluginValue annotation) { + return Strings.trimToOptional(annotation.value()); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidators.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidators.java deleted file mode 100644 index 374c8ec406a..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidators.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config.plugins.validation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; - -import org.apache.logging.log4j.core.util.ReflectionUtil; - -/** - * Utility class to locate an appropriate {@link ConstraintValidator} implementation for an annotation. - * - * @since 2.1 - */ -public final class ConstraintValidators { - - private ConstraintValidators() { - } - - /** - * Finds all relevant {@link ConstraintValidator} objects from an array of annotations. All validators will be - * {@link ConstraintValidator#initialize(java.lang.annotation.Annotation) initialized} before being returned. - * - * @param annotations the annotations to find constraint validators for - * @return a collection of ConstraintValidators for the given annotations - */ - public static Collection> findValidators(final Annotation... annotations) { - final Collection> validators = - new ArrayList<>(); - for (final Annotation annotation : annotations) { - final Class type = annotation.annotationType(); - if (type.isAnnotationPresent(Constraint.class)) { - final ConstraintValidator validator = getValidator(annotation, type); - if (validator != null) { - validators.add(validator); - } - } - } - return validators; - } - - private static ConstraintValidator getValidator(final A annotation, - final Class type) { - final Constraint constraint = type.getAnnotation(Constraint.class); - final Class> validatorClass = constraint.value(); - if (type.equals(getConstraintValidatorAnnotationType(validatorClass))) { - @SuppressWarnings("unchecked") // I don't think we could be any more thorough in validation here - final ConstraintValidator validator = (ConstraintValidator) - ReflectionUtil.instantiate(validatorClass); - validator.initialize(annotation); - return validator; - } - return null; - } - - private static Type getConstraintValidatorAnnotationType(final Class> type) { - for (final Type parentType : type.getGenericInterfaces()) { - if (parentType instanceof ParameterizedType) { - final ParameterizedType parameterizedType = (ParameterizedType) parentType; - if (ConstraintValidator.class.equals(parameterizedType.getRawType())) { - return parameterizedType.getActualTypeArguments()[0]; - } - } - } - return Void.TYPE; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/Required.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/Required.java deleted file mode 100644 index e6f3c56338c..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/Required.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.validation.constraints; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apache.logging.log4j.core.config.plugins.validation.Constraint; -import org.apache.logging.log4j.core.config.plugins.validation.validators.RequiredValidator; - -/** - * Marks a plugin builder field or plugin factory parameter as required. - * - * @since 2.1 - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD, ElementType.PARAMETER}) -@Constraint(RequiredValidator.class) -public @interface Required { - - /** - * The message to be logged if this constraint is violated. This should normally be overridden. - */ - String message() default "The parameter is null"; -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidPort.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidPort.java deleted file mode 100644 index a7c68b1aeb6..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidPort.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config.plugins.validation.constraints; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.apache.logging.log4j.core.config.plugins.validation.Constraint; -import org.apache.logging.log4j.core.config.plugins.validation.validators.ValidPortValidator; - -/** - * Indicates that a plugin attribute must be a valid port number. A valid port number is an integer between 0 and - * 65535. - * - * @since 2.8 - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD, ElementType.PARAMETER}) -@Constraint(ValidPortValidator.class) -public @interface ValidPort { - - /** - * The message to be logged if this constraint is violated. This should normally be overridden. - */ - String message() default "The port number is invalid"; -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/package-info.java deleted file mode 100644 index f22ba49422b..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * Validation annotations. - * - * @since 2.1 - */ -package org.apache.logging.log4j.core.config.plugins.validation.constraints; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/package-info.java deleted file mode 100644 index 171b25a004f..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * Constraint validators for plugin factory methods. - * - * @since 2.1 - */ -package org.apache.logging.log4j.core.config.plugins.validation; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidator.java deleted file mode 100644 index a59742cd7fb..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.config.plugins.validation.validators; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; -import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * Validator that checks an object to verify it is a valid port number (an integer between 0 and 65535). - * - * @since 2.8 - */ -public class ValidPortValidator implements ConstraintValidator { - - private static final Logger LOGGER = StatusLogger.getLogger(); - - private ValidPort annotation; - - @Override - public void initialize(final ValidPort annotation) { - this.annotation = annotation; - } - - @Override - public boolean isValid(final String name, final Object value) { - if (value instanceof CharSequence) { - return isValid(name, TypeConverters.convert(value.toString(), Integer.class, -1)); - } - if (!Integer.class.isInstance(value)) { - LOGGER.error(annotation.message()); - return false; - } - final int port = (int) value; - if (port < 0 || port > 65535) { - LOGGER.error(annotation.message()); - return false; - } - return true; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/package-info.java deleted file mode 100644 index a8ac56084ba..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * ConstraintValidator implementations for the constraint annotations. - * - * @since 2.1 - */ -package org.apache.logging.log4j.core.config.plugins.validation.validators; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginAttributeVisitor.java new file mode 100644 index 00000000000..95bec784b09 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginAttributeVisitor.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.visit; + +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; + +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.convert.TypeConverter; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Keys; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.util.StringBuilders; +import org.apache.logging.log4j.util.Strings; + +// copied to log4j-core for backward compatibility +@SuppressWarnings({ "DuplicatedCode", "deprecation" }) +public class PluginAttributeVisitor implements NodeVisitor { + private static final Map> DEFAULT_VALUE_EXTRACTORS = Map.ofEntries( + Map.entry(int.class, PluginAttribute::defaultInt), + Map.entry(Integer.class, PluginAttribute::defaultInt), + Map.entry(long.class, PluginAttribute::defaultLong), + Map.entry(Long.class, PluginAttribute::defaultLong), + Map.entry(boolean.class, PluginAttribute::defaultBoolean), + Map.entry(Boolean.class, PluginAttribute::defaultBoolean), + Map.entry(float.class, PluginAttribute::defaultFloat), + Map.entry(Float.class, PluginAttribute::defaultFloat), + Map.entry(double.class, PluginAttribute::defaultDouble), + Map.entry(Double.class, PluginAttribute::defaultDouble), + Map.entry(byte.class, PluginAttribute::defaultByte), + Map.entry(Byte.class, PluginAttribute::defaultByte), + Map.entry(char.class, PluginAttribute::defaultChar), + Map.entry(Character.class, PluginAttribute::defaultChar), + Map.entry(short.class, PluginAttribute::defaultShort), + Map.entry(Short.class, PluginAttribute::defaultShort), + Map.entry(Class.class, PluginAttribute::defaultClass) + ); + + private final Function stringSubstitutionStrategy; + private final Injector injector; + + @Inject + public PluginAttributeVisitor( + @Named(Keys.SUBSTITUTOR_NAME) final Function stringSubstitutionStrategy, + final Injector injector) { + this.stringSubstitutionStrategy = stringSubstitutionStrategy; + this.injector = injector; + } + + @Override + public Object visitField(final Field field, final Node node, final StringBuilder debugLog) { + final String name = Keys.getName(field); + final Collection aliases = Keys.getAliases(field); + final Type targetType = field.getGenericType(); + final TypeConverter converter = injector.getTypeConverter(targetType); + final PluginAttribute annotation = field.getAnnotation(PluginAttribute.class); + final boolean sensitive = annotation.sensitive(); + final Object value = node.removeMatchingAttribute(name, aliases) + .map(stringSubstitutionStrategy.andThen(s -> (Object) converter.convert(s, null, sensitive))) + .orElseGet(() -> getDefaultValue(targetType, annotation)); + StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", "); + return value; + } + + @Override + public Object visitParameter(final Parameter parameter, final Node node, final StringBuilder debugLog) { + final String name = Keys.getName(parameter); + final Collection aliases = Keys.getAliases(parameter); + final Type targetType = parameter.getParameterizedType(); + final TypeConverter converter = injector.getTypeConverter(targetType); + final PluginAttribute annotation = parameter.getAnnotation(PluginAttribute.class); + final boolean sensitive = annotation.sensitive(); + final Object value = node.removeMatchingAttribute(name, aliases) + .map(stringSubstitutionStrategy.andThen(s -> (Object) converter.convert(s, null, sensitive))) + .orElseGet(() -> getDefaultValue(targetType, annotation)); + StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", "); + return value; + } + + private Object getDefaultValue(final Type targetType, final PluginAttribute annotation) { + final Function extractor = DEFAULT_VALUE_EXTRACTORS.get(targetType); + if (extractor != null) { + return extractor.apply(annotation); + } + final TypeConverter converter = injector.getTypeConverter(targetType); + final var value = stringSubstitutionStrategy.apply(annotation.defaultString()); + return Strings.isEmpty(value) ? null : converter.convert(value, null); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginBuilderAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginBuilderAttributeVisitor.java new file mode 100644 index 00000000000..658b5f119b8 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginBuilderAttributeVisitor.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.plugins.visit; + +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Keys; + +import java.lang.reflect.AnnotatedElement; +import java.util.function.Function; + +public class PluginBuilderAttributeVisitor extends org.apache.logging.log4j.plugins.visit.PluginBuilderAttributeVisitor { + @Inject + public PluginBuilderAttributeVisitor( + @Named(Keys.SUBSTITUTOR_NAME) final Function stringSubstitutionStrategy, + final Injector injector) { + super(stringSubstitutionStrategy, injector); + } + + @SuppressWarnings("deprecation") + @Override + protected boolean isSensitive(final AnnotatedElement element) { + return element.getAnnotation(PluginBuilderAttribute.class).sensitive(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginConfigurationVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginConfigurationVisitor.java new file mode 100644 index 00000000000..a9523d202ea --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginConfigurationVisitor.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.visit; + +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.util.TypeUtil; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Cast; +import org.apache.logging.log4j.util.StringBuilders; + +public class PluginConfigurationVisitor implements NodeVisitor { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final Configuration configuration; + + @Inject + public PluginConfigurationVisitor(final Configuration configuration) { + this.configuration = configuration; + } + + @Override + public Object visitField(final Field field, final Node node, final StringBuilder debugLog) { + if (TypeUtil.isAssignable(field.getGenericType(), configuration.getClass())) { + StringBuilders.appendKeyDqValueWithJoiner(debugLog, "configuration", configuration, ", "); + return Cast.cast(configuration); + } else { + LOGGER.error("Field {} annotated with @PluginConfiguration is not compatible with type {}", field, + configuration.getClass()); + return null; + } + } + + @Override + public Object visitParameter(final Parameter parameter, final Node node, final StringBuilder debugLog) { + if (TypeUtil.isAssignable(parameter.getParameterizedType(), configuration.getClass())) { + StringBuilders.appendKeyDqValueWithJoiner(debugLog, "configuration", configuration, ", "); + return Cast.cast(configuration); + } else { + LOGGER.error("Parameter {} annotated with @PluginConfiguration is not compatible with type {}", parameter, + configuration.getClass()); + return null; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginLoggerContextVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginLoggerContextVisitor.java new file mode 100644 index 00000000000..b0774ec133d --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginLoggerContextVisitor.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.config.plugins.visit; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.Parameter; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.util.TypeUtil; +import org.apache.logging.log4j.plugins.visit.NodeVisitor; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Cast; +import org.apache.logging.log4j.util.StringBuilders; + +/** + * PluginVisitor implementation for {@link PluginConfiguration}. + */ +public class PluginLoggerContextVisitor implements NodeVisitor { + private static final Logger LOGGER = StatusLogger.getLogger(); + private LoggerContext loggerContext; + + @Inject + public PluginLoggerContextVisitor(final WeakReference loggerContext) { + this.loggerContext = loggerContext.get(); + } + + @Override + public Object visitField(final Field field, final Node node, final StringBuilder debugLog) { + if (TypeUtil.isAssignable(field.getGenericType(), LoggerContext.class)) { + StringBuilders.appendKeyDqValueWithJoiner(debugLog, "loggerContext", loggerContext, ", "); + return Cast.cast(loggerContext); + } else { + LOGGER.error("Field {} annotated with @PluginLoggerContext is not compatible with type {}", field, + loggerContext.getClass()); + return null; + } + } + + @Override + public Object visitParameter(final Parameter parameter, final Node node, final StringBuilder debugLog) { + if (TypeUtil.isAssignable(parameter.getParameterizedType(), loggerContext.getClass())) { + StringBuilders.appendKeyDqValueWithJoiner(debugLog, "loggerContext", loggerContext, ", "); + return Cast.cast(loggerContext); + } else { + LOGGER.error("Parameter {} annotated with @PluginLoggerContext is not compatible with type {}", + parameter, loggerContext.getClass()); + return null; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java deleted file mode 100644 index 560cbe3311e..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Member; -import java.util.Map; -import java.util.Objects; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Strings; - -/** - * Base class for PluginVisitor implementations. Provides convenience methods as well as all method implementations - * other than the {@code visit} method. - * - * @param the Plugin annotation type. - */ -public abstract class AbstractPluginVisitor implements PluginVisitor { - - /** Status logger. */ - protected static final Logger LOGGER = StatusLogger.getLogger(); - - /** - * - */ - protected final Class clazz; - /** - * - */ - protected A annotation; - /** - * - */ - protected String[] aliases; - /** - * - */ - protected Class conversionType; - /** - * - */ - protected StrSubstitutor substitutor; - /** - * - */ - protected Member member; - - /** - * This constructor must be overridden by implementation classes as a no-arg constructor. - * - * @param clazz the annotation class this PluginVisitor is for. - */ - protected AbstractPluginVisitor(final Class clazz) { - this.clazz = clazz; - } - - @SuppressWarnings("unchecked") - @Override - public PluginVisitor setAnnotation(final Annotation anAnnotation) { - final Annotation a = Objects.requireNonNull(anAnnotation, "No annotation was provided"); - if (this.clazz.isInstance(a)) { - this.annotation = (A) a; - } - return this; - } - - @Override - public PluginVisitor setAliases(final String... someAliases) { - this.aliases = someAliases; - return this; - } - - @Override - public PluginVisitor setConversionType(final Class aConversionType) { - this.conversionType = Objects.requireNonNull(aConversionType, "No conversion type class was provided"); - return this; - } - - @Override - public PluginVisitor setStrSubstitutor(final StrSubstitutor aSubstitutor) { - this.substitutor = Objects.requireNonNull(aSubstitutor, "No StrSubstitutor was provided"); - return this; - } - - @Override - public PluginVisitor setMember(final Member aMember) { - this.member = aMember; - return this; - } - - /** - * Removes an Entry from a given Map using a key name and aliases for that key. Keys are case-insensitive. - * - * @param attributes the Map to remove an Entry from. - * @param name the key name to look up. - * @param aliases optional aliases of the key name to look up. - * @return the value corresponding to the given key or {@code null} if nonexistent. - */ - protected static String removeAttributeValue(final Map attributes, - final String name, - final String... aliases) { - for (final Map.Entry entry : attributes.entrySet()) { - final String key = entry.getKey(); - final String value = entry.getValue(); - if (key.equalsIgnoreCase(name)) { - attributes.remove(key); - return value; - } - if (aliases != null) { - for (final String alias : aliases) { - if (key.equalsIgnoreCase(alias)) { - attributes.remove(key); - return value; - } - } - } - } - return null; - } - - /** - * Converts the given value into the configured type falling back to the provided default value. - * - * @param value the value to convert. - * @param defaultValue the fallback value to use in case of no value or an error. - * @return the converted value whether that be based on the given value or the default value. - */ - protected Object convert(final String value, final Object defaultValue) { - if (defaultValue instanceof String) { - return TypeConverters.convert(value, this.conversionType, Strings.trimToNull((String) defaultValue)); - } - return TypeConverters.convert(value, this.conversionType, defaultValue); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java deleted file mode 100644 index f4da42b8b30..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import java.util.Map; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.util.NameUtil; -import org.apache.logging.log4j.util.StringBuilders; - -/** - * PluginVisitor implementation for {@link PluginAttribute}. - */ -public class PluginAttributeVisitor extends AbstractPluginVisitor { - public PluginAttributeVisitor() { - super(PluginAttribute.class); - } - - @Override - public Object visit(final Configuration configuration, final Node node, final LogEvent event, - final StringBuilder log) { - final String name = this.annotation.value(); - final Map attributes = node.getAttributes(); - final String rawValue = removeAttributeValue(attributes, name, this.aliases); - final String replacedValue = this.substitutor.replace(event, rawValue); - final Object defaultValue = findDefaultValue(event); - final Object value = convert(replacedValue, defaultValue); - final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value; - StringBuilders.appendKeyDqValue(log, name, debugValue); - return value; - } - - private Object findDefaultValue(final LogEvent event) { - if (this.conversionType == int.class || this.conversionType == Integer.class) { - return this.annotation.defaultInt(); - } - if (this.conversionType == long.class || this.conversionType == Long.class) { - return this.annotation.defaultLong(); - } - if (this.conversionType == boolean.class || this.conversionType == Boolean.class) { - return this.annotation.defaultBoolean(); - } - if (this.conversionType == float.class || this.conversionType == Float.class) { - return this.annotation.defaultFloat(); - } - if (this.conversionType == double.class || this.conversionType == Double.class) { - return this.annotation.defaultDouble(); - } - if (this.conversionType == byte.class || this.conversionType == Byte.class) { - return this.annotation.defaultByte(); - } - if (this.conversionType == char.class || this.conversionType == Character.class) { - return this.annotation.defaultChar(); - } - if (this.conversionType == short.class || this.conversionType == Short.class) { - return this.annotation.defaultShort(); - } - if (this.conversionType == Class.class) { - return this.annotation.defaultClass(); - } - return this.substitutor.replace(event, this.annotation.defaultString()); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java deleted file mode 100644 index e951456eef1..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import java.util.Map; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.util.NameUtil; -import org.apache.logging.log4j.util.StringBuilders; - -/** - * PluginVisitor for PluginBuilderAttribute. If {@code null} is returned for the - * {@link #visit(org.apache.logging.log4j.core.config.Configuration, org.apache.logging.log4j.core.config.Node, org.apache.logging.log4j.core.LogEvent, StringBuilder)} - * method, then the default value of the field should remain untouched. - * - * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder - */ -public class PluginBuilderAttributeVisitor extends AbstractPluginVisitor { - - public PluginBuilderAttributeVisitor() { - super(PluginBuilderAttribute.class); - } - - @Override - public Object visit(final Configuration configuration, final Node node, final LogEvent event, - final StringBuilder log) { - final String overridden = this.annotation.value(); - final String name = overridden.isEmpty() ? this.member.getName() : overridden; - final Map attributes = node.getAttributes(); - final String rawValue = removeAttributeValue(attributes, name, this.aliases); - final String replacedValue = this.substitutor.replace(event, rawValue); - final Object value = convert(replacedValue, null); - final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value; - StringBuilders.appendKeyDqValue(log, name, debugValue); - return value; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java deleted file mode 100644 index a5a896ff385..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; - -/** - * PluginVisitor implementation for {@link PluginConfiguration}. - */ -public class PluginConfigurationVisitor extends AbstractPluginVisitor { - public PluginConfigurationVisitor() { - super(PluginConfiguration.class); - } - - @Override - public Object visit(final Configuration configuration, final Node node, final LogEvent event, - final StringBuilder log) { - if (this.conversionType.isInstance(configuration)) { - log.append("Configuration"); - if (configuration.getName() != null) { - log.append('(').append(configuration.getName()).append(')'); - } - return configuration; - } - LOGGER.warn("Variable annotated with @PluginConfiguration is not compatible with type {}.", - configuration.getClass()); - return null; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java deleted file mode 100644 index 95b3ae69dfb..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; - -/** - * PluginVisitor implementation for {@link PluginElement}. Supports arrays as well as singular values. - */ -public class PluginElementVisitor extends AbstractPluginVisitor { - public PluginElementVisitor() { - super(PluginElement.class); - } - - @Override - public Object visit(final Configuration configuration, final Node node, final LogEvent event, - final StringBuilder log) { - final String name = this.annotation.value(); - if (this.conversionType.isArray()) { - setConversionType(this.conversionType.getComponentType()); - final List values = new ArrayList<>(); - final Collection used = new ArrayList<>(); - log.append("={"); - boolean first = true; - for (final Node child : node.getChildren()) { - final PluginType childType = child.getType(); - if (name.equalsIgnoreCase(childType.getElementName()) || - this.conversionType.isAssignableFrom(childType.getPluginClass())) { - if (!first) { - log.append(", "); - } - first = false; - used.add(child); - final Object childObject = child.getObject(); - if (childObject == null) { - LOGGER.error("Null object returned for {} in {}.", child.getName(), node.getName()); - continue; - } - if (childObject.getClass().isArray()) { - log.append(Arrays.toString((Object[]) childObject)).append('}'); - return childObject; - } - log.append(child.toString()); - values.add(childObject); - } - } - log.append('}'); - // note that we need to return an empty array instead of null if the types are correct - if (!values.isEmpty() && !this.conversionType.isAssignableFrom(values.get(0).getClass())) { - LOGGER.error("Attempted to assign attribute {} to list of type {} which is incompatible with {}.", - name, values.get(0).getClass(), this.conversionType); - return null; - } - node.getChildren().removeAll(used); - // we need to use reflection here because values.toArray() will cause type errors at runtime - final Object[] array = (Object[]) Array.newInstance(this.conversionType, values.size()); - for (int i = 0; i < array.length; i++) { - array[i] = values.get(i); - } - return array; - } - final Node namedNode = findNamedNode(name, node.getChildren()); - if (namedNode == null) { - log.append(name).append("=null"); - return null; - } - log.append(namedNode.getName()).append('(').append(namedNode.toString()).append(')'); - node.getChildren().remove(namedNode); - return namedNode.getObject(); - } - - private Node findNamedNode(final String name, final Iterable children) { - for (final Node child : children) { - final PluginType childType = child.getType(); - if (childType == null) { - //System.out.println(); - } - if (name.equalsIgnoreCase(childType.getElementName()) || - this.conversionType.isAssignableFrom(childType.getPluginClass())) { - // FIXME: check child.getObject() for null? - // doing so would be more consistent with the array version - return child; - } - } - return null; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java deleted file mode 100644 index 7f15392ad8e..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.PluginNode; - -/** - * PluginVisitor implementation for {@link PluginNode}. - */ -public class PluginNodeVisitor extends AbstractPluginVisitor { - public PluginNodeVisitor() { - super(PluginNode.class); - } - - @Override - public Object visit(final Configuration configuration, final Node node, final LogEvent event, - final StringBuilder log) { - if (this.conversionType.isInstance(node)) { - log.append("Node=").append(node.getName()); - return node; - } - LOGGER.warn("Variable annotated with @PluginNode is not compatible with the type {}.", node.getClass()); - return null; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java deleted file mode 100644 index 8544570ea4d..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.PluginValue; -import org.apache.logging.log4j.util.StringBuilders; -import org.apache.logging.log4j.util.Strings; - -/** - * PluginVisitor implementation for {@link PluginValue}. - */ -public class PluginValueVisitor extends AbstractPluginVisitor { - public PluginValueVisitor() { - super(PluginValue.class); - } - - @Override - public Object visit(final Configuration configuration, final Node node, final LogEvent event, - final StringBuilder log) { - final String name = this.annotation.value(); - final String elementValue = node.getValue(); - final String attributeValue = node.getAttributes().get("value"); - String rawValue = null; // if neither is specified, return null (LOG4J2-1313) - if (Strings.isNotEmpty(elementValue)) { - if (Strings.isNotEmpty(attributeValue)) { - LOGGER.error("Configuration contains {} with both attribute value ({}) AND element" + - " value ({}). Please specify only one value. Using the element value.", - node.getName(), attributeValue, elementValue); - } - rawValue = elementValue; - } else { - rawValue = removeAttributeValue(node.getAttributes(), "value"); - } - final String value = this.substitutor.replace(event, rawValue); - StringBuilders.appendKeyDqValue(log, name, value); - return value; - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitor.java deleted file mode 100644 index 34e2b789147..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitor.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Member; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; - -/** - * Visitor strategy for parsing data from a {@link Node}, doing any relevant type conversion, and returning a - * parsed value for that variable. Implementations must be constructable using the default constructor. - * - * @param the Annotation type. - */ -public interface PluginVisitor { - - /** - * Sets the Annotation to be used for this. If the given Annotation is not compatible with this class's type, then - * it is ignored. - * - * @param annotation the Annotation instance. - * @return {@code this}. - * @throws NullPointerException if the argument is {@code null}. - */ - PluginVisitor setAnnotation(Annotation annotation); - - /** - * Sets the list of aliases to use for this visit. No aliases are required, however. - * - * @param aliases the list of aliases to use. - * @return {@code this}. - */ - PluginVisitor setAliases(String... aliases); - - /** - * Sets the class to convert the plugin value to on this visit. This should correspond with a class obtained from - * a factory method or builder class field. Not all PluginVisitor implementations may need this value. - * - * @param conversionType the type to convert the plugin string to (if applicable). - * @return {@code this}. - * @throws NullPointerException if the argument is {@code null}. - */ - PluginVisitor setConversionType(Class conversionType); - - /** - * Sets the StrSubstitutor to use for converting raw strings before type conversion. Generally obtained from a - * {@link org.apache.logging.log4j.core.config.Configuration}. - * - * @param substitutor the StrSubstitutor to use on plugin values. - * @return {@code this}. - * @throws NullPointerException if the argument is {@code null}. - */ - PluginVisitor setStrSubstitutor(StrSubstitutor substitutor); - - /** - * Sets the Member that this visitor is being used for injection upon. For instance, this could be the Field - * that is being used for injecting a value, or it could be the factory method being used to inject parameters - * into. - * - * @param member the member this visitor is parsing a value for. - * @return {@code this}. - */ - PluginVisitor setMember(Member member); - - /** - * Visits a Node to obtain a value for constructing a Plugin object. - * - * @param configuration the current Configuration. - * @param node the current Node corresponding to the Plugin object being created. - * @param event the current LogEvent that caused this Plugin object to be made (optional). - * @param log the StringBuilder being used to build a debug message. - * @return the converted value to be used for Plugin creation. - */ - Object visit(Configuration configuration, Node node, LogEvent event, StringBuilder log); -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitors.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitors.java deleted file mode 100644 index 10ee0dfbfe7..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitors.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.config.plugins.visitors; - -import java.lang.annotation.Annotation; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.PluginVisitorStrategy; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * Utility class to locate an appropriate {@link PluginVisitor} implementation for an annotation. - */ -public final class PluginVisitors { - - private static final Logger LOGGER = StatusLogger.getLogger(); - - private PluginVisitors() { - } - - /** - * Creates a PluginVisitor instance for the given annotation class using metadata provided by the annotation's - * {@link PluginVisitorStrategy} annotation. This instance must be further populated with - * data to be useful. Such data is passed through both the setters and the visit method. - * - * @param annotation the Plugin annotation class to find a PluginVisitor for. - * @return a PluginVisitor instance if one could be created, or {@code null} otherwise. - */ - public static PluginVisitor findVisitor(final Class annotation) { - final PluginVisitorStrategy strategy = annotation.getAnnotation(PluginVisitorStrategy.class); - if (strategy == null) { - return null; - } - try { - return strategy.value().newInstance(); - } catch (final Exception e) { - LOGGER.error("Error loading PluginVisitor [{}] for annotation [{}].", strategy.value(), annotation, e); - return null; - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java deleted file mode 100644 index eae8603107e..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -/** - * Visitor classes for extracting values from a Configuration or Node corresponding to a plugin annotation. - * Visitor implementations must implement {@link org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor}, - * and the corresponding annotation must be annotated with - * {@link org.apache.logging.log4j.core.config.plugins.PluginVisitorStrategy}. - */ -package org.apache.logging.log4j.core.config.plugins.visitors; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java index 3143259ffd4..2d98ced6b59 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java @@ -14,14 +14,15 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.config.properties; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationException; @@ -41,7 +42,8 @@ import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder; import org.apache.logging.log4j.core.filter.AbstractFilter.AbstractFilterBuilder; -import org.apache.logging.log4j.core.util.Builder; +import org.apache.logging.log4j.plugins.util.Builder; +import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.Strings; @@ -53,6 +55,7 @@ public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory implements Builder { + private static final Logger LOGGER = StatusLogger.getLogger(); private static final String ADVERTISER_KEY = "advertiser"; private static final String STATUS_KEY = "status"; private static final String SHUTDOWN_HOOK = "shutdownHook"; @@ -170,8 +173,9 @@ public PropertiesConfiguration build() { } } } else { + final Map loggers = PropertiesUtil - .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "logger")); + .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "logger"), true); for (final Map.Entry entry : loggers.entrySet()) { final String name = entry.getKey().trim(); if (!name.equals(LoggerConfig.ROOT)) { @@ -180,13 +184,18 @@ public PropertiesConfiguration build() { } } + final String rootProp = rootProperties.getProperty("rootLogger"); final Properties props = PropertiesUtil.extractSubset(rootProperties, "rootLogger"); + if (rootProp != null) { + props.setProperty("", rootProp); + rootProperties.remove("rootLogger"); + } if (props.size() > 0) { builder.add(createRootLogger(props)); } - + builder.setLoggerContext(loggerContext); - + return builder.build(false); } @@ -207,10 +216,7 @@ private ScriptFileComponentBuilder createScriptFile(final Properties properties) } private AppenderComponentBuilder createAppender(final String key, final Properties properties) { - final String name = (String) properties.remove(CONFIG_NAME); - if (Strings.isEmpty(name)) { - throw new ConfigurationException("No name attribute provided for Appender " + key); - } + final String name = Objects.toString(properties.remove(CONFIG_NAME), key); final String type = (String) properties.remove(CONFIG_TYPE); if (Strings.isEmpty(type)) { throw new ConfigurationException("No type attribute provided for Appender " + key); @@ -228,7 +234,7 @@ private AppenderComponentBuilder createAppender(final String key, final Properti private FilterComponentBuilder createFilter(final String key, final Properties properties) { final String type = (String) properties.remove(CONFIG_TYPE); if (Strings.isEmpty(type)) { - throw new ConfigurationException("No type attribute provided for Appender " + key); + throw new ConfigurationException("No type attribute provided for Filter " + key); } final String onMatch = (String) properties.remove(AbstractFilterBuilder.ATTR_ON_MATCH); final String onMismatch = (String) properties.remove(AbstractFilterBuilder.ATTR_ON_MISMATCH); @@ -250,6 +256,7 @@ private AppenderRefComponentBuilder createAppenderRef(final String key, final Pr } private LoggerComponentBuilder createLogger(final String key, final Properties properties) { + final String levelAndRefs = properties.getProperty(""); final String name = (String) properties.remove(CONFIG_NAME); final String location = (String) properties.remove("includeLocation"); if (Strings.isEmpty(name)) { @@ -258,7 +265,7 @@ private LoggerComponentBuilder createLogger(final String key, final Properties p final String level = Strings.trimToNull((String) properties.remove("level")); final String type = (String) properties.remove(CONFIG_TYPE); final LoggerComponentBuilder loggerBuilder; - boolean includeLocation; + final boolean includeLocation; if (type != null) { if (type.equalsIgnoreCase("asyncLogger")) { if (location != null) { @@ -284,10 +291,14 @@ private LoggerComponentBuilder createLogger(final String key, final Properties p if (!Strings.isEmpty(additivity)) { loggerBuilder.addAttribute("additivity", additivity); } + if (levelAndRefs != null) { + loggerBuilder.addAttribute("levelAndRefs", levelAndRefs); + } return loggerBuilder; } private RootLoggerComponentBuilder createRootLogger(final Properties properties) { + final String levelAndRefs = properties.getProperty(""); final String level = Strings.trimToNull((String) properties.remove("level")); final String type = (String) properties.remove(CONFIG_TYPE); final String location = (String) properties.remove("includeLocation"); @@ -313,6 +324,9 @@ private RootLoggerComponentBuilder createRootLogger(final Properties properties) } } addLoggersToComponent(loggerBuilder, properties); + if (levelAndRefs != null) { + loggerBuilder.addAttribute("levelAndRefs", levelAndRefs); + } return addFiltersToComponent(loggerBuilder, properties); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java index d5e1aa991d4..3cfc05957be 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java @@ -16,23 +16,25 @@ */ package org.apache.logging.log4j.core.config.properties; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationException; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.Order; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; /** * Creates a PropertiesConfiguration from a properties file. * * @since 2.4 */ -@Plugin(name = "PropertiesConfigurationFactory", category = ConfigurationFactory.CATEGORY) +@Namespace(ConfigurationFactory.NAMESPACE) +@Plugin("PropertiesConfigurationFactory") @Order(8) public class PropertiesConfigurationFactory extends ConfigurationFactory { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java index a6f4d1f3a81..6070d30bca2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java @@ -24,8 +24,8 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingQueue; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.util.FileUtils; @@ -44,10 +44,10 @@ public class StatusConfiguration { private static final Level DEFAULT_STATUS = Level.ERROR; private static final Verbosity DEFAULT_VERBOSITY = Verbosity.QUIET; - private final Collection errorMessages = Collections.synchronizedCollection(new LinkedList()); + private final Collection errorMessages = new LinkedBlockingQueue(); private final StatusLogger logger = StatusLogger.getLogger(); - private volatile boolean initialized = false; + private volatile boolean initialized; private PrintStream destination = DEFAULT_STREAM; private Level status = DEFAULT_STATUS; @@ -94,7 +94,7 @@ public void error(final String message) { * @param destination where status log messages should be output. * @return {@code this} */ - public StatusConfiguration withDestination(final String destination) { + public StatusConfiguration setDestination(final String destination) { try { this.destination = parseStreamName(destination); } catch (final URISyntaxException e) { @@ -131,7 +131,7 @@ private PrintStream parseStreamName(final String name) throws URISyntaxException * @return {@code this} * @see Level */ - public StatusConfiguration withStatus(final String status) { + public StatusConfiguration setStatus(final String status) { this.status = Level.toLevel(status, null); if (this.status == null) { this.error("Invalid status level specified: " + status + ". Defaulting to ERROR."); @@ -146,19 +146,19 @@ public StatusConfiguration withStatus(final String status) { * @param status logger level to filter below. * @return {@code this} */ - public StatusConfiguration withStatus(final Level status) { + public StatusConfiguration setStatus(final Level status) { this.status = status; return this; } /** * Specifies the verbosity level to log at. This only applies to classes configured by - * {@link #withVerboseClasses(String...) verboseClasses}. + * {@link #setVerboseClasses(String...) verboseClasses}. * * @param verbosity basic filter for status logger messages. * @return {@code this} */ - public StatusConfiguration withVerbosity(final String verbosity) { + public StatusConfiguration setVerbosity(final String verbosity) { this.verbosity = Verbosity.toVerbosity(verbosity); return this; } @@ -169,7 +169,7 @@ public StatusConfiguration withVerbosity(final String verbosity) { * @param verboseClasses names of classes to filter if not using VERBOSE. * @return {@code this} */ - public StatusConfiguration withVerboseClasses(final String... verboseClasses) { + public StatusConfiguration setVerboseClasses(final String... verboseClasses) { this.verboseClasses = verboseClasses; return this; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java index 7efb6aba01f..830a3c0737c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java @@ -17,7 +17,6 @@ package org.apache.logging.log4j.core.config.xml; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -28,7 +27,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; @@ -38,17 +36,16 @@ import org.apache.logging.log4j.core.config.AbstractConfiguration; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher; -import org.apache.logging.log4j.core.config.Node; import org.apache.logging.log4j.core.config.Reconfigurable; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; -import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil; import org.apache.logging.log4j.core.config.status.StatusConfiguration; import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.core.util.FileWatcher; +import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.core.util.Patterns; import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.model.PluginType; +import org.apache.logging.log4j.plugins.util.ResolverUtil; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -77,13 +74,12 @@ public class XmlConfiguration extends AbstractConfiguration implements Reconfigu public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource configSource) { super(loggerContext, configSource); - final File configFile = configSource.getFile(); byte[] buffer = null; try { final InputStream configStream = configSource.getInputStream(); try { - buffer = toByteArray(configStream); + buffer = configStream.readAllBytes(); } finally { Closer.closeSilently(configStream); } @@ -108,23 +104,24 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo } rootElement = document.getDocumentElement(); final Map attrs = processAttributes(rootNode, rootElement); - final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES) - .withStatus(getDefaultStatus()); + final StatusConfiguration statusConfig = new StatusConfiguration().setVerboseClasses(VERBOSE_CLASSES) + .setStatus(getDefaultStatus()); + int monitorIntervalSeconds = 0; for (final Map.Entry entry : attrs.entrySet()) { final String key = entry.getKey(); - final String value = getStrSubstitutor().replace(entry.getValue()); + final String value = getConfigurationStrSubstitutor().replace(entry.getValue()); if ("status".equalsIgnoreCase(key)) { - statusConfig.withStatus(value); + statusConfig.setStatus(value); } else if ("dest".equalsIgnoreCase(key)) { - statusConfig.withDestination(value); + statusConfig.setDestination(value); } else if ("shutdownHook".equalsIgnoreCase(key)) { isShutdownHookEnabled = !"disable".equalsIgnoreCase(value); } else if ("shutdownTimeout".equalsIgnoreCase(key)) { shutdownTimeoutMillis = Long.parseLong(value); } else if ("verbose".equalsIgnoreCase(key)) { - statusConfig.withVerbosity(value); + statusConfig.setVerbosity(value); } else if ("packages".equalsIgnoreCase(key)) { - pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))); + LOGGER.warn("The packages attribute is no longer supported"); } else if ("name".equalsIgnoreCase(key)) { setName(value); } else if ("strict".equalsIgnoreCase(key)) { @@ -132,26 +129,20 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo } else if ("schema".equalsIgnoreCase(key)) { schemaResource = value; } else if ("monitorInterval".equalsIgnoreCase(key)) { - final int intervalSeconds = Integer.parseInt(value); - if (intervalSeconds > 0) { - getWatchManager().setIntervalSeconds(intervalSeconds); - if (configFile != null) { - final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners); - getWatchManager().watchFile(configFile, watcher); - } - } + monitorIntervalSeconds = Integers.parseInt(value); } else if ("advertiser".equalsIgnoreCase(key)) { createAdvertiser(value, configSource, buffer, "text/xml"); } } + initializeWatchers(this, configSource, monitorIntervalSeconds); statusConfig.initialize(); } catch (final SAXException | IOException | ParserConfigurationException e) { LOGGER.error("Error parsing " + configSource.getLocation(), e); } if (strict && schemaResource != null && buffer != null) { - try (InputStream is = Loader.getResourceAsStream(schemaResource, XmlConfiguration.class.getClassLoader())) { + try (final InputStream is = Loader.getResourceAsStream(schemaResource, XmlConfiguration.class.getClassLoader())) { if (is != null) { - final Source src = new StreamSource(is, LOG4J_XSD); + final javax.xml.transform.Source src = new StreamSource(is, LOG4J_XSD); final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = null; try { @@ -182,7 +173,7 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo /** * Creates a new DocumentBuilder suitable for parsing a configuration file. - * + * * @param xIncludeAware enabled XInclude * @return a new DocumentBuilder * @throws ParserConfigurationException @@ -199,23 +190,23 @@ static DocumentBuilder newDocumentBuilder(final boolean xIncludeAware) throws Pa return factory.newDocumentBuilder(); } - private static void disableDtdProcessing(final DocumentBuilderFactory factory) throws ParserConfigurationException { + private static void disableDtdProcessing(final DocumentBuilderFactory factory) { factory.setValidating(false); factory.setExpandEntityReferences(false); setFeature(factory, "http://xml.org/sax/features/external-general-entities", false); setFeature(factory, "http://xml.org/sax/features/external-parameter-entities", false); setFeature(factory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", false); } - - private static void setFeature(final DocumentBuilderFactory factory, final String featureName, final boolean value) - throws ParserConfigurationException { + + private static void setFeature(final DocumentBuilderFactory factory, final String featureName, final boolean value) { try { factory.setFeature(featureName, value); - } catch (ParserConfigurationException e) { - throw e; - } catch (Exception | LinkageError e) { - getStatusLogger().error("Caught {} setting feature {} to {} on DocumentBuilderFactory {}: {}", - e.getClass().getCanonicalName(), featureName, value, factory, e, e); + } catch (final ParserConfigurationException e) { + LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory, + featureName, e); + } catch (final AbstractMethodError err) { + LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory, + err); } } @@ -229,32 +220,18 @@ private static void enableXInclude(final DocumentBuilderFactory factory) { // Alternative: We set if a system property on the command line is set, for example: // -DLog4j.XInclude=true factory.setXIncludeAware(true); - } catch (final UnsupportedOperationException e) { + // LOG4J2-3531: Xerces only checks if the feature is supported when creating a factory. To reproduce: + // -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XML11NonValidatingConfiguration + factory.newDocumentBuilder(); + } catch (final UnsupportedOperationException | ParserConfigurationException e) { + factory.setXIncludeAware(false); LOGGER.warn("The DocumentBuilderFactory [{}] does not support XInclude: {}", factory, e); - } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError | NoSuchMethodError err) { + } catch (final AbstractMethodError | NoSuchMethodError err) { LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support XInclude: {}", factory, err); } - try { - // Alternative: We could specify all features and values with system properties like: - // -DLog4j.DocumentBuilderFactory.Feature="http://apache.org/xml/features/xinclude/fixup-base-uris true" - factory.setFeature(XINCLUDE_FIXUP_BASE_URIS, true); - } catch (final ParserConfigurationException e) { - LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory, - XINCLUDE_FIXUP_BASE_URIS, e); - } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) { - LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory, - err); - } - try { - factory.setFeature(XINCLUDE_FIXUP_LANGUAGE, true); - } catch (final ParserConfigurationException e) { - LOGGER.warn("The DocumentBuilderFactory [{}] does not support the feature [{}]: {}", factory, - XINCLUDE_FIXUP_LANGUAGE, e); - } catch (@SuppressWarnings("ErrorNotRethrown") final AbstractMethodError err) { - LOGGER.warn("The DocumentBuilderFactory [{}] is out of date and does not support setFeature: {}", factory, - err); - } + setFeature(factory, XINCLUDE_FIXUP_BASE_URIS, true); + setFeature(factory, XINCLUDE_FIXUP_LANGUAGE, true); } @Override @@ -298,7 +275,7 @@ private void constructHierarchy(final Node node, final Element element) { if (w3cNode instanceof Element) { final Element child = (Element) w3cNode; final String name = getType(child); - final PluginType type = pluginManager.getPluginType(name); + final PluginType type = corePlugins.get(name); final Node childNode = new Node(node, name, type); constructHierarchy(childNode, child); if (type == null) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java index a37718fac73..ae967e14ed5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java @@ -21,12 +21,14 @@ import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.Order; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; /** * Factory to construct an XmlConfiguration. */ -@Plugin(name = "XmlConfigurationFactory", category = ConfigurationFactory.CATEGORY) +@Namespace(ConfigurationFactory.NAMESPACE) +@Plugin("XmlConfigurationFactory") @Order(5) public class XmlConfigurationFactory extends ConfigurationFactory { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java index c8f4560dfbc..ced88a676c1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java @@ -21,10 +21,12 @@ import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationSource; import org.apache.logging.log4j.core.config.Order; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; -@Plugin(name = "YamlConfigurationFactory", category = ConfigurationFactory.CATEGORY) +@Namespace(ConfigurationFactory.NAMESPACE) +@Plugin("YamlConfigurationFactory") @Order(7) public class YamlConfigurationFactory extends ConfigurationFactory { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java index 5e3c13358b2..102ca724611 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java @@ -22,7 +22,7 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.message.Message; /** @@ -69,21 +69,21 @@ public B setOnMatch(final Result onMatch) { /** * Sets the Result to return when the filter does not match. The default is Result.DENY. - * @param onMismatch the Result to return when the filter does not match. + * @param onMismatch the Result to return when the filter does not match. * @return this */ public B setOnMismatch(final Result onMismatch) { this.onMismatch = onMismatch; return asBuilder(); } - + @SuppressWarnings("unchecked") public B asBuilder() { return (B) this; } } - + /** * The onMatch Result. */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java index 3bd20ce1d56..c9e3d3ebb39 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java @@ -21,9 +21,10 @@ import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.LifeCycle2; +import org.apache.logging.log4j.core.LifeCycle; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.plugins.PluginElement; /** * Enhances a Class by allowing it to contain Filters. @@ -40,20 +41,32 @@ public abstract static class Builder> { @PluginElement("Filter") private Filter filter; - public Filter getFilter() { - return filter; - } + @PluginElement("Properties") + private Property[] propertyArray; @SuppressWarnings("unchecked") public B asBuilder() { return (B) this; } - public B withFilter(final Filter filter) { + public Filter getFilter() { + return filter; + } + + public Property[] getPropertyArray() { + return propertyArray; + } + + public B setFilter(final Filter filter) { this.filter = filter; return asBuilder(); } + public B setPropertyArray(final Property[] properties) { + this.propertyArray = properties; + return asBuilder(); + } + } /** @@ -61,20 +74,15 @@ public B withFilter(final Filter filter) { */ private volatile Filter filter; - protected AbstractFilterable(final Filter filter) { - this.filter = filter; - } + private final Property[] propertyArray; protected AbstractFilterable() { + this(null, null); } - /** - * Returns the Filter. - * @return the Filter or null. - */ - @Override - public Filter getFilter() { - return filter; + protected AbstractFilterable(final Filter filter, final Property[] propertyArray) { + this.filter = filter; + this.propertyArray = propertyArray == null ? Property.EMPTY_ARRAY : propertyArray; } /** @@ -96,6 +104,38 @@ public synchronized void addFilter(final Filter filter) { } } + /** + * Returns the Filter. + * @return the Filter or null. + */ + @Override + public Filter getFilter() { + return filter; + } + + public Property[] getPropertyArray() { + return propertyArray; + } + + /** + * Determines if a Filter is present. + * @return false if no Filter is present. + */ + @Override + public boolean hasFilter() { + return filter != null; + } + + /** + * Determine if the LogEvent should be processed or ignored. + * @param event The LogEvent. + * @return true if the LogEvent should be processed. + */ + @Override + public boolean isFiltered(final LogEvent event) { + return filter != null && filter.filter(event) == Filter.Result.DENY; + } + /** * Removes a Filter. * @param filter The Filter to remove. @@ -121,15 +161,6 @@ public synchronized void removeFilter(final Filter filter) { } } - /** - * Determines if a Filter is present. - * @return false if no Filter is present. - */ - @Override - public boolean hasFilter() { - return filter != null; - } - /** * Make the Filter available for use. */ @@ -159,12 +190,7 @@ protected boolean stop(final long timeout, final TimeUnit timeUnit, final boolea } boolean stopped = true; if (filter != null) { - if (filter instanceof LifeCycle2) { - stopped = ((LifeCycle2) filter).stop(timeout, timeUnit); - } else { - filter.stop(); - stopped = true; - } + stopped = ((LifeCycle) filter).stop(timeout, timeUnit); } if (changeLifeCycleState) { this.setStopped(); @@ -172,14 +198,4 @@ protected boolean stop(final long timeout, final TimeUnit timeUnit, final boolea return stopped; } - /** - * Determine if the LogEvent should be processed or ignored. - * @param event The LogEvent. - * @return true if the LogEvent should be processed. - */ - @Override - public boolean isFiltered(final LogEvent event) { - return filter != null && filter.filter(event) == Filter.Result.DENY; - } - } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java index c2326ad11d1..d6277bbe3b5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java @@ -17,22 +17,22 @@ package org.apache.logging.log4j.core.filter; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.DelayQueue; -import java.util.concurrent.Delayed; -import java.util.concurrent.TimeUnit; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; /** * The BurstFilter is a logging filter that regulates logging traffic. @@ -46,14 +46,15 @@ * * <Console name="console">
    *  <PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %x %t %m%n"/>
    - *  <filters>
    - *   <Burst level="INFO" rate="16" maxBurst="100"/>
    - *  </filters>
    + *  <Filters>
    + *   <BurstFilter level="INFO" rate="16" maxBurst="100"/>
    + *  </Filters>
    * </Console>
    *

    */ -@Plugin(name = "BurstFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin public final class BurstFilter extends AbstractFilter { private static final long NANOS_IN_SECONDS = 1000000000; @@ -272,11 +273,7 @@ public boolean equals(final Object o) { final LogDelay logDelay = (LogDelay) o; - if (expireTime != logDelay.expireTime) { - return false; - } - - return true; + return expireTime == logDelay.expireTime; } @Override @@ -285,12 +282,12 @@ public int hashCode() { } } - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - public static class Builder extends AbstractFilterBuilder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder extends AbstractFilterBuilder implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute private Level level = Level.WARN; @@ -303,7 +300,7 @@ public static class Builder extends AbstractFilterBuilder implements or /** * Sets the logging level to use. - * @param level the logging level to use. + * @param level the logging level to use. * @return this */ public Builder setLevel(final Level level) { @@ -313,7 +310,7 @@ public Builder setLevel(final Level level) { /** * Sets the average number of events per second to allow. - * @param rate the average number of events per second to allow. This must be a positive number. + * @param rate the average number of events per second to allow. This must be a positive number. * @return this */ public Builder setRate(final float rate) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java index 5394bfbf547..254c9115534 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java @@ -26,25 +26,26 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.AbstractLifeCycle; import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.LifeCycle2; +import org.apache.logging.log4j.core.LifeCycle; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.util.ObjectArrayIterator; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Composes and invokes one or more filters. */ -@Plugin(name = "filters", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin("filters") @PerformanceSensitive("allocation") public final class CompositeFilter extends AbstractLifeCycle implements Iterable, Filter { - private static final Filter[] EMPTY_FILTERS = new Filter[0]; + private static final Filter[] EMPTY_FILTERS = Filter.EMPTY_ARRAY; private final Filter[] filters; private CompositeFilter() { @@ -63,9 +64,9 @@ public CompositeFilter addFilter(final Filter filter) { if (filter instanceof CompositeFilter) { final int size = this.filters.length + ((CompositeFilter) filter).size(); final Filter[] copy = Arrays.copyOf(this.filters, size); - final int index = this.filters.length; + int index = this.filters.length; for (final Filter currentFilter : ((CompositeFilter) filter).filters) { - copy[index] = currentFilter; + copy[index++] = currentFilter; } return new CompositeFilter(copy); } @@ -90,7 +91,7 @@ public CompositeFilter removeFilter(final Filter filter) { } else { filterList.remove(filter); } - return new CompositeFilter(filterList.toArray(new Filter[this.filters.length - 1])); + return new CompositeFilter(filterList.toArray(Filter.EMPTY_ARRAY)); } @Override @@ -98,17 +99,6 @@ public Iterator iterator() { return new ObjectArrayIterator<>(filters); } - /** - * Gets a new list over the internal filter array. - * - * @return a new list over the internal filter array - * @deprecated Use {@link #getFiltersArray()} - */ - @Deprecated - public List getFilters() { - return Arrays.asList(filters); - } - public Filter[] getFiltersArray() { return filters; } @@ -139,11 +129,7 @@ public void start() { public boolean stop(final long timeout, final TimeUnit timeUnit) { this.setStopping(); for (final Filter filter : filters) { - if (filter instanceof LifeCycle2) { - ((LifeCycle2) filter).stop(timeout, timeUnit); - } else { - filter.stop(); - } + ((LifeCycle) filter).stop(timeout, timeUnit); } setStopped(); return true; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DenyAllFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DenyAllFilter.java new file mode 100644 index 00000000000..567ad95886e --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DenyAllFilter.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.PerformanceSensitive; + +/** + * This filter causes all logging events to be dropped. + */ +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin +@PerformanceSensitive("allocation") +public final class DenyAllFilter extends AbstractFilter { + + private DenyAllFilter(final Result onMatch, final Result onMismatch) { + super(onMatch, onMismatch); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object... params) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, + final Throwable t) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, + final Throwable t) { + return Result.DENY; + } + + @Override + public Result filter(final LogEvent event) { + return Result.DENY; + } + + private Result filter(final Marker marker) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + return Result.DENY; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + return Result.DENY; + } + + @Override + public String toString() { + return "DenyAll"; + } + + @PluginFactory + public static DenyAllFilter.Builder newBuilder() { + return new DenyAllFilter.Builder(); + } + + public static class Builder extends AbstractFilterBuilder implements org.apache.logging.log4j.core.util.Builder { + + @Override + public DenyAllFilter build() { + return new DenyAllFilter(this.getOnMatch(), this.getOnMismatch()); + } + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java index 923a39c3d1b..efa7c1b9e67 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java @@ -16,34 +16,40 @@ */ package org.apache.logging.log4j.core.filter; -import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.ContextDataInjector; +import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.StringMap; /** * Compares against a log level that is associated with a context value. By default the context is the * {@link ThreadContext}, but users may {@linkplain ContextDataInjectorFactory configure} a custom * {@link ContextDataInjector} which obtains context data from some other source. */ -@Plugin(name = "DynamicThresholdFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PerformanceSensitive("allocation") public final class DynamicThresholdFilter extends AbstractFilter { @@ -55,35 +61,87 @@ public final class DynamicThresholdFilter extends AbstractFilter { * @param onMatch The action to perform if a match occurs. * @param onMismatch The action to perform if no match occurs. * @return The DynamicThresholdFilter. + * @deprecated use {@link Builder} */ - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - @PluginFactory + @Deprecated(since = "3.0.0", forRemoval = true) public static DynamicThresholdFilter createFilter( - @PluginAttribute("key") final String key, - @PluginElement("Pairs") final KeyValuePair[] pairs, - @PluginAttribute("defaultThreshold") final Level defaultThreshold, - @PluginAttribute("onMatch") final Result onMatch, - @PluginAttribute("onMismatch") final Result onMismatch) { - final Map map = new HashMap<>(); - for (final KeyValuePair pair : pairs) { - map.put(pair.getKey(), Level.toLevel(pair.getValue())); + final String key, final KeyValuePair[] pairs, final Level defaultThreshold, final Result onMatch, + final Result onMismatch) { + return newBuilder() + .setKey(key) + .setPairs(pairs) + .setDefaultThreshold(defaultThreshold) + .setOnMatch(onMatch) + .setOnMismatch(onMismatch) + .setContextDataInjector(ContextDataInjectorFactory.createInjector()) + .get(); + } + + @PluginFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder extends AbstractFilter.AbstractFilterBuilder implements Supplier { + private String key; + private KeyValuePair[] pairs; + private Level defaultThreshold; + private ContextDataInjector contextDataInjector; + + public Builder setKey(@PluginAttribute final String key) { + this.key = key; + return this; + } + + public Builder setPairs(@PluginElement final KeyValuePair[] pairs) { + this.pairs = pairs; + return this; + } + + public Builder setDefaultThreshold(@PluginAttribute final Level defaultThreshold) { + this.defaultThreshold = defaultThreshold; + return this; + } + + @Inject + public Builder setContextDataInjector(final ContextDataInjector contextDataInjector) { + this.contextDataInjector = contextDataInjector; + return this; + } + + @Override + public DynamicThresholdFilter get() { + if (contextDataInjector == null) { + contextDataInjector = ContextDataInjectorFactory.createInjector(); + } + if (defaultThreshold == null) { + defaultThreshold = Level.ERROR; + } + final Map map = + Stream.of(pairs).collect(Collectors.toMap(KeyValuePair::getKey, pair -> Level.toLevel(pair.getValue()))); + return new DynamicThresholdFilter(key, map, defaultThreshold, getOnMatch(), getOnMismatch(), contextDataInjector); } - final Level level = defaultThreshold == null ? Level.ERROR : defaultThreshold; - return new DynamicThresholdFilter(key, map, level, onMatch, onMismatch); } - private Level defaultThreshold = Level.ERROR; + private final Level defaultThreshold; private final String key; - private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); - private Map levelMap = new HashMap<>(); + private final ContextDataInjector injector; + private final Map levelMap; - private DynamicThresholdFilter(final String key, final Map pairs, final Level defaultLevel, - final Result onMatch, final Result onMismatch) { + private DynamicThresholdFilter( + final String key, final Map pairs, final Level defaultLevel, + final Result onMatch, final Result onMismatch, final ContextDataInjector injector) { super(onMatch, onMismatch); + // ContextDataFactory looks up a property. The Spring PropertySource may log which will cause recursion. + // By initializing the ContextDataFactory here recursion will be prevented. + StringMap map = ContextDataFactory.createContextData(); + LOGGER.debug("Successfully initialized ContextDataFactory by retrieving the context data with {} entries", + map.size()); Objects.requireNonNull(key, "key cannot be null"); this.key = key; this.levelMap = pairs; this.defaultThreshold = defaultLevel; + this.injector = injector; } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/Filterable.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/Filterable.java index e6dd630acdb..993d04bcd29 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/Filterable.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/Filterable.java @@ -22,7 +22,7 @@ /** * Interface implemented by Classes that allow filtering to occur. - * + * *

    * Extends {@link LifeCycle} since filters have a life cycle. *

    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelMatchFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelMatchFilter.java new file mode 100644 index 00000000000..50b07f5112c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelMatchFilter.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.PerformanceSensitive; + +/** + * This filter returns the onMatch result if the logging level in the event matches the specified logging level + * exactly. + */ +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin +@PerformanceSensitive("allocation") +public final class LevelMatchFilter extends AbstractFilter { + + public static final String ATTR_MATCH = "match"; + private final Level level; + + private LevelMatchFilter(final Level level, final Result onMatch, final Result onMismatch) { + super(onMatch, onMismatch); + this.level = level; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object... params) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, + final Throwable t) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, + final Throwable t) { + return filter(level); + } + + @Override + public Result filter(final LogEvent event) { + return filter(event.getLevel()); + } + + private Result filter(final Level level) { + return level == this.level ? onMatch : onMismatch; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + return filter(level); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + return filter(level); + } + + @Override + public String toString() { + return level.toString(); + } + + @PluginFactory + public static LevelMatchFilter.Builder newBuilder() { + return new LevelMatchFilter.Builder(); + } + + public static class Builder extends AbstractFilterBuilder implements org.apache.logging.log4j.core.util.Builder { + @PluginBuilderAttribute + private Level level = Level.ERROR; + + /** + * Sets the logging level to use. + * @param level the logging level to use. + * @return this + */ + public LevelMatchFilter.Builder setLevel(final Level level) { + this.level = level; + return this; + } + + @Override + public LevelMatchFilter build() { + return new LevelMatchFilter(this.level, this.getOnMatch(), this.getOnMismatch()); + } + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java index 0e79b8508f2..c0eb6400d40 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java @@ -21,11 +21,11 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -38,7 +38,8 @@ * The default Levels are both {@link Level#ERROR ERROR}. *

    */ -@Plugin(name = "LevelRangeFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PerformanceSensitive("allocation") public final class LevelRangeFilter extends AbstractFilter { @@ -59,10 +60,10 @@ public final class LevelRangeFilter extends AbstractFilter { @PluginFactory public static LevelRangeFilter createFilter( // @formatter:off - @PluginAttribute("minLevel") final Level minLevel, - @PluginAttribute("maxLevel") final Level maxLevel, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) { + @PluginAttribute final Level minLevel, + @PluginAttribute final Level maxLevel, + @PluginAttribute final Result match, + @PluginAttribute final Result mismatch) { // @formatter:on final Level actualMinLevel = minLevel == null ? Level.ERROR : minLevel; final Level actualMaxLevel = maxLevel == null ? Level.ERROR : maxLevel; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java index 39f3f148e20..26d43a29dfb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java @@ -27,15 +27,14 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.util.BiConsumer; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; import org.apache.logging.log4j.util.IndexedStringMap; import org.apache.logging.log4j.util.PerformanceSensitive; @@ -45,7 +44,8 @@ /** * A Filter that operates on a Map. */ -@Plugin(name = "MapFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PerformanceSensitive("allocation") public class MapFilter extends AbstractFilter { @@ -212,19 +212,6 @@ protected boolean isAnd() { return isAnd; } - /** @deprecated use {@link #getStringMap()} instead */ - @Deprecated - protected Map> getMap() { - final Map> result = new HashMap<>(map.size()); - map.forEach(new BiConsumer>() { - @Override - public void accept(final String key, final List value) { - result.put(key, value); - } - }); - return result; - } - /** * Returns the IndexedStringMap with {@code List} values that this MapFilter was constructed with. * @return the IndexedStringMap with {@code List} values to match against @@ -237,10 +224,10 @@ protected IndexedReadOnlyStringMap getStringMap() { // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder @PluginFactory public static MapFilter createFilter( - @PluginElement("Pairs") final KeyValuePair[] pairs, - @PluginAttribute("operator") final String oper, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) { + @PluginElement final KeyValuePair[] pairs, + @PluginAttribute final String operator, + @PluginAttribute final Result onMatch, + @PluginAttribute final Result onMismatch) { if (pairs == null || pairs.length == 0) { LOGGER.error("keys and values must be specified for the MapFilter"); return null; @@ -270,7 +257,7 @@ public static MapFilter createFilter( LOGGER.error("MapFilter is not configured with any valid key value pairs"); return null; } - final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or"); - return new MapFilter(map, isAnd, match, mismatch); + final boolean isAnd = operator == null || !operator.equalsIgnoreCase("or"); + return new MapFilter(map, isAnd, onMatch, onMismatch); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java index b828e316fe8..bdcc982ba71 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java @@ -21,22 +21,22 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.PerformanceSensitive; /** * This filter returns the onMatch result if the marker in the LogEvent is the same as or has the * configured marker as a parent. */ -@Plugin(name = "MarkerFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PerformanceSensitive("allocation") public final class MarkerFilter extends AbstractFilter { - public static final String ATTR_MARKER = "marker"; private final String name; private MarkerFilter(final String name, final Result onMatch, final Result onMismatch) { @@ -148,22 +148,22 @@ public String toString() { /** * Creates the MarkerFilter. * @param marker The Marker name to match. - * @param match The action to take if a match occurs. - * @param mismatch The action to take if no match occurs. + * @param onMatch The action to take if a match occurs. + * @param onMismatch The action to take if no match occurs. * @return A MarkerFilter. */ // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder @PluginFactory public static MarkerFilter createFilter( - @PluginAttribute(ATTR_MARKER) final String marker, - @PluginAttribute(AbstractFilterBuilder.ATTR_ON_MATCH) final Result match, - @PluginAttribute(AbstractFilterBuilder.ATTR_ON_MISMATCH) final Result mismatch) { + @PluginAttribute final String marker, + @PluginAttribute final Result onMatch, + @PluginAttribute final Result onMismatch) { if (marker == null) { LOGGER.error("A marker must be provided for MarkerFilter"); return null; } - return new MarkerFilter(marker, match, mismatch); + return new MarkerFilter(marker, onMatch, onMismatch); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java new file mode 100644 index 00000000000..7d7bf2f0f68 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java @@ -0,0 +1,416 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationException; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationScheduler; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.filter.mutable.KeyValuePairConfig; +import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; +import org.apache.logging.log4j.core.util.AuthorizationProvider; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.core.util.internal.HttpInputStreamUtil; +import org.apache.logging.log4j.core.util.internal.LastModifiedSource; +import org.apache.logging.log4j.core.util.internal.Status; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.PerformanceSensitive; +import org.apache.logging.log4j.util.PropertiesUtil; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Filter based on a value in the Thread Context Map (MDC). + */ +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin +@PluginAliases("MutableContextMapFilter") +@PerformanceSensitive("allocation") +public class MutableThreadContextMapFilter extends AbstractFilter { + + private static final ObjectMapper MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + private static final KeyValuePair[] EMPTY_ARRAY = {}; + + private volatile Filter filter; + private final long pollInterval; + private final ConfigurationScheduler scheduler; + private final LastModifiedSource source; + private final AuthorizationProvider authorizationProvider; + private final List listeners = new ArrayList<>(); + private ScheduledFuture future = null; + + private MutableThreadContextMapFilter(final Filter filter, final LastModifiedSource source, + final long pollInterval, final AuthorizationProvider authorizationProvider, + final Result onMatch, final Result onMismatch, final Configuration configuration) { + super(onMatch, onMismatch); + this.filter = filter; + this.pollInterval = pollInterval; + this.source = source; + this.scheduler = configuration.getScheduler(); + this.authorizationProvider = authorizationProvider; + } + + @Override + public void start() { + + if (pollInterval > 0) { + future = scheduler.scheduleWithFixedDelay(new FileMonitor(), 0, pollInterval, TimeUnit.SECONDS); + LOGGER.debug("Watching {} with poll interval {}", source.toString(), pollInterval); + } + super.start(); + } + + @Override + public boolean stop(long timeout, TimeUnit timeUnit) { + future.cancel(true); + return super.stop(timeout, timeUnit); + } + + public void registerListener(FilterConfigUpdateListener listener) { + listeners.add(listener); + } + + @PluginFactory + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public Result filter(LogEvent event) { + return filter.filter(event); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { + return filter.filter(logger, level, marker, msg, t); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { + return filter.filter(logger, level, marker, msg, t); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) { + return filter.filter(logger, level, marker, msg, params); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0) { + return filter.filter(logger, level, marker, msg, p0); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1) { + return filter.filter(logger, level, marker, msg, p0, p1); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2) { + return filter.filter(logger, level, marker, msg, p0, p1, p2); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2, Object p3) { + return filter.filter(logger, level, marker, msg, p0, p1, p2, p3); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2, Object p3, Object p4) { + return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2, Object p3, Object p4, Object p5) { + return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2, Object p3, Object p4, Object p5, Object p6) { + return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2, Object p3, Object p4, Object p5, Object p6, Object p7) { + return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6, p7); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8) { + return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6, p7, p8); + } + + @Override + public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, + Object p1, + Object p2, Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9) { + return filter.filter(logger, level, marker, msg, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); + } + + public static class Builder extends AbstractFilterBuilder + implements org.apache.logging.log4j.core.util.Builder { + @PluginAttribute + private String configLocation; + + @PluginAttribute + private long pollInterval; + + @PluginConfiguration + private Configuration configuration; + + /** + * Sets the Configuration. + * @param configuration The Configuration. + * @return this. + */ + public Builder setConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + + /** + * Set the frequency in seconds that changes to the list a ThreadContext valudes should be + * checked. + * @param pollInterval interval in seconds to check the file for changes. + * @return this. + */ + public Builder setPollInterval(final long pollInterval) { + this.pollInterval = pollInterval; + return this; + } + + /** + * Sets the configuration to use. + * @param configLocation the location of the configuration. + * @return this + */ + public Builder setConfigLocation(final String configLocation) { + this.configLocation = configLocation; + return this; + } + + @Override + public MutableThreadContextMapFilter build() { + final LastModifiedSource source = getSource(configLocation); + if (source == null) { + return new MutableThreadContextMapFilter(new NoOpFilter(), null, 0, + null, getOnMatch(), getOnMismatch(), configuration); + } + final AuthorizationProvider authorizationProvider = + ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties()); + Filter filter; + if (pollInterval <= 0) { + ConfigResult result = getConfig(source, authorizationProvider); + if (result.status == Status.SUCCESS) { + if (result.pairs.length > 0) { + filter = ThreadContextMapFilter.createFilter(result.pairs, "or", + getOnMatch(), getOnMismatch()); + } else { + filter = new NoOpFilter(); + } + } else if (result.status == Status.NOT_FOUND || result.status == Status.EMPTY) { + filter = new NoOpFilter(); + } else { + LOGGER.warn("Unexpected response returned on initial call: {}", result.status); + filter = new NoOpFilter(); + } + } else { + filter = new NoOpFilter(); + } + + if (pollInterval > 0) { + configuration.getScheduler().incrementScheduledItems(); + } + return new MutableThreadContextMapFilter(filter, source, pollInterval, authorizationProvider, + getOnMatch(), getOnMismatch(), configuration); + } + } + + private class FileMonitor implements Runnable { + + @Override + public void run() { + final ConfigResult result = getConfig(source, authorizationProvider); + if (result.status == Status.SUCCESS) { + filter = ThreadContextMapFilter.newBuilder() + .setPairs(result.pairs) + .setOperator("or") + .setOnMatch(getOnMatch()) + .setOnMismatch(getOnMismatch()) + .setContextDataInjector(ContextDataInjectorFactory.createInjector()) + .get(); + LOGGER.info("Filter configuration was updated: {}", filter.toString()); + for (FilterConfigUpdateListener listener : listeners) { + listener.onEvent(); + } + } else if (result.status == Status.NOT_FOUND) { + if (!(filter instanceof NoOpFilter)) { + LOGGER.info("Filter configuration was removed"); + filter = new NoOpFilter(); + for (FilterConfigUpdateListener listener : listeners) { + listener.onEvent(); + } + } + } else if (result.status == Status.EMPTY) { + LOGGER.debug("Filter configuration is empty"); + filter = new NoOpFilter(); + } + } + } + + private static LastModifiedSource getSource(final String configLocation) { + LastModifiedSource source = null; + try { + final URI uri = new URI(configLocation); + if (uri.getScheme() != null) { + source = new LastModifiedSource(new URI(configLocation)); + } else { + source = new LastModifiedSource(new File(configLocation)); + } + + } catch (Exception ex) { + source = new LastModifiedSource(new File(configLocation)); + } + return source; + } + + private static ConfigResult getConfig(final LastModifiedSource source, + final AuthorizationProvider authorizationProvider) { + final File inputFile = source.getFile(); + InputStream inputStream = null; + HttpInputStreamUtil.Result result = null; + final long lastModified = source.getLastModified(); + if (inputFile != null && inputFile.exists()) { + try { + final long modified = inputFile.lastModified(); + if (modified > lastModified) { + source.setLastModified(modified); + inputStream = new FileInputStream(inputFile); + result = new HttpInputStreamUtil.Result(Status.SUCCESS); + } else { + result = new HttpInputStreamUtil.Result(Status.NOT_MODIFIED); + } + } catch (Exception ex) { + result = new HttpInputStreamUtil.Result(Status.ERROR); + } + } else if (source.getURI() != null) { + try { + result = HttpInputStreamUtil.getInputStream(source, authorizationProvider); + inputStream = result.getInputStream(); + } catch (ConfigurationException ex) { + result = new HttpInputStreamUtil.Result(Status.ERROR); + } + } else { + result = new HttpInputStreamUtil.Result(Status.NOT_FOUND); + } + final ConfigResult configResult = new ConfigResult(); + if (result.getStatus() == Status.SUCCESS) { + LOGGER.debug("Processing Debug key/value pairs from: {}", source.toString()); + try { + final KeyValuePairConfig keyValuePairConfig = MAPPER.readValue(inputStream, KeyValuePairConfig.class); + if (keyValuePairConfig != null) { + final Map configs = keyValuePairConfig.getConfigs(); + if (configs != null && configs.size() > 0) { + final List pairs = new ArrayList<>(); + for (Map.Entry entry : configs.entrySet()) { + final String key = entry.getKey(); + for (final String value : entry.getValue()) { + if (value != null) { + pairs.add(new KeyValuePair(key, value)); + } else { + LOGGER.warn("Ignoring null value for {}", key); + } + } + } + if (pairs.size() > 0) { + configResult.pairs = pairs.toArray(EMPTY_ARRAY); + configResult.status = Status.SUCCESS; + } else { + configResult.status = Status.EMPTY; + } + } else { + LOGGER.debug("No configuration data in {}", source.toString()); + configResult.status = Status.EMPTY; + } + } else { + LOGGER.warn("No configs element in MutableThreadContextMapFilter configuration"); + configResult.status = Status.ERROR; + } + } catch (Exception ex) { + LOGGER.warn("Invalid key/value pair configuration, input ignored: {}", ex.getMessage()); + configResult.status = Status.ERROR; + } + } else { + configResult.status = result.getStatus(); + } + return configResult; + } + + private static class NoOpFilter extends AbstractFilter { + + public NoOpFilter() { + super(Result.NEUTRAL, Result.NEUTRAL); + } + } + + public interface FilterConfigUpdateListener { + void onEvent(); + } + + private static class ConfigResult extends HttpInputStreamUtil.Result { + public KeyValuePair[] pairs; + public Status status; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NeutralFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NeutralFilter.java new file mode 100644 index 00000000000..cfb6425e6f8 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NeutralFilter.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.message.Message; + +/** + * A neutral filter where all filter methods return {@link org.apache.logging.log4j.core.Filter.Result#NEUTRAL}. + */ +public class NeutralFilter extends AbstractFilter { + + public static final NeutralFilter INSTANCE = new NeutralFilter(); + + @Override + public Result filter(final LogEvent event) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2, + final Object p3) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, final Object p7) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) { + return Result.NEUTRAL; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object p0, final Object p1, final Object p2, + final Object p3, final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) { + return Result.NEUTRAL; + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NoMarkerFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NoMarkerFilter.java new file mode 100644 index 00000000000..01b970ba238 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NoMarkerFilter.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.PerformanceSensitive; + +/** + * This filter returns the onMatch result if there is no marker in the LogEvent. + */ +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin +@PerformanceSensitive("allocation") +public final class NoMarkerFilter extends AbstractFilter { + + private NoMarkerFilter(final Result onMatch, final Result onMismatch) { + super(onMatch, onMismatch); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object... params) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, + final Throwable t) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, + final Throwable t) { + return filter(marker); + } + + @Override + public Result filter(final LogEvent event) { + return filter(event.getMarker()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + return filter(marker); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + return filter(marker); + } + + private Result filter(final Marker marker) { + return null == marker ? onMatch : onMismatch; + } + + + @PluginFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder extends AbstractFilterBuilder implements org.apache.logging.log4j.core.util.Builder { + + @Override + public NoMarkerFilter build() { + return new NoMarkerFilter(this.getOnMatch(), this.getOnMismatch()); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java index 6b18b6b0357..4123d64c8a7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java @@ -16,23 +16,24 @@ */ package org.apache.logging.log4j.core.filter; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Comparator; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * This filter returns the onMatch result if the message matches the regular expression. @@ -41,7 +42,8 @@ * calling Message.getMessageFormat (true) or Message.getFormattedMessage() (false). The default is false. * */ -@Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin public final class RegexFilter extends AbstractFilter { private static final int DEFAULT_PATTERN_FLAGS = 0; @@ -57,7 +59,10 @@ private RegexFilter(final boolean raw, final Pattern pattern, final Result onMat @Override public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) { - return filter(msg); + if (useRawMessage || params == null || params.length == 0) { + return filter(msg); + } + return filter(ParameterizedMessage.format(msg, params)); } @Override @@ -107,12 +112,12 @@ public String toString() { * @param regex * The regular expression to match. * @param patternFlags - * An array of Stirngs where each String is a {@link Pattern#compile(String, int)} compilation flag. + * An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag. * @param useRawMsg * If true, the raw message will be used, otherwise the formatted message will be used. - * @param match + * @param onMatch * The action to perform when a match occurs. - * @param mismatch + * @param onMismatch * The action to perform when a mismatch occurs. * @return The RegexFilter. * @throws IllegalAccessException @@ -122,18 +127,18 @@ public String toString() { @PluginFactory public static RegexFilter createFilter( //@formatter:off - @PluginAttribute("regex") final String regex, - @PluginElement("PatternFlags") final String[] patternFlags, - @PluginAttribute("useRawMsg") final Boolean useRawMsg, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) + @PluginAttribute final String regex, + @PluginElement final String[] patternFlags, + @PluginAttribute final Boolean useRawMsg, + @PluginAttribute final Result onMatch, + @PluginAttribute final Result onMismatch) //@formatter:on throws IllegalArgumentException, IllegalAccessException { if (regex == null) { LOGGER.error("A regular expression must be provided for RegexFilter"); return null; } - return new RegexFilter(useRawMsg, Pattern.compile(regex, toPatternFlags(patternFlags)), match, mismatch); + return new RegexFilter(useRawMsg, Pattern.compile(regex, toPatternFlags(patternFlags)), onMatch, onMismatch); } private static int toPatternFlags(final String[] patternFlags) throws IllegalArgumentException, @@ -142,13 +147,7 @@ private static int toPatternFlags(final String[] patternFlags) throws IllegalArg return DEFAULT_PATTERN_FLAGS; } final Field[] fields = Pattern.class.getDeclaredFields(); - final Comparator comparator = new Comparator() { - - @Override - public int compare(final Field f1, final Field f2) { - return f1.getName().compareTo(f2.getName()); - } - }; + final Comparator comparator = (f1, f2) -> f1.getName().compareTo(f2.getName()); Arrays.sort(fields, comparator); final String[] fieldNames = new String[fields.length]; for (int i = 0; i < fields.length; i++) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java deleted file mode 100644 index 1e47d4e759e..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.filter; - -import javax.script.SimpleBindings; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.script.AbstractScript; -import org.apache.logging.log4j.core.script.ScriptRef; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * Returns the onMatch result if the script returns True and returns the onMismatch value otherwise. - */ -@Plugin(name = "ScriptFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) -public final class ScriptFilter extends AbstractFilter { - - private static org.apache.logging.log4j.Logger logger = StatusLogger.getLogger(); - - private final AbstractScript script; - private final Configuration configuration; - - private ScriptFilter(final AbstractScript script, final Configuration configuration, final Result onMatch, - final Result onMismatch) { - super(onMatch, onMismatch); - this.script = script; - this.configuration = configuration; - if (!(script instanceof ScriptRef)) { - configuration.getScriptManager().addScript(script); - } - } - - @Override - public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, - final Object... params) { - final SimpleBindings bindings = new SimpleBindings(); - bindings.put("logger", logger); - bindings.put("level", level); - bindings.put("marker", marker); - bindings.put("message", new SimpleMessage(msg)); - bindings.put("parameters", params); - bindings.put("throwable", null); - bindings.putAll(configuration.getProperties()); - bindings.put("substitutor", configuration.getStrSubstitutor()); - final Object object = configuration.getScriptManager().execute(script.getName(), bindings); - return object == null || !Boolean.TRUE.equals(object) ? onMismatch : onMatch; - } - - @Override - public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, - final Throwable t) { - final SimpleBindings bindings = new SimpleBindings(); - bindings.put("logger", logger); - bindings.put("level", level); - bindings.put("marker", marker); - bindings.put("message", msg instanceof String ? new SimpleMessage((String)msg) : new ObjectMessage(msg)); - bindings.put("parameters", null); - bindings.put("throwable", t); - bindings.putAll(configuration.getProperties()); - bindings.put("substitutor", configuration.getStrSubstitutor()); - final Object object = configuration.getScriptManager().execute(script.getName(), bindings); - return object == null || !Boolean.TRUE.equals(object) ? onMismatch : onMatch; - } - - @Override - public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, - final Throwable t) { - final SimpleBindings bindings = new SimpleBindings(); - bindings.put("logger", logger); - bindings.put("level", level); - bindings.put("marker", marker); - bindings.put("message", msg); - bindings.put("parameters", null); - bindings.put("throwable", t); - bindings.putAll(configuration.getProperties()); - bindings.put("substitutor", configuration.getStrSubstitutor()); - final Object object = configuration.getScriptManager().execute(script.getName(), bindings); - return object == null || !Boolean.TRUE.equals(object) ? onMismatch : onMatch; - } - - @Override - public Result filter(final LogEvent event) { - final SimpleBindings bindings = new SimpleBindings(); - bindings.put("logEvent", event); - bindings.putAll(configuration.getProperties()); - bindings.put("substitutor", configuration.getStrSubstitutor()); - final Object object = configuration.getScriptManager().execute(script.getName(), bindings); - return object == null || !Boolean.TRUE.equals(object) ? onMismatch : onMatch; - } - - @Override - public String toString() { - return script.getName(); - } - - /** - * Creates the ScriptFilter. - * @param script The script to run. The script must return a boolean value. Either script or scriptFile must be - * provided. - * @param match The action to take if a match occurs. - * @param mismatch The action to take if no match occurs. - * @param configuration the configuration - * @return A ScriptFilter. - */ - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - @PluginFactory - public static ScriptFilter createFilter( - @PluginElement("Script") final AbstractScript script, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch, - @PluginConfiguration final Configuration configuration) { - - if (script == null) { - LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter"); - return null; - } - if (script instanceof ScriptRef) { - if (configuration.getScriptManager().getScript(script.getName()) == null) { - logger.error("No script with name {} has been declared.", script.getName()); - return null; - } - } - - return new ScriptFilter(script, configuration, match, mismatch); - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java new file mode 100644 index 00000000000..35422696838 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.PerformanceSensitive; + +/** + * This filter returns the onMatch result if the logging level in the event matches the specified logging level + * exactly. + */ +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin +@PerformanceSensitive("allocation") +public final class StringMatchFilter extends AbstractFilter { + + public static final String ATTR_MATCH = "match"; + private final String text; + + private StringMatchFilter(final String text, final Result onMatch, final Result onMismatch) { + super(onMatch, onMismatch); + this.text = text; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object... params) { + return filter(logger.getMessageFactory().newMessage(msg, params).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, + final Throwable t) { + return filter(logger.getMessageFactory().newMessage(msg).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, + final Throwable t) { + return filter(msg.getFormattedMessage()); + } + + @Override + public Result filter(final LogEvent event) { + return filter(event.getMessage().getFormattedMessage()); + } + + private Result filter(final String msg) { + return msg.contains(this.text) ? onMatch : onMismatch; + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0) { + return filter(logger.getMessageFactory().newMessage(msg, p0).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7).getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7, p8) + .getFormattedMessage()); + } + + @Override + public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, + final Object p0, final Object p1, final Object p2, final Object p3, + final Object p4, final Object p5, final Object p6, + final Object p7, final Object p8, final Object p9) { + return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9) + .getFormattedMessage()); + } + + @Override + public String toString() { + return text; + } + + @PluginFactory + public static StringMatchFilter.Builder newBuilder() { + return new StringMatchFilter.Builder(); + } + + public static class Builder extends AbstractFilterBuilder implements org.apache.logging.log4j.core.util.Builder { + @PluginBuilderAttribute + private String text = ""; + + /** + * Sets the logging level to use. + * @param level the logging level to use. + * @return this + */ + public StringMatchFilter.Builder setMatchString(final String text) { + this.text = text; + return this; + } + + @Override + public StringMatchFilter build() { + return new StringMatchFilter(this.text, this.getOnMatch(), this.getOnMismatch()); + } + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java index 91a992400f1..e978dbc65ce 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java @@ -26,14 +26,14 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.StructuredDataMessage; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilders; @@ -41,12 +41,13 @@ /** * Filter based on data in a StructuredDataMessage. */ -@Plugin(name = "StructuredDataFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PerformanceSensitive("allocation") public final class StructuredDataFilter extends MapFilter { private static final int MAX_BUFFER_SIZE = 2048; - private static ThreadLocal threadLocalStringBuilder = new ThreadLocal<>(); + private static final ThreadLocal threadLocalStringBuilder = new ThreadLocal<>(); private StructuredDataFilter(final Map> map, final boolean oper, final Result onMatch, final Result onMismatch) { @@ -149,18 +150,18 @@ private boolean listContainsValue(final List candidates, final StringBui /** * Creates the StructuredDataFilter. * @param pairs Key and value pairs. - * @param oper The operator to perform. If not "or" the operation will be an "and". - * @param match The action to perform on a match. - * @param mismatch The action to perform on a mismatch. + * @param operator The operator to perform. If not "or" the operation will be an "and". + * @param onMatch The action to perform on a match. + * @param onMismatch The action to perform on a mismatch. * @return The StructuredDataFilter. */ // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder @PluginFactory public static StructuredDataFilter createFilter( - @PluginElement("Pairs") final KeyValuePair[] pairs, - @PluginAttribute("operator") final String oper, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) { + @PluginElement final KeyValuePair[] pairs, + @PluginAttribute final String operator, + @PluginAttribute final Result onMatch, + @PluginAttribute final Result onMismatch) { if (pairs == null || pairs.length == 0) { LOGGER.error("keys and values must be specified for the StructuredDataFilter"); return null; @@ -190,7 +191,7 @@ public static StructuredDataFilter createFilter( LOGGER.error("StructuredDataFilter is not configured with any valid key value pairs"); return null; } - final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or"); - return new StructuredDataFilter(map, isAnd, match, mismatch); + final boolean isAnd = operator == null || !operator.equalsIgnoreCase("or"); + return new StructuredDataFilter(map, isAnd, onMatch, onMismatch); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java index 7f11e322a7d..37919806958 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; @@ -28,36 +29,47 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAliases; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.impl.ContextDataFactory; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAliases; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.Required; import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.StringMap; /** * Filter based on a value in the Thread Context Map (MDC). */ -@Plugin(name = "ThreadContextMapFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PluginAliases("ContextMapFilter") @PerformanceSensitive("allocation") public class ThreadContextMapFilter extends MapFilter { - private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); + private final ContextDataInjector injector; private final String key; private final String value; private final boolean useMap; - public ThreadContextMapFilter(final Map> pairs, final boolean oper, final Result onMatch, - final Result onMismatch) { + public ThreadContextMapFilter( + final Map> pairs, final boolean oper, final Result onMatch, + final Result onMismatch, final ContextDataInjector injector) { super(pairs, oper, onMatch, onMismatch); + // ContextDataFactory looks up a property. The Spring PropertySource may log which will cause recursion. + // By initializing the ContextDataFactory here recursion will be prevented. + StringMap map = ContextDataFactory.createContextData(); + LOGGER.debug("Successfully initialized ContextDataFactory by retrieving the context data with {} entries", + map.size()); if (pairs.size() == 1) { final Iterator>> iter = pairs.entrySet().iterator(); final Map.Entry> entry = iter.next(); @@ -75,6 +87,7 @@ public ThreadContextMapFilter(final Map> pairs, final boole this.value = null; this.useMap = true; } + this.injector = injector; } @Override @@ -194,43 +207,77 @@ public Result filter(final Logger logger, final Level level, final Marker marker return filter(); } - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - @PluginFactory - public static ThreadContextMapFilter createFilter( - @PluginElement("Pairs") final KeyValuePair[] pairs, - @PluginAttribute("operator") final String oper, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) { - if (pairs == null || pairs.length == 0) { - LOGGER.error("key and value pairs must be specified for the ThreadContextMapFilter"); - return null; + public static class Builder extends AbstractFilterBuilder implements Supplier { + private KeyValuePair[] pairs; + private String operator; + private ContextDataInjector contextDataInjector; + + public Builder setPairs(@Required @PluginElement final KeyValuePair[] pairs) { + this.pairs = pairs; + return this; + } + + public Builder setOperator(@PluginAttribute final String operator) { + this.operator = operator; + return this; } - final Map> map = new HashMap<>(); - for (final KeyValuePair pair : pairs) { - final String key = pair.getKey(); - if (key == null) { - LOGGER.error("A null key is not valid in MapFilter"); - continue; + + @Inject + public Builder setContextDataInjector(final ContextDataInjector contextDataInjector) { + this.contextDataInjector = contextDataInjector; + return this; + } + + @Override + public ThreadContextMapFilter get() { + if (pairs == null || pairs.length == 0) { + LOGGER.error("key and value pairs must be specified for the ThreadContextMapFilter"); + return null; } - final String value = pair.getValue(); - if (value == null) { - LOGGER.error("A null value for key " + key + " is not allowed in MapFilter"); - continue; + final Map> map = new HashMap<>(); + for (final KeyValuePair pair : pairs) { + final String key = pair.getKey(); + if (key == null) { + LOGGER.error("A null key is not valid in MapFilter"); + continue; + } + final String value = pair.getValue(); + if (value == null) { + LOGGER.error("A null value for key " + key + " is not allowed in MapFilter"); + continue; + } + List list = map.get(pair.getKey()); + if (list != null) { + list.add(value); + } else { + list = new ArrayList<>(); + list.add(value); + map.put(pair.getKey(), list); + } } - List list = map.get(pair.getKey()); - if (list != null) { - list.add(value); - } else { - list = new ArrayList<>(); - list.add(value); - map.put(pair.getKey(), list); + if (map.isEmpty()) { + LOGGER.error("ThreadContextMapFilter is not configured with any valid key value pairs"); + return null; } + final boolean isAnd = operator == null || !operator.equalsIgnoreCase("or"); + return new ThreadContextMapFilter(map, isAnd, getOnMatch(), getOnMismatch(), contextDataInjector); } - if (map.isEmpty()) { - LOGGER.error("ThreadContextMapFilter is not configured with any valid key value pairs"); - return null; - } - final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or"); - return new ThreadContextMapFilter(map, isAnd, match, mismatch); + } + + @PluginFactory + public static Builder newBuilder() { + return new Builder(); + } + + @Deprecated(since = "3.0.0", forRemoval = true) + public static ThreadContextMapFilter createFilter( + final KeyValuePair[] pairs, final String operator, final Result onMatch, final Result onMismatch) { + return newBuilder() + .setPairs(pairs) + .setOperator(operator) + .setOnMatch(onMatch) + .setOnMismatch(onMismatch) + .setContextDataInjector(ContextDataInjectorFactory.createInjector()) + .get(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java index ae9d12671f3..1a55c8e4b4a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java @@ -21,11 +21,11 @@ import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -36,7 +36,8 @@ * * The default Level is ERROR. */ -@Plugin(name = "ThresholdFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PerformanceSensitive("allocation") public final class ThresholdFilter extends AbstractFilter { @@ -162,7 +163,7 @@ public String toString() { // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder @PluginFactory public static ThresholdFilter createFilter( - @PluginAttribute("level") final Level level, + @PluginAttribute final Level level, @PluginAttribute("onMatch") final Result match, @PluginAttribute("onMismatch") final Result mismatch) { final Level actualLevel = level == null ? Level.ERROR : level; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java index 3d01e358bc4..c321724abc2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java @@ -16,89 +16,117 @@ */ package org.apache.logging.log4j.core.filter; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.TimeZone; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.function.Supplier; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.util.Clock; -import org.apache.logging.log4j.core.util.ClockFactory; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.ClockFactory; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Filters events that fall within a specified time period in each day. */ -@Plugin(name = "TimeFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin @PerformanceSensitive("allocation") public final class TimeFilter extends AbstractFilter { - private static final Clock CLOCK = ClockFactory.getClock(); + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); /** * Length of hour in milliseconds. */ private static final long HOUR_MS = 3600000; - /** - * Length of minute in milliseconds. - */ - private static final long MINUTE_MS = 60000; - - /** - * Length of second in milliseconds. - */ - private static final long SECOND_MS = 1000; + private static final long DAY_MS = HOUR_MS * 24; /** * Starting offset from midnight in milliseconds. */ - private final long start; + private volatile long start; + private final LocalTime startTime; + /** * Ending offset from midnight in milliseconds. */ - private final long end; + private volatile long end; + private final LocalTime endTime; + + private final long duration; + /** * Timezone. */ - private final TimeZone timezone; + private final ZoneId timeZone; - private long midnightToday; - private long midnightTomorrow; + private final Clock clock; - - private TimeFilter(final long start, final long end, final TimeZone tz, final Result onMatch, - final Result onMismatch) { + /* + * Expose for unit testing. + */ + TimeFilter( + final LocalTime start, final LocalTime end, final ZoneId timeZone, final Result onMatch, + final Result onMismatch, final LocalDate now, final Clock clock) { super(onMatch, onMismatch); - this.start = start; - this.end = end; - timezone = tz; - initMidnight(start); + this.startTime = start; + this.endTime = end; + this.timeZone = timeZone; + this.start = ZonedDateTime.of(now, startTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli(); + long endMillis = ZonedDateTime.of(now, endTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli(); + if (end.isBefore(start)) { + // End time must be tomorrow. + endMillis += DAY_MS; + } + duration = startTime.isBefore(endTime) ? Duration.between(startTime, endTime).toMillis() : + Duration.between(startTime, endTime).plusHours(24).toMillis(); + final long difference = (endMillis - this.start) - duration; + if (difference != 0) { + // Handle switch from standard time to daylight time and daylight time to standard time. + endMillis -= difference; + } + this.end = endMillis; + this.clock = clock; } - /** - * Initializes the midnight boundaries to midnight in the specified time zone. - * @param now a time in milliseconds since the epoch, used to pinpoint the current date - */ - void initMidnight(final long now) { - final Calendar calendar = Calendar.getInstance(timezone); - calendar.setTimeInMillis(now); - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.set(Calendar.MINUTE, 0); - calendar.set(Calendar.SECOND, 0); - calendar.set(Calendar.MILLISECOND, 0); - midnightToday = calendar.getTimeInMillis(); - - calendar.add(Calendar.DATE, 1); - midnightTomorrow = calendar.getTimeInMillis(); + private TimeFilter( + final LocalTime start, final LocalTime end, final ZoneId timeZone, final Result onMatch, + final Result onMismatch, final Clock clock) { + this(start, end, timeZone, onMatch, onMismatch, LocalDate.now(timeZone), clock); + } + + private synchronized void adjustTimes(final long currentTimeMillis) { + if (currentTimeMillis <= end) { + return; + } + final LocalDate date = Instant.ofEpochMilli(currentTimeMillis).atZone(timeZone).toLocalDate(); + this.start = ZonedDateTime.of(date, startTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli(); + long endMillis = ZonedDateTime.of(date, endTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli(); + if (endTime.isBefore(startTime)) { + // End time must be tomorrow. + endMillis += DAY_MS; + } + final long difference = (endMillis - this.start) - duration; + if (difference != 0) { + // Handle switch from standard time to daylight time and daylight time to standard time. + endMillis -= difference; + } + this.end = endMillis; } /** @@ -109,12 +137,10 @@ void initMidnight(final long now) { * @return the action to perform */ Result filter(final long currentTimeMillis) { - if (currentTimeMillis >= midnightTomorrow || currentTimeMillis < midnightToday) { - initMidnight(currentTimeMillis); + if (currentTimeMillis > end) { + adjustTimes(currentTimeMillis); } - return currentTimeMillis >= midnightToday + start && currentTimeMillis <= midnightToday + end // - ? onMatch // within window - : onMismatch; + return currentTimeMillis >= start && currentTimeMillis <= end ? onMatch : onMismatch; } @Override @@ -123,7 +149,7 @@ public Result filter(final LogEvent event) { } private Result filter() { - return filter(CLOCK.currentTimeMillis()); + return filter(clock.currentTimeMillis()); } @Override @@ -213,7 +239,7 @@ public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("start=").append(start); sb.append(", end=").append(end); - sb.append(", timezone=").append(timezone.toString()); + sb.append(", timezone=").append(timeZone.toString()); return sb.toString(); } @@ -226,34 +252,79 @@ public String toString() { * @param mismatch Action to perform if the action does not match. * @return A TimeFilter. */ - // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder - @PluginFactory + @Deprecated(since = "3.0.0", forRemoval = true) public static TimeFilter createFilter( - @PluginAttribute("start") final String start, - @PluginAttribute("end") final String end, - @PluginAttribute("timezone") final String tz, - @PluginAttribute("onMatch") final Result match, - @PluginAttribute("onMismatch") final Result mismatch) { - final long s = parseTimestamp(start, 0); - final long e = parseTimestamp(end, Long.MAX_VALUE); - final TimeZone timezone = tz == null ? TimeZone.getDefault() : TimeZone.getTimeZone(tz); - final Result onMatch = match == null ? Result.NEUTRAL : match; - final Result onMismatch = mismatch == null ? Result.DENY : mismatch; - return new TimeFilter(s, e, timezone, onMatch, onMismatch); + final String start, final String end, final String tz, final Result match, final Result mismatch) { + final Builder builder = newBuilder() + .setStart(start) + .setEnd(end); + if (tz != null) { + builder.setTimezone(ZoneId.of(tz)); + } + if (match != null) { + builder.setOnMatch(match); + } + if (mismatch != null) { + builder.setOnMismatch(mismatch); + } + return builder.get(); } - private static long parseTimestamp(final String timestamp, final long defaultValue) { + private static LocalTime parseTimestamp(final String timestamp, final LocalTime defaultValue) { if (timestamp == null) { return defaultValue; } - final SimpleDateFormat stf = new SimpleDateFormat("HH:mm:ss"); - stf.setTimeZone(TimeZone.getTimeZone("UTC")); + try { - return stf.parse(timestamp).getTime(); - } catch (final ParseException e) { + return LocalTime.parse(timestamp, FORMATTER); + } catch (final Exception e) { LOGGER.warn("Error parsing TimeFilter timestamp value {}", timestamp, e); return defaultValue; } } + @PluginFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder extends AbstractFilterBuilder implements Supplier { + private String start; + private String end; + @PluginAttribute + private ZoneId timezone = ZoneId.systemDefault(); + private Clock clock; + + public Builder setStart(@PluginAttribute final String start) { + this.start = start; + return this; + } + + public Builder setEnd(@PluginAttribute final String end) { + this.end = end; + return this; + } + + public Builder setTimezone(final ZoneId timezone) { + this.timezone = timezone; + return this; + } + + @Inject + public Builder setClock(final Clock clock) { + this.clock = clock; + return this; + } + + @Override + public TimeFilter get() { + final LocalTime startTime = parseTimestamp(start, LocalTime.MIN); + final LocalTime endTime = parseTimestamp(end, LocalTime.MAX); + if (clock == null) { + clock = ClockFactory.getClock(); + } + return new TimeFilter(startTime, endTime, timezone, getOnMatch(), getOnMismatch(), clock); + } + } + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/mutable/KeyValuePairConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/mutable/KeyValuePairConfig.java new file mode 100644 index 00000000000..564bbaaa01b --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/mutable/KeyValuePairConfig.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.filter.mutable; + +import java.util.Map; + +/** + * Class representing the configuration of KeyValue pairs. + */ +public class KeyValuePairConfig { + + /** + * Map of keys and values for the MutableThreadContextMapFilter. Example file: + *
    +     *   {
    +     *     "config": {
    +     *       "loginId": ["rgoers", "adam"],
    +     *       "accountNumber": ["30510263"]
    +     *   }
    +     * }
    +     * 
    + */ + private Map configs; + + public Map getConfigs() { + return configs; + } + + public void setConfigs(Map configs) { + this.configs = configs; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java index 8c4d417cca5..935e8357d79 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java @@ -16,9 +16,8 @@ */ /** * Log4j 2 Filter support. {@link org.apache.logging.log4j.core.Filter} plugins should use the - * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#category() plugin category} - * {@link org.apache.logging.log4j.core.config.Node#CATEGORY Core} and the - * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() element type} + * {@link org.apache.logging.log4j.plugins.Configurable} plugin namespace annotation and the + * {@linkplain org.apache.logging.log4j.plugins.Configurable#elementType() element type} value of * {@link org.apache.logging.log4j.core.Filter#ELEMENT_TYPE filter}. */ package org.apache.logging.log4j.core.filter; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java index b1adb7836d7..5676672ca86 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java @@ -16,16 +16,14 @@ */ package org.apache.logging.log4j.core.impl; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; import java.util.Map; import java.util.Map.Entry; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.util.IndexedStringMap; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.SortedArrayStringMap; @@ -37,7 +35,8 @@ * instances may be either populated with key-value pairs from the context, or completely replaced altogether. *

    * By default returns {@code SortedArrayStringMap} objects. Can be configured by setting system property - * {@code "log4j2.ContextData"} to the fully qualified class name of a class implementing the {@code StringMap} + * {@value Log4jProperties#THREAD_CONTEXT_DATA_CLASS_NAME} + * to the fully qualified class name of a class implementing the {@code StringMap} * interface. The class must have a public default constructor, and if possible should also have a public constructor * that takes a single {@code int} argument for the initial capacity. *

    @@ -48,11 +47,17 @@ * @since 2.7 */ public class ContextDataFactory { - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - private static final String CLASS_NAME = PropertiesUtil.getProperties().getStringProperty("log4j2.ContextData"); + private static final String CLASS_NAME = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.THREAD_CONTEXT_DATA_CLASS_NAME); private static final Class CACHED_CLASS = createCachedClass(CLASS_NAME); - private static final MethodHandle DEFAULT_CONSTRUCTOR = createDefaultConstructor(CACHED_CLASS); - private static final MethodHandle INITIAL_CAPACITY_CONSTRUCTOR = createInitialCapacityConstructor(CACHED_CLASS); + + /** + * In LOG4J2-2649 (https://issues.apache.org/jira/browse/LOG4J2-2649), + * the reporter said some reason about using graalvm to static compile. + * In graalvm doc (https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md), + * graalvm is not support MethodHandle now, so the Constructor need not to return MethodHandle. + */ + private static final Constructor DEFAULT_CONSTRUCTOR = createDefaultConstructor(CACHED_CLASS); + private static final Constructor INITIAL_CAPACITY_CONSTRUCTOR = createInitialCapacityConstructor(CACHED_CLASS); private static final StringMap EMPTY_STRING_MAP = createContextData(0); @@ -65,30 +70,30 @@ private static Class createCachedClass(final String classNa return null; } try { - return LoaderUtil.loadClass(className).asSubclass(IndexedStringMap.class); + return Loader.loadClass(className).asSubclass(IndexedStringMap.class); } catch (final Exception any) { return null; } } - private static MethodHandle createDefaultConstructor(final Class cachedClass) { + private static Constructor createDefaultConstructor(final Class cachedClass){ if (cachedClass == null) { return null; } try { - return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class)); - } catch (final NoSuchMethodException | IllegalAccessException ignored) { + return cachedClass.getConstructor(); + } catch (final NoSuchMethodException | IllegalAccessError ignored) { return null; } } - private static MethodHandle createInitialCapacityConstructor(final Class cachedClass) { + private static Constructor createInitialCapacityConstructor(final Class cachedClass){ if (cachedClass == null) { return null; } try { - return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class, int.class)); - } catch (final NoSuchMethodException | IllegalAccessException ignored) { + return cachedClass.getConstructor(int.class); + } catch (final NoSuchMethodException | IllegalAccessError ignored) { return null; } } @@ -98,7 +103,7 @@ public static StringMap createContextData() { return new SortedArrayStringMap(); } try { - return (IndexedStringMap) DEFAULT_CONSTRUCTOR.invoke(); + return (IndexedStringMap) DEFAULT_CONSTRUCTOR.newInstance(); } catch (final Throwable ignored) { return new SortedArrayStringMap(); } @@ -109,7 +114,7 @@ public static StringMap createContextData(final int initialCapacity) { return new SortedArrayStringMap(initialCapacity); } try { - return (IndexedStringMap) INITIAL_CAPACITY_CONSTRUCTOR.invoke(initialCapacity); + return (IndexedStringMap) INITIAL_CAPACITY_CONSTRUCTOR.newInstance(initialCapacity); } catch (final Throwable ignored) { return new SortedArrayStringMap(initialCapacity); } @@ -117,7 +122,7 @@ public static StringMap createContextData(final int initialCapacity) { public static StringMap createContextData(final Map context) { final StringMap contextData = createContextData(context.size()); - for (Entry entry : context.entrySet()) { + for (final Entry entry : context.entrySet()) { contextData.putValue(entry.getKey(), entry.getValue()); } return contextData; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java index fb1c33093ad..2ff65023b6b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java @@ -19,11 +19,11 @@ import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.spi.CopyOnWrite; import org.apache.logging.log4j.spi.DefaultThreadContextMap; import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.ReadOnlyStringMap; @@ -48,6 +48,10 @@ public class ContextDataInjectorFactory { * {@code ContextDataInjector} classes defined in {@link ThreadContextDataInjector} which is most appropriate for * the ThreadContext implementation. *

    + * Note: It is no longer recommended that users provide a custom implementation of the ContextDataInjector. + * Instead, provide a {@code ContextDataProvider}. + *

    + *

    * Users may use this system property to specify the fully qualified class name of a class that implements the * {@code ContextDataInjector} interface. *

    @@ -62,12 +66,12 @@ public class ContextDataInjectorFactory { * @see ContextDataInjector */ public static ContextDataInjector createInjector() { - final String className = PropertiesUtil.getProperties().getStringProperty("log4j2.ContextDataInjector"); + final String className = PropertiesUtil.getProperties().getStringProperty(Log4jProperties.THREAD_CONTEXT_DATA_INJECTOR_CLASS_NAME); if (className == null) { return createDefaultInjector(); } try { - final Class cls = LoaderUtil.loadClass(className).asSubclass( + final Class cls = Loader.loadClass(className).asSubclass( ContextDataInjector.class); return cls.newInstance(); } catch (final Exception dynamicFailed) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java new file mode 100644 index 00000000000..6e5f4f8168c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java @@ -0,0 +1,268 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.ContextDataInjector; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.DefaultConfigurationFactory; +import org.apache.logging.log4j.core.config.composite.DefaultMergeStrategy; +import org.apache.logging.log4j.core.config.composite.MergeStrategy; +import org.apache.logging.log4j.core.lookup.Interpolator; +import org.apache.logging.log4j.core.lookup.InterpolatorFactory; +import org.apache.logging.log4j.core.lookup.StrLookup; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; +import org.apache.logging.log4j.core.selector.ContextSelector; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.NanoClock; +import org.apache.logging.log4j.core.time.PreciseClock; +import org.apache.logging.log4j.core.time.internal.CachedClock; +import org.apache.logging.log4j.core.time.internal.CoarseCachedClock; +import org.apache.logging.log4j.core.time.internal.DummyNanoClock; +import org.apache.logging.log4j.core.time.internal.SystemClock; +import org.apache.logging.log4j.core.time.internal.SystemMillisClock; +import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry; +import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; +import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.plugins.Named; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Ordered; +import org.apache.logging.log4j.plugins.SingletonFactory; +import org.apache.logging.log4j.plugins.condition.ConditionalOnProperty; +import org.apache.logging.log4j.plugins.di.InjectException; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.spi.CopyOnWrite; +import org.apache.logging.log4j.spi.DefaultThreadContextMap; +import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; + +import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; + +/** + * Contains default bindings for Log4j including support for {@link PropertiesUtil}-based configuration. + * + * @see Log4jProperties + * @see ContextSelector + * @see ShutdownCallbackRegistry + * @see Clock + * @see NanoClock + * @see ConfigurationFactory + * @see MergeStrategy + * @see InterpolatorFactory + * @see ContextDataInjector + * @see LogEventFactory + * @see StrSubstitutor + */ +public class DefaultBundle { + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final Injector injector; + private final PropertyEnvironment properties; + private final ClassLoader classLoader; + + public DefaultBundle(final Injector injector, final PropertyEnvironment properties, final ClassLoader classLoader) { + this.injector = injector; + this.properties = properties; + this.classLoader = classLoader; + } + + @ConditionalOnProperty(name = Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME) + @SingletonFactory + @Ordered(100) + public ContextSelector systemPropertyContextSelector() throws ClassNotFoundException { + return newInstanceOfProperty(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, ContextSelector.class); + } + + @SingletonFactory + public ContextSelector defaultContextSelector() { + return new ClassLoaderContextSelector(injector); + } + + @ConditionalOnProperty(name = Log4jProperties.SHUTDOWN_CALLBACK_REGISTRY_CLASS_NAME) + @SingletonFactory + @Ordered(100) + public ShutdownCallbackRegistry systemPropertyShutdownCallbackRegistry() throws ClassNotFoundException { + return newInstanceOfProperty(Log4jProperties.SHUTDOWN_CALLBACK_REGISTRY_CLASS_NAME, ShutdownCallbackRegistry.class); + } + + @SingletonFactory + public ShutdownCallbackRegistry defaultShutdownCallbackRegistry() { + return new DefaultShutdownCallbackRegistry(); + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_CLOCK, value = "SystemClock") + @SingletonFactory + @Ordered(200) + public Clock systemClock() { + return logSupportedPrecision(new SystemClock()); + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_CLOCK, value = "SystemMillisClock") + @SingletonFactory + @Ordered(200) + public Clock systemMillisClock() { + return logSupportedPrecision(new SystemMillisClock()); + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_CLOCK, value = "CachedClock") + @SingletonFactory + @Ordered(200) + public Clock cachedClock() { + return logSupportedPrecision(CachedClock.instance()); + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_CLOCK, value = "org.apache.logging.log4j.core.time.internal.CachedClock") + @SingletonFactory + @Ordered(200) + public Clock cachedClockFqcn() { + return logSupportedPrecision(CachedClock.instance()); + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_CLOCK, value = "CoarseCachedClock") + @SingletonFactory + @Ordered(200) + public Clock coarseCachedClock() { + return logSupportedPrecision(CoarseCachedClock.instance()); + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_CLOCK, value = "org.apache.logging.log4j.core.time.internal.CoarseCachedClock") + @SingletonFactory + @Ordered(200) + public Clock coarseCachedClockFqcn() { + return logSupportedPrecision(CoarseCachedClock.instance()); + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_CLOCK) + @SingletonFactory + @Ordered(100) + public Clock systemPropertyClock() throws ClassNotFoundException { + return logSupportedPrecision(newInstanceOfProperty(Log4jProperties.CONFIG_CLOCK, Clock.class)); + } + + @SingletonFactory + public Clock defaultClock() { + return new SystemClock(); + } + + @SingletonFactory + public NanoClock defaultNanoClock() { + return new DummyNanoClock(); + } + + @ConditionalOnProperty(name = Log4jProperties.THREAD_CONTEXT_DATA_INJECTOR_CLASS_NAME) + @Factory + @Ordered(100) + public ContextDataInjector systemPropertyContextDataInjector() throws ClassNotFoundException { + return newInstanceOfProperty(Log4jProperties.THREAD_CONTEXT_DATA_INJECTOR_CLASS_NAME, ContextDataInjector.class); + } + + @Factory + public ContextDataInjector defaultContextDataInjector() { + final ReadOnlyThreadContextMap threadContextMap = ThreadContext.getThreadContextMap(); + + // note: map may be null (if legacy custom ThreadContextMap was installed by user) + if (threadContextMap instanceof DefaultThreadContextMap || threadContextMap == null) { + // for non StringMap-based context maps + return new ThreadContextDataInjector.ForDefaultThreadContextMap(); + } + if (threadContextMap instanceof CopyOnWrite) { + return new ThreadContextDataInjector.ForCopyOnWriteThreadContextMap(); + } + return new ThreadContextDataInjector.ForGarbageFreeThreadContextMap(); + } + + @ConditionalOnProperty(name = Log4jProperties.LOG_EVENT_FACTORY_CLASS_NAME) + @SingletonFactory + @Ordered(100) + public LogEventFactory systemPropertyLogEventFactory() throws ClassNotFoundException { + return newInstanceOfProperty(Log4jProperties.LOG_EVENT_FACTORY_CLASS_NAME, LogEventFactory.class); + } + + @SingletonFactory + public LogEventFactory defaultLogEventFactory( + final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock) { + return isThreadLocalsEnabled() ? new ReusableLogEventFactory(injector, clock, nanoClock) : + new DefaultLogEventFactory(injector, clock, nanoClock); + } + + @SingletonFactory + public InterpolatorFactory interpolatorFactory( + @Namespace(StrLookup.CATEGORY) final Map> strLookupPlugins) { + return defaultLookup -> new Interpolator(defaultLookup, strLookupPlugins); + } + + @SingletonFactory + public StrSubstitutor strSubstitutor(final InterpolatorFactory factory) { + return new StrSubstitutor(factory.newInterpolator(null)); + } + + @SingletonFactory + public ConfigurationFactory configurationFactory(final StrSubstitutor substitutor) { + // TODO(ms): should be able to @Import classes to get @ConditionalOnWhatever on the classes to treat as bundles-ish? + final DefaultConfigurationFactory factory = new DefaultConfigurationFactory(injector); + factory.setSubstitutor(substitutor); + return factory; + } + + @ConditionalOnProperty(name = Log4jProperties.CONFIG_MERGE_STRATEGY_CLASS_NAME) + @SingletonFactory + @Ordered(100) + public MergeStrategy systemPropertyMergeStrategy() throws ClassNotFoundException { + return newInstanceOfProperty(Log4jProperties.CONFIG_MERGE_STRATEGY_CLASS_NAME, MergeStrategy.class); + } + + @SingletonFactory + public MergeStrategy defaultMergeStrategy() { + return new DefaultMergeStrategy(); + } + + @ConditionalOnProperty(name = Log4jProperties.STATUS_DEFAULT_LEVEL) + @SingletonFactory + @Named("StatusLogger") + @Ordered(100) + public Level systemPropertyDefaultStatusLevel() { + return Level.getLevel(properties.getStringProperty(Log4jProperties.STATUS_DEFAULT_LEVEL)); + } + + @SingletonFactory + @Named("StatusLogger") + public Level defaultStatusLevel() { + return Level.ERROR; + } + + private T newInstanceOfProperty(final String propertyName, final Class supertype) throws ClassNotFoundException { + final String property = properties.getStringProperty(propertyName); + if (property == null) { + throw new InjectException("No property defined for name " + propertyName); + } + return injector.getInstance(classLoader.loadClass(property).asSubclass(supertype)); + } + + private static Clock logSupportedPrecision(final Clock clock) { + final String support = clock instanceof PreciseClock ? "supports" : "does not support"; + LOGGER.debug("{} {} precise timestamps.", clock.getClass().getName(), support); + return clock; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultCallback.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultCallback.java new file mode 100644 index 00000000000..df5912ca12e --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultCallback.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.InjectorCallback; +import org.apache.logging.log4j.util.PropertiesUtil; + +public class DefaultCallback implements InjectorCallback { + @Override + public void configure(final Injector injector) { + injector.setReflectionAccessor(object -> object.setAccessible(true)); + injector.registerBundle(new DefaultBundle(injector, PropertiesUtil.getProperties(), Loader.getClassLoader())); + } + + @Override + public String toString() { + return getClass().getName(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java index 127b02a1208..bcdd46c8b97 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java @@ -16,23 +16,45 @@ */ package org.apache.logging.log4j.core.impl; -import java.util.List; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.util.StringMap; + +import java.util.List; /** * Always creates new LogEvent instances. */ public class DefaultLogEventFactory implements LogEventFactory { - private static final DefaultLogEventFactory instance = new DefaultLogEventFactory(); + public static DefaultLogEventFactory newInstance() { + final var injector = DI.createInjector(); + injector.init(); + return injector.getInstance(DefaultLogEventFactory.class); + } + + private final ContextDataInjector injector; + private final Clock clock; + private final NanoClock nanoClock; - public static DefaultLogEventFactory getInstance() { - return instance; + @Inject + public DefaultLogEventFactory( + final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock) { + this.injector = injector; + this.clock = clock; + this.nanoClock = nanoClock; + } + + private StringMap createContextData(final List properties) { + return injector.injectContextData(properties, ContextDataFactory.createContextData()); } /** @@ -51,6 +73,47 @@ public static DefaultLogEventFactory getInstance() { public LogEvent createEvent(final String loggerName, final Marker marker, final String fqcn, final Level level, final Message data, final List properties, final Throwable t) { - return new Log4jLogEvent(loggerName, marker, fqcn, level, data, properties, t); + return Log4jLogEvent.newBuilder() + .setNanoTime(nanoClock.nanoTime()) + .setClock(clock) + .setLoggerName(loggerName) + .setMarker(marker) + .setLoggerFqcn(fqcn) + .setLevel(level) + .setMessage(data) + .setContextData(createContextData(properties)) + .setThrown(t) + .build(); + } + + /** + * Creates a log event. + * + * @param loggerName The name of the Logger. + * @param marker An optional Marker. + * @param fqcn The fully qualified class name of the caller. + * @param location The location of the caller + * @param level The event Level. + * @param data The Message. + * @param properties Properties to be added to the log event. + * @param t An optional Throwable. + * @return The LogEvent. + */ + @Override + public LogEvent createEvent(final String loggerName, final Marker marker, final String fqcn, + final StackTraceElement location, final Level level, final Message data, + final List properties, final Throwable t) { + return Log4jLogEvent.newBuilder() + .setNanoTime(nanoClock.nanoTime()) + .setClock(clock) + .setLoggerName(loggerName) + .setMarker(marker) + .setLoggerFqcn(fqcn) + .setSource(location) + .setLevel(level) + .setMessage(data) + .setContextData(createContextData(properties)) + .setThrown(t) + .build(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java index 44d42f9220a..820d7ac0ccb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java @@ -36,7 +36,7 @@ public final class ExtendedClassInfo implements Serializable { /** * Constructs a new instance. - * + * * @param exact * @param location * @param version @@ -120,4 +120,4 @@ public String toString() { return sb.toString(); } -} \ No newline at end of file +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java index 06183e46443..377d0e61842 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedStackTraceElement.java @@ -20,6 +20,7 @@ import org.apache.logging.log4j.core.pattern.PlainTextRenderer; import org.apache.logging.log4j.core.pattern.TextRenderer; +import org.apache.logging.log4j.util.Strings; /** * Wraps and extends the concept of the JRE's final class {@link StackTraceElement} by adding more location information. @@ -34,6 +35,8 @@ */ public final class ExtendedStackTraceElement implements Serializable { + static final ExtendedStackTraceElement[] EMPTY_ARRAY = {}; + private static final long serialVersionUID = -2171069569241280505L; private final ExtendedClassInfo extraClassInfo; @@ -55,6 +58,17 @@ public ExtendedStackTraceElement(final String declaringClass, final String metho new ExtendedClassInfo(exact, location, version)); } + /** + * Called from Jackson for XML and JSON IO. + */ + public ExtendedStackTraceElement(final String classLoaderName, final String moduleName, final String moduleVersion, + final String declaringClass, final String methodName, final String fileName, final int lineNumber, + final boolean exact, final String location, final String version) { + this(new StackTraceElement(classLoaderName, moduleName, moduleVersion, declaringClass, methodName, fileName, + lineNumber), + new ExtendedClassInfo(exact, location, version)); + } + @Override public boolean equals(final Object obj) { if (this == obj) { @@ -84,6 +98,18 @@ public boolean equals(final Object obj) { return true; } + public String getClassLoaderName() { + return this.stackTraceElement.getClassLoaderName(); + } + + public String getModuleName() { + return this.stackTraceElement.getModuleName(); + } + + public String getModuleVersion() { + return this.stackTraceElement.getModuleVersion(); + } + public String getClassName() { return this.stackTraceElement.getClassName(); } @@ -142,6 +168,16 @@ void renderOn(final StringBuilder output, final TextRenderer textRenderer) { private void render(final StackTraceElement stElement, final StringBuilder output, final TextRenderer textRenderer) { final String fileName = stElement.getFileName(); final int lineNumber = stElement.getLineNumber(); + final String moduleName = getModuleName(); + final String moduleVersion = getModuleVersion(); + if (Strings.isNotEmpty(moduleName)) { + textRenderer.render(moduleName, output, "StackTraceElement.ModuleName"); + if (Strings.isNotEmpty(moduleVersion) && !moduleName.startsWith("java")) { + textRenderer.render("@", output, "StackTraceElement.ModuleVersionSeparator"); + textRenderer.render(moduleVersion, output, "StackTraceElement.ModuleVersion"); + } + textRenderer.render("/", output, "StackTraceElement.ModuleNameSeparator"); + } textRenderer.render(getClassName(), output, "StackTraceElement.ClassName"); textRenderer.render(".", output, "StackTraceElement.ClassMethodSeparator"); textRenderer.render(stElement.getMethodName(), output, "StackTraceElement.MethodName"); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java index 4091276d7f0..631d7b3d04a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java @@ -25,25 +25,23 @@ import org.apache.logging.log4j.util.BiConsumer; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.StringMap; +import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.TriConsumer; /** * Provides a read-only {@code StringMap} view of a {@code Map}. */ -class JdkMapAdapterStringMap implements StringMap { +public class JdkMapAdapterStringMap implements StringMap { private static final long serialVersionUID = -7348247784983193612L; private static final String FROZEN = "Frozen collection cannot be modified"; - private static final Comparator NULL_FIRST_COMPARATOR = new Comparator() { - @Override - public int compare(final String left, final String right) { - if (left == null) { - return -1; - } - if (right == null) { - return 1; - } - return left.compareTo(right); + private static final Comparator NULL_FIRST_COMPARATOR = (Comparator) (left, right) -> { + if (left == null) { + return -1; + } + if (right == null) { + return 1; } + return left.compareTo(right); }; private final Map map; @@ -94,7 +92,7 @@ public void forEach(final TriConsumer action, final private String[] getSortedKeys() { if (sortedKeys == null) { - sortedKeys = map.keySet().toArray(new String[map.size()]); + sortedKeys = map.keySet().toArray(Strings.EMPTY_ARRAY); Arrays.sort(sortedKeys, NULL_FIRST_COMPARATOR); } return sortedKeys; @@ -143,12 +141,7 @@ public void putAll(final ReadOnlyStringMap source) { sortedKeys = null; } - private static TriConsumer> PUT_ALL = new TriConsumer>() { - @Override - public void accept(final String key, final String value, final Map stringStringMap) { - stringStringMap.put(key, value); - } - }; + private static final TriConsumer> PUT_ALL = (key, value, stringStringMap) -> stringStringMap.put(key, value); @Override public void putValue(final String key, final Object value) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java index bfd6df2a405..41e071e0958 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java @@ -19,36 +19,40 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import org.apache.logging.log4j.core.LifeCycle; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; -import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationFactory; import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; import org.apache.logging.log4j.core.selector.ContextSelector; import org.apache.logging.log4j.core.util.Cancellable; import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry; import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.PropertiesUtil; +import static org.apache.logging.log4j.util.Constants.isWebApp; + /** * Factory to locate a ContextSelector and then load a LoggerContext. */ +@Singleton public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallbackRegistry { private static final StatusLogger LOGGER = StatusLogger.getLogger(); - private static final boolean SHUTDOWN_HOOK_ENABLED = - PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true) && - !Constants.IS_WEB_APP; + private final Injector injector; private final ContextSelector selector; private final ShutdownCallbackRegistry shutdownCallbackRegistry; @@ -56,7 +60,12 @@ public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallba * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}. */ public Log4jContextFactory() { - this(createContextSelector(), createShutdownCallbackRegistry()); + injector = DI.createInjector(); + injector.init(); + this.selector = injector.getInstance(ContextSelector.KEY); + this.shutdownCallbackRegistry = injector.getInstance(ShutdownCallbackRegistry.KEY); + LOGGER.debug("Using ShutdownCallbackRegistry {}", this.shutdownCallbackRegistry.getClass()); + initializeShutdownCallbackRegistry(); } /** @@ -64,7 +73,14 @@ public Log4jContextFactory() { * @param selector the selector to use */ public Log4jContextFactory(final ContextSelector selector) { - this(selector, createShutdownCallbackRegistry()); + Objects.requireNonNull(selector, "No ContextSelector provided"); + injector = DI.createInjector(); + injector.init(); + injector.registerBinding(ContextSelector.KEY, () -> selector); + this.selector = injector.getInstance(ContextSelector.KEY); + this.shutdownCallbackRegistry = injector.getInstance(ShutdownCallbackRegistry.KEY); + LOGGER.debug("Using ShutdownCallbackRegistry {}", this.shutdownCallbackRegistry.getClass()); + initializeShutdownCallbackRegistry(); } /** @@ -75,7 +91,14 @@ public Log4jContextFactory(final ContextSelector selector) { * @since 2.1 */ public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) { - this(createContextSelector(), shutdownCallbackRegistry); + Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided"); + injector = DI.createInjector(); + injector.init(); + injector.registerBinding(ShutdownCallbackRegistry.KEY, () -> shutdownCallbackRegistry); + this.selector = injector.getInstance(ContextSelector.KEY); + this.shutdownCallbackRegistry = injector.getInstance(ShutdownCallbackRegistry.KEY); + LOGGER.debug("Using ShutdownCallbackRegistry {}", this.shutdownCallbackRegistry.getClass()); + initializeShutdownCallbackRegistry(); } /** @@ -87,37 +110,29 @@ public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegist */ public Log4jContextFactory(final ContextSelector selector, final ShutdownCallbackRegistry shutdownCallbackRegistry) { - this.selector = Objects.requireNonNull(selector, "No ContextSelector provided"); - this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided"); - LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass()); + Objects.requireNonNull(selector, "No ContextSelector provided"); + Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided"); + injector = DI.createInjector(); + injector.init(); + injector.registerBinding(ContextSelector.KEY, () -> selector) + .registerBinding(ShutdownCallbackRegistry.KEY, () -> shutdownCallbackRegistry); + this.selector = injector.getInstance(ContextSelector.KEY); + this.shutdownCallbackRegistry = injector.getInstance(ShutdownCallbackRegistry.KEY); + LOGGER.debug("Using ShutdownCallbackRegistry {}", this.shutdownCallbackRegistry.getClass()); initializeShutdownCallbackRegistry(); } - private static ContextSelector createContextSelector() { - try { - final ContextSelector selector = LoaderUtil.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR, - ContextSelector.class); - if (selector != null) { - return selector; - } - } catch (final Exception e) { - LOGGER.error("Unable to create custom ContextSelector. Falling back to default.", e); - } - return new ClassLoaderContextSelector(); + @Inject + public Log4jContextFactory(final Injector injector, final ContextSelector selector, final ShutdownCallbackRegistry registry) { + this.injector = injector; + this.selector = selector; + this.shutdownCallbackRegistry = registry; + LOGGER.debug("Using ShutdownCallbackRegistry {}", this.shutdownCallbackRegistry.getClass()); + initializeShutdownCallbackRegistry(); } - private static ShutdownCallbackRegistry createShutdownCallbackRegistry() { - try { - final ShutdownCallbackRegistry registry = LoaderUtil.newCheckedInstanceOfProperty( - ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY, ShutdownCallbackRegistry.class - ); - if (registry != null) { - return registry; - } - } catch (final Exception e) { - LOGGER.error("Unable to create custom ShutdownCallbackRegistry. Falling back to default.", e); - } - return new DefaultShutdownCallbackRegistry(); + public Log4jContextFactory(final Injector injector) { + this(injector, injector.getInstance(ContextSelector.KEY), injector.getInstance(ShutdownCallbackRegistry.KEY)); } private void initializeShutdownCallbackRegistry() { @@ -174,8 +189,8 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin if (ctx.getState() == LifeCycle.State.INITIALIZED) { if (source != null) { ContextAnchor.THREAD_CONTEXT.set(ctx); - final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, source); - LOGGER.debug("Starting LoggerContext[name={}] from configuration {}", ctx.getName(), source); + final Configuration config = injector.getInstance(ConfigurationFactory.KEY).getConfiguration(ctx, source); + LOGGER.debug("Starting {} from configuration {}", ctx, source); ctx.start(config); ContextAnchor.THREAD_CONTEXT.remove(); } else { @@ -230,13 +245,35 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin ctx.setExternalContext(externalContext); } if (name != null) { - ctx.setName(name); + ctx.setName(name); + } + if (ctx.getState() == LifeCycle.State.INITIALIZED) { + if (configLocation != null || name != null) { + ContextAnchor.THREAD_CONTEXT.set(ctx); + final Configuration config = + injector.getInstance(ConfigurationFactory.KEY).getConfiguration(ctx, name, configLocation); + LOGGER.debug("Starting {} from configuration at {}", ctx, configLocation); + ctx.start(config); + ContextAnchor.THREAD_CONTEXT.remove(); + } else { + ctx.start(); + } + } + return ctx; + } + + public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Map.Entry entry, + final boolean currentContext, final URI configLocation, final String name) { + final LoggerContext ctx = selector.getContext(fqcn, loader, entry, currentContext, configLocation); + if (name != null) { + ctx.setName(name); } if (ctx.getState() == LifeCycle.State.INITIALIZED) { if (configLocation != null || name != null) { ContextAnchor.THREAD_CONTEXT.set(ctx); - final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, name, configLocation); - LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation); + final Configuration config = + injector.getInstance(ConfigurationFactory.KEY).getConfiguration(ctx, name, configLocation); + LOGGER.debug("Starting {} from configuration at {}", ctx, configLocation); ctx.start(config); ContextAnchor.THREAD_CONTEXT.remove(); } else { @@ -261,20 +298,31 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin ContextAnchor.THREAD_CONTEXT.set(ctx); final List configurations = new ArrayList<>(configLocations.size()); for (final URI configLocation : configLocations) { - final Configuration currentReadConfiguration = ConfigurationFactory.getInstance() + final Configuration currentReadConfiguration = injector.getInstance(ConfigurationFactory.KEY) .getConfiguration(ctx, name, configLocation); - if (currentReadConfiguration instanceof AbstractConfiguration) { - configurations.add((AbstractConfiguration) currentReadConfiguration); + if (currentReadConfiguration != null) { + if (currentReadConfiguration instanceof DefaultConfiguration) { + LOGGER.warn("Unable to locate configuration {}, ignoring", configLocation.toString()); + } + else if (currentReadConfiguration instanceof AbstractConfiguration) { + configurations.add((AbstractConfiguration) currentReadConfiguration); + } else { + LOGGER.error( + "Found configuration {}, which is not an AbstractConfiguration and can't be handled by CompositeConfiguration", + configLocation); + } } else { - LOGGER.error( - "Found configuration {}, which is not an AbstractConfiguration and can't be handled by CompositeConfiguration", - configLocation); + LOGGER.info("Unable to access configuration {}, ignoring", configLocation.toString()); } } - final CompositeConfiguration compositeConfiguration = new CompositeConfiguration(configurations); - LOGGER.debug("Starting LoggerContext[name={}] from configurations at {}", ctx.getName(), - configLocations); - ctx.start(compositeConfiguration); + if (configurations.size() == 0) { + LOGGER.error("No configurations could be created for {}", configLocations.toString()); + } else if (configurations.size() == 1) { + ctx.start(configurations.get(0)); + } else { + final CompositeConfiguration compositeConfiguration = new CompositeConfiguration(configurations); + ctx.start(compositeConfiguration); + } ContextAnchor.THREAD_CONTEXT.remove(); } else { ctx.start(); @@ -283,6 +331,27 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin return ctx; } + @Override + public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext, final boolean allContexts) { + if (selector.hasContext(fqcn, loader, currentContext)) { + selector.shutdown(fqcn, loader, currentContext, allContexts); + } + } + + /** + * Checks to see if a LoggerContext is installed. + * @param fqcn The fully qualified class name of the caller. + * @param loader The ClassLoader to use or null. + * @param currentContext If true returns the current Context, if false returns the Context appropriate + * for the caller if a more appropriate Context can be determined. + * @return true if a LoggerContext has been installed, false otherwise. + * @since 2.13.0 + */ + @Override + public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + return selector.hasContext(fqcn, loader, currentContext); + } + /** * Returns the ContextSelector. * @return The ContextSelector. @@ -291,15 +360,15 @@ public ContextSelector getSelector() { return selector; } - /** - * Returns the ShutdownCallbackRegistry - * - * @return the ShutdownCallbackRegistry - * @since 2.4 - */ - public ShutdownCallbackRegistry getShutdownCallbackRegistry() { - return shutdownCallbackRegistry; - } + /** + * Returns the ShutdownCallbackRegistry + * + * @return the ShutdownCallbackRegistry + * @since 2.4 + */ + public ShutdownCallbackRegistry getShutdownCallbackRegistry() { + return shutdownCallbackRegistry; + } /** * Removes knowledge of a LoggerContext. @@ -313,12 +382,18 @@ public void removeContext(final org.apache.logging.log4j.spi.LoggerContext conte } } + @Override + public boolean isClassLoaderDependent() { + return selector.isClassLoaderDependent(); + } + @Override public Cancellable addShutdownCallback(final Runnable callback) { return isShutdownHookEnabled() ? shutdownCallbackRegistry.addShutdownCallback(callback) : null; } public boolean isShutdownHookEnabled() { - return SHUTDOWN_HOOK_ENABLED; + return !isWebApp() && PropertiesUtil.getProperties() + .getBooleanProperty(Log4jProperties.SHUTDOWN_HOOK_ENABLED, true); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java index 2fe5a9be91c..28be3a64057 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java @@ -21,30 +21,27 @@ import java.io.ObjectInputStream; import java.io.Serializable; import java.rmi.MarshalledObject; -import java.util.List; -import java.util.Map; import java.util.Objects; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.ContextDataInjector; -import org.apache.logging.log4j.core.util.*; -import org.apache.logging.log4j.core.time.Instant; -import org.apache.logging.log4j.core.time.MutableInstant; -import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.async.RingBufferLogEvent; import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.ClockFactory; +import org.apache.logging.log4j.core.time.Instant; +import org.apache.logging.log4j.core.time.MutableInstant; import org.apache.logging.log4j.message.LoggerNameAwareMessage; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.ReusableMessage; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.message.TimestampMessage; +import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StringMap; -import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; /** @@ -53,9 +50,6 @@ public class Log4jLogEvent implements LogEvent { private static final long serialVersionUID = -8393305700508709443L; - private static final Clock CLOCK = ClockFactory.getClock(); - private static volatile NanoClock nanoClock = new DummyNanoClock(); - private static final ContextDataInjector CONTEXT_DATA_INJECTOR = ContextDataInjectorFactory.createInjector(); private final String loggerFqcn; private final Marker marker; @@ -77,7 +71,7 @@ public class Log4jLogEvent implements LogEvent { private final transient long nanoTime; /** LogEvent Builder helper class. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { private String loggerFqcn; private Marker marker; @@ -85,9 +79,9 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde private String loggerName; private Message message; private Throwable thrown; - private MutableInstant instant = new MutableInstant(); + private final MutableInstant instant = new MutableInstant(); private ThrowableProxy thrownProxy; - private StringMap contextData = createContextData((List) null); + private StringMap contextData; private ThreadContext.ContextStack contextStack = ThreadContext.getImmutableStack(); private long threadId; private String threadName; @@ -96,8 +90,11 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde private boolean includeLocation; private boolean endOfBatch = false; private long nanoTime; + private Clock clock; + private ContextDataInjector contextDataInjector; public Builder() { + initDefaultContextData(); } public Builder(final LogEvent other) { @@ -122,6 +119,7 @@ public Builder(final LogEvent other) { this.endOfBatch = other.isEndOfBatch(); this.nanoTime = other.getNanoTime(); + initDefaultContextData(); // Avoid unnecessarily initializing thrownProxy, threadName and source if possible if (other instanceof Log4jLogEvent) { final Log4jLogEvent evt = (Log4jLogEvent) other; @@ -196,17 +194,6 @@ public Builder setThrownProxy(final ThrowableProxy thrownProxy) { return this; } - @Deprecated - public Builder setContextMap(final Map contextMap) { - contextData = ContextDataFactory.createContextData(); // replace with new instance - if (contextMap != null) { - for (final Map.Entry entry : contextMap.entrySet()) { - contextData.putValue(entry.getKey(), entry.getValue()); - } - } - return this; - } - public Builder setContextData(final StringMap contextData) { this.contextData = contextData; return this; @@ -258,6 +245,16 @@ public Builder setNanoTime(final long nanoTime) { return this; } + public Builder setClock(final Clock clock) { + this.clock = clock; + return this; + } + + public Builder setContextDataInjector(final ContextDataInjector contextDataInjector) { + this.contextDataInjector = contextDataInjector; + return this; + } + @Override public Log4jLogEvent build() { initTimeFields(); @@ -271,9 +268,18 @@ public Log4jLogEvent build() { private void initTimeFields() { if (instant.getEpochMillisecond() == 0) { - instant.initFrom(CLOCK); + if (message instanceof TimestampMessage) { + instant.initFromEpochMilli(((TimestampMessage) message).getTimestamp(), 0); + } else { + instant.initFrom(clock != null ? clock : ClockFactory.getClock()); + } } } + + private void initDefaultContextData() { + contextDataInjector = ContextDataInjectorFactory.createInjector(); + contextData = contextDataInjector.injectContextData(null, ContextDataFactory.createContextData()); + } } /** @@ -281,117 +287,14 @@ private void initTimeFields() { * @return a new empty builder. */ public static Builder newBuilder() { - return new Builder(); + return new Builder().setLoggerName(Strings.EMPTY); } public Log4jLogEvent() { - this(Strings.EMPTY, null, Strings.EMPTY, null, null, (Throwable) null, null, null, null, 0, null, - 0, null, CLOCK, nanoClock.nanoTime()); + this(Strings.EMPTY, null, Strings.EMPTY, null, null, null, null, null, null, 0, null, 0, null, 0, 0, 0); } - /** - * - * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release. - */ - @Deprecated - public Log4jLogEvent(final long timestamp) { - this(Strings.EMPTY, null, Strings.EMPTY, null, null, (Throwable) null, null, null, null, 0, null, - 0, null, timestamp, 0, nanoClock.nanoTime()); - } - - /** - * Constructor. - * @param loggerName The name of the Logger. - * @param marker The Marker or null. - * @param loggerFQCN The fully qualified class name of the caller. - * @param level The logging Level. - * @param message The Message. - * @param t A Throwable or null. - * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release. - */ - @Deprecated - public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level, - final Message message, final Throwable t) { - this(loggerName, marker, loggerFQCN, level, message, null, t); - } - /** - * Constructor. - * @param loggerName The name of the Logger. - * @param marker The Marker or null. - * @param loggerFQCN The fully qualified class name of the caller. - * @param level The logging Level. - * @param message The Message. - * @param properties the properties to be merged with ThreadContext key-value pairs into the event's ReadOnlyStringMap. - * @param t A Throwable or null. - */ - // This constructor is called from LogEventFactories. - public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level, - final Message message, final List properties, final Throwable t) { - this(loggerName, marker, loggerFQCN, level, message, t, null, createContextData(properties), - ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), // mutable copy - 0, // thread id - null, // thread name - 0, // thread priority - null, // StackTraceElement source - CLOCK, // - nanoClock.nanoTime()); - } - - /** - * Constructor. - * @param loggerName The name of the Logger. - * @param marker The Marker or null. - * @param loggerFQCN The fully qualified class name of the caller. - * @param level The logging Level. - * @param message The Message. - * @param t A Throwable or null. - * @param mdc The mapped diagnostic context. - * @param ndc the nested diagnostic context. - * @param threadName The name of the thread. - * @param location The locations of the caller. - * @param timestampMillis The timestamp of the event. - * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release. - */ - @Deprecated - public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level, - final Message message, final Throwable t, final Map mdc, - final ThreadContext.ContextStack ndc, final String threadName, - final StackTraceElement location, final long timestampMillis) { - this(loggerName, marker, loggerFQCN, level, message, t, null, createContextData(mdc), ndc, 0, - threadName, 0, location, timestampMillis, 0, nanoClock.nanoTime()); - } - - /** - * Create a new LogEvent. - * @param loggerName The name of the Logger. - * @param marker The Marker or null. - * @param loggerFQCN The fully qualified class name of the caller. - * @param level The logging Level. - * @param message The Message. - * @param thrown A Throwable or null. - * @param thrownProxy A ThrowableProxy or null. - * @param mdc The mapped diagnostic context. - * @param ndc the nested diagnostic context. - * @param threadName The name of the thread. - * @param location The locations of the caller. - * @param timestamp The timestamp of the event. - * @return a new LogEvent - * @deprecated use {@link Log4jLogEvent.Builder} instead. This method will be removed in an upcoming release. - */ - @Deprecated - public static Log4jLogEvent createEvent(final String loggerName, final Marker marker, final String loggerFQCN, - final Level level, final Message message, final Throwable thrown, - final ThrowableProxy thrownProxy, - final Map mdc, final ThreadContext.ContextStack ndc, - final String threadName, final StackTraceElement location, - final long timestamp) { - final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFQCN, level, message, thrown, - thrownProxy, createContextData(mdc), ndc, 0, threadName, 0, location, timestamp, 0, nanoClock.nanoTime()); - return result; - } - - /** * Constructor. * @param loggerName The name of the Logger. * @param marker The Marker or null. @@ -416,29 +319,6 @@ private Log4jLogEvent(final String loggerName, final Marker marker, final String final StringMap contextData, final ThreadContext.ContextStack contextStack, final long threadId, final String threadName, final int threadPriority, final StackTraceElement source, final long timestampMillis, final int nanoOfMillisecond, final long nanoTime) { - this(loggerName, marker, loggerFQCN, level, message, thrown, thrownProxy, contextData, contextStack, threadId, threadName, threadPriority, source, nanoTime); - long millis = message instanceof TimestampMessage - ? ((TimestampMessage) message).getTimestamp() - : timestampMillis; - instant.initFromEpochMilli(millis, nanoOfMillisecond); - } - private Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level, - final Message message, final Throwable thrown, final ThrowableProxy thrownProxy, - final StringMap contextData, final ThreadContext.ContextStack contextStack, final long threadId, - final String threadName, final int threadPriority, final StackTraceElement source, - final Clock clock, final long nanoTime) { - this(loggerName, marker, loggerFQCN, level, message, thrown, thrownProxy, contextData, contextStack, threadId, threadName, threadPriority, source, nanoTime); - if (message instanceof TimestampMessage) { - instant.initFromEpochMilli(((TimestampMessage) message).getTimestamp(), 0); - } else { - instant.initFrom(clock); - } - } - private Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level, - final Message message, final Throwable thrown, final ThrowableProxy thrownProxy, - final StringMap contextData, final ThreadContext.ContextStack contextStack, final long threadId, - final String threadName, final int threadPriority, final StackTraceElement source, - final long nanoTime) { this.loggerName = loggerName; this.marker = marker; this.loggerFqcn = loggerFQCN; @@ -456,42 +336,10 @@ private Log4jLogEvent(final String loggerName, final Marker marker, final String ((LoggerNameAwareMessage) message).setLoggerName(loggerName); } this.nanoTime = nanoTime; - } - - private static StringMap createContextData(final Map contextMap) { - final StringMap result = ContextDataFactory.createContextData(); - if (contextMap != null) { - for (final Map.Entry entry : contextMap.entrySet()) { - result.putValue(entry.getKey(), entry.getValue()); - } - } - return result; - } - - private static StringMap createContextData(final List properties) { - final StringMap reusable = ContextDataFactory.createContextData(); - return CONTEXT_DATA_INJECTOR.injectContextData(properties, reusable); - } - - /** - * Returns the {@code NanoClock} to use for creating the nanoTime timestamp of log events. - * @return the {@code NanoClock} to use for creating the nanoTime timestamp of log events - */ - public static NanoClock getNanoClock() { - return nanoClock; - } - - /** - * Sets the {@code NanoClock} to use for creating the nanoTime timestamp of log events. - *

    - * FOR INTERNAL USE. This method may be called with a different {@code NanoClock} implementation when the - * configuration changes. - * - * @param nanoClock the {@code NanoClock} to use for creating the nanoTime timestamp of log events - */ - public static void setNanoClock(final NanoClock nanoClock) { - Log4jLogEvent.nanoClock = Objects.requireNonNull(nanoClock, "NanoClock must be non-null"); - StatusLogger.getLogger().trace("Using {} for nanosecond timestamps.", nanoClock.getClass().getSimpleName()); + final long millis = message instanceof TimestampMessage + ? ((TimestampMessage) message).getTimestamp() + : timestampMillis; + instant.initFromEpochMilli(millis, nanoOfMillisecond); } /** @@ -538,7 +386,7 @@ public Message getMessage() { } public void makeMessageImmutable() { - message = new SimpleMessage(message.getFormattedMessage()); + message = new MementoMessage(message.getFormattedMessage(), message.getFormat(), message.getParameters()); } @Override @@ -579,7 +427,7 @@ public long getTimeMillis() { /** * {@inheritDoc} - * @since 2.11 + * @since 2.11.0 */ @Override public Instant getInstant() { @@ -635,14 +483,6 @@ public String getLoggerFqcn() { public ReadOnlyStringMap getContextData() { return contextData; } - /** - * Returns the immutable copy of the ThreadContext Map. - * @return The context Map. - */ - @Override - public Map getContextMap() { - return contextData.toMap(); - } /** * Returns an immutable copy of the ThreadContext stack. @@ -762,7 +602,7 @@ private void readObject(final ObjectInputStream stream) throws InvalidObjectExce public static LogEvent createMemento(final LogEvent logEvent) { return new Log4jLogEvent.Builder(logEvent).build(); } - + /** * Creates and returns a new immutable copy of this {@code Log4jLogEvent}. * @@ -838,10 +678,10 @@ public boolean equals(final Object o) { if (threadPriority != that.threadPriority) { return false; } - if (thrown != null ? !thrown.equals(that.thrown) : that.thrown != null) { + if (!Objects.equals(thrown, that.thrown)) { return false; } - if (thrownProxy != null ? !thrownProxy.equals(that.thrownProxy) : that.thrownProxy != null) { + if (!Objects.equals(thrownProxy, that.thrownProxy)) { return false; } @@ -864,7 +704,7 @@ public int hashCode() { result = 31 * result + (contextStack != null ? contextStack.hashCode() : 0); result = 31 * result + (int) (threadId ^ (threadId >>> 32)); result = 31 * result + (threadName != null ? threadName.hashCode() : 0); - result = 31 * result + (threadPriority ^ (threadPriority >>> 32)); + result = 31 * result + threadPriority; result = 31 * result + (source != null ? source.hashCode() : 0); result = 31 * result + (includeLocation ? 1 : 0); result = 31 * result + (endOfBatch ? 1 : 0); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProperties.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProperties.java new file mode 100644 index 00000000000..62a2a710e7f --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProperties.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +public class Log4jProperties { + // TODO: rename properties according to established theme in + // https://cwiki.apache.org/confluence/display/LOGGING/Properties+Enhancement + + // LoggerContext.shutdownCallbackRegistry + public static final String SHUTDOWN_CALLBACK_REGISTRY_CLASS_NAME = "log4j2.shutdownCallbackRegistry"; + // LoggerContext.shutdownHookEnabled + public static final String SHUTDOWN_HOOK_ENABLED = "log4j2.shutdownHookEnabled"; + + // LoggerContext.stacktraceOnStart + public static final String LOGGER_CONTEXT_STACKTRACE_ON_START = "log4j2.LoggerContext.stacktraceOnStart"; + + // Jansi.enabled + public static final String JANSI_DISABLED = "log4j2.skipJansi"; + + // LoggerContext.contextSelector + public static final String CONTEXT_SELECTOR_CLASS_NAME = "log4j2.contextSelector"; + + // LoggerContext.logEventFactory + public static final String LOG_EVENT_FACTORY_CLASS_NAME = "log4j2.logEventFactory"; + + // StatusLogger.defaultStatusLevel + public static final String STATUS_DEFAULT_LEVEL = "log4j2.defaultStatusLevel"; + + // Configuration.level + public static final String CONFIG_DEFAULT_LEVEL = "log4j2.level"; + // Configuration.clock + public static final String CONFIG_CLOCK = "log4j2.clock"; + // Configuration.mergeStrategy + public static final String CONFIG_MERGE_STRATEGY_CLASS_NAME = "log4j2.mergeStrategy"; + // Configuration.reliabilityStrategy + public static final String CONFIG_RELIABILITY_STRATEGY = "log4j2.reliabilityStrategy"; + // Configuration.reliabilityStrategyAwaitUnconditionallyMillis + public static final String CONFIG_RELIABILITY_STRATEGY_AWAIT_UNCONDITIONALLY_MILLIS = "log4j2.waitMillisBeforeStopOldConfig"; + // Configuration.factory + public static final String CONFIG_CONFIGURATION_FACTORY_CLASS_NAME = "log4j2.configurationFactory"; + // Configuration.location + public static final String CONFIG_LOCATION = "log4j2.configurationFile"; + public static final String CONFIG_V1_FILE_NAME = "log4j.configuration"; + public static final String CONFIG_V1_COMPATIBILITY_ENABLED = "log4j1.compatibility"; + + // AsyncLogger.formatMsgAsync + public static final String ASYNC_LOGGER_FORMAT_MESSAGES_IN_BACKGROUND = "log4j2.formatMsgAsync"; + // AsyncLogger.queueFullPolicy + public static final String ASYNC_LOGGER_QUEUE_FULL_POLICY = "log4j2.asyncQueueFullPolicy"; + // AsyncLogger.discardThreshold + public static final String ASYNC_LOGGER_DISCARD_THRESHOLD = "log4j2.discardThreshold"; + // AsyncLogger.ringBufferSize + public static final String ASYNC_LOGGER_RING_BUFFER_SIZE = "log4j2.AsyncLogger.ringBufferSize"; + // AsyncLogger.waitStrategy + public static final String ASYNC_LOGGER_WAIT_STRATEGY = "log4j2.AsyncLogger.waitStrategy"; + // AsyncLogger.synchronizeEnqueueWhenQueueFull + public static final String ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = "log4j2.AsyncLogger.synchronizeEnqueueWhenQueueFull"; + // AsyncLogger.exceptionHandler + public static final String ASYNC_LOGGER_EXCEPTION_HANDLER_CLASS_NAME = "log4j2.AsyncLogger.exceptionHandler"; + // AsyncLogger.threadNameStrategy + public static final String ASYNC_LOGGER_THREAD_NAME_STRATEGY = "log4j2.AsyncLogger.threadNameStrategy"; + // AsyncLoggerConfig.ringBufferSize + public static final String ASYNC_CONFIG_RING_BUFFER_SIZE = "log4j2.AsyncLoggerConfig.ringBufferSize"; + // AsyncLoggerConfig.waitStrategy + public static final String ASYNC_CONFIG_WAIT_STRATEGY = "log4j2.AsyncLoggerConfig.waitStrategy"; + // AsyncLoggerConfig.synchronizeEnqueueWhenQueueFull + public static final String ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = "log4j2.AsyncLoggerConfig.synchronizeEnqueueWhenQueueFull"; + // AsyncLoggerConfig.exceptionHandler + public static final String ASYNC_CONFIG_EXCEPTION_HANDLER_CLASS_NAME = "log4j2.AsyncLoggerConfig.exceptionHandler"; + + // GC.enableDirectEncoders + public static final String GC_ENABLE_DIRECT_ENCODERS = "log4j2.enableDirectEncoders"; + // GC.initialReusableMsgSize + public static final String GC_INITIAL_REUSABLE_MESSAGE_SIZE = "log4j2.initialReusableMsgSize"; + // GC.encoderCharBufferSize + public static final String GC_ENCODER_CHAR_BUFFER_SIZE = "log4j2.encoderCharBufferSize"; + // GC.encoderByteBufferSize + public static final String GC_ENCODER_BYTE_BUFFER_SIZE = "log4j2.encoderByteBufferSize"; + // GC.layoutStringBuilderMaxSize + public static final String GC_LAYOUT_STRING_BUILDER_MAX_SIZE = "log4j2.layoutStringBuilderMaxSize"; + + // ThreadContext.contextData + public static final String THREAD_CONTEXT_DATA_CLASS_NAME = "log4j2.contextData"; + // ThreadContext.contextDataInjector + public static final String THREAD_CONTEXT_DATA_INJECTOR_CLASS_NAME = "log4j2.contextDataInjector"; + + // JMX.enabled + public static final String JMX_DISABLED = "log4j2.disableJmx"; + // JMX.notifyAsync + public static final String JMX_NOTIFY_ASYNC = "log4j2.jmxNotifyAsync"; + + // TransportSecurity.trustStore.location + public static final String TRANSPORT_SECURITY_TRUST_STORE_LOCATION = "log4j2.trustStoreLocation"; + // TransportSecurity.trustStore.password + public static final String TRANSPORT_SECURITY_TRUST_STORE_PASSWORD = "log4j2.trustStorePassword"; + // TransportSecurity.trustStore.passwordFile + public static final String TRANSPORT_SECURITY_TRUST_STORE_PASSWORD_FILE = "log4j2.trustStorePasswordFile"; + // TransportSecurity.trustStore.passwordEnvironmentVariable + public static final String TRANSPORT_SECURITY_TRUST_STORE_PASSWORD_ENV_VAR = "log4j2.trustStorePasswordEnvironmentVariable"; + // TransportSecurity.trustStore.keyStoreType + public static final String TRANSPORT_SECURITY_TRUST_STORE_KEY_STORE_TYPE = "log4j2.trustStoreKeyStoreType"; + // TransportSecurity.trustStore.keyManagerFactoryAlgorithm + public static final String TRANSPORT_SECURITY_TRUST_STORE_KEY_MANAGER_FACTORY_ALGORITHM = "log4j2.trustStoreKeyManagerFactoryAlgorithm"; + // TransportSecurity.keyStore.location + public static final String TRANSPORT_SECURITY_KEY_STORE_LOCATION = "log4j2.keyStoreLocation"; + // TransportSecurity.keyStore.password + public static final String TRANSPORT_SECURITY_KEY_STORE_PASSWORD = "log4j2.keyStorePassword"; + // TransportSecurity.keyStore.passwordFile + public static final String TRANSPORT_SECURITY_KEY_STORE_PASSWORD_FILE = "log4j2.keyStorePasswordFile"; + // TransportSecurity.keyStore.passwordEnvironmentVariable + public static final String TRANSPORT_SECURITY_KEY_STORE_PASSWORD_ENV_VAR = "log4j2.keyStorePasswordEnvironmentVariable"; + // TransportSecurity.keyStore.keyStoreType + public static final String TRANSPORT_SECURITY_KEY_STORE_TYPE = "log4j2.keyStoreType"; + // TransportSecurity.keyStore.keyManagerFactoryAlgorithm + public static final String TRANSPORT_SECURITY_KEY_STORE_KEY_MANAGER_FACTORY_ALGORITHM = "log4j2.keyStoreKeyManagerFactoryAlgorithm"; + // TransportSecurity.verifyHostName + public static final String TRANSPORT_SECURITY_VERIFY_HOST_NAME = "log4j2.sslVerifyHostName"; + + /** + * Property that may be used to seed the UUID generation with an integer value. + * + * @see org.apache.logging.log4j.core.util.UuidUtil + */ + // UUID.sequence + public static final String UUID_SEQUENCE = "log4j2.uuidSequence"; +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java index 499fb4534e3..61d4cad830d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java @@ -23,6 +23,6 @@ */ public class Log4jProvider extends Provider { public Log4jProvider() { - super(10, "2.6.0", Log4jContextFactory.class); + super(10, "3.0.0", Log4jContextFactory.class); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java index ad9128c4a8c..476396d8414 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java @@ -17,19 +17,33 @@ package org.apache.logging.log4j.core.impl; -import java.util.List; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Property; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.di.Key; + +import java.util.List; /** * */ public interface LogEventFactory { + Key KEY = new Key<>() {}; LogEvent createEvent(String loggerName, Marker marker, String fqcn, Level level, Message data, List properties, Throwable t); + + default LogEvent createEvent( + String loggerName, + Marker marker, + String fqcn, + @SuppressWarnings("unused") StackTraceElement location, + Level level, + Message data, + List properties, + Throwable t) { + return createEvent(loggerName, marker, fqcn, level, data, properties, t); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MementoMessage.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MementoMessage.java new file mode 100644 index 00000000000..3cfcb3d6c94 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MementoMessage.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.util.StringBuilderFormattable; + +import java.util.Arrays; + +/** + * Consider this class private. + * + * {@link MementoMessage} is intended to be used when we need to make an + * immutable copy of a {@link Message} without forgetting the original + * {@link Message#getFormat()} and {@link Message#getParameters()} values. + * + * @since 3.0 + */ +public final class MementoMessage implements Message, StringBuilderFormattable { + + private final String formattedMessage; + private final String format; + private final Object[] parameters; + + public MementoMessage(final String formattedMessage, final String format, final Object[] parameters) { + this.formattedMessage = formattedMessage; + this.format = format; + this.parameters = parameters; + } + + @Override + public String getFormattedMessage() { + return formattedMessage; + } + + @Override + public String getFormat() { + return format; + } + + @Override + public Object[] getParameters() { + return parameters; + } + + /** + * Always returns null. + * + * @return null + */ + @Override + public Throwable getThrowable() { + return null; + } + + @Override + public void formatTo(final StringBuilder buffer) { + buffer.append(formattedMessage); + } + + @Override + public String toString() { + return "MementoMessage{" + + "formattedMessage='" + formattedMessage + '\'' + + ", format='" + format + '\'' + + ", parameters=" + Arrays.toString(parameters) + + '}'; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java index d03223f5f9e..787a9121755 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java @@ -19,17 +19,23 @@ import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.util.Arrays; -import java.util.Map; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.async.InternalAsyncUtil; -import org.apache.logging.log4j.core.util.*; +import org.apache.logging.log4j.core.time.Clock; import org.apache.logging.log4j.core.time.Instant; import org.apache.logging.log4j.core.time.MutableInstant; -import org.apache.logging.log4j.message.*; +import org.apache.logging.log4j.core.time.NanoClock; +import org.apache.logging.log4j.core.util.Constants; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ParameterConsumer; +import org.apache.logging.log4j.message.ParameterVisitable; +import org.apache.logging.log4j.message.ReusableMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.message.TimestampMessage; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StringBuilders; @@ -40,12 +46,12 @@ * Mutable implementation of the {@code LogEvent} interface. * @since 2.6 */ -public class MutableLogEvent implements LogEvent, ReusableMessage { +public class MutableLogEvent implements LogEvent, ReusableMessage, ParameterVisitable { private static final Message EMPTY = new SimpleMessage(Strings.EMPTY); private int threadPriority; private long threadId; - private MutableInstant instant = new MutableInstant(); + private final MutableInstant instant = new MutableInstant(); private long nanoTime; private short parameterCount; private boolean includeLocation; @@ -54,6 +60,7 @@ public class MutableLogEvent implements LogEvent, ReusableMessage { private String threadName; private String loggerName; private Message message; + private String messageFormat; private StringBuilder messageText; private Object[] parameters; private Throwable thrown; @@ -66,7 +73,8 @@ public class MutableLogEvent implements LogEvent, ReusableMessage { transient boolean reserved = false; public MutableLogEvent() { - this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]); + // messageText and the parameter array are lazily initialized + this(null, null); } public MutableLogEvent(final StringBuilder msgText, final Object[] replacementParameters) { @@ -124,6 +132,7 @@ public void clear() { level = null; loggerName = null; message = null; + messageFormat = null; thrown = null; thrownProxy = null; source = null; @@ -144,9 +153,7 @@ public void clear() { StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE); if (parameters != null) { - for (int i = 0; i < parameters.length; i++) { - parameters[i] = null; - } + Arrays.fill(parameters, null); } // primitive fields that cannot be cleared: @@ -209,10 +216,9 @@ public void setMessage(final Message msg) { if (msg instanceof ReusableMessage) { final ReusableMessage reusable = (ReusableMessage) msg; reusable.formatTo(getMessageTextForWriting()); - if (parameters != null) { - parameters = reusable.swapParameters(parameters); - parameterCount = reusable.getParameterCount(); - } + this.messageFormat = msg.getFormat(); + parameters = reusable.swapParameters(parameters == null ? new Object[10] : parameters); + parameterCount = reusable.getParameterCount(); } else { this.message = InternalAsyncUtil.makeMessageImmutable(msg); } @@ -220,8 +226,7 @@ public void setMessage(final Message msg) { private StringBuilder getMessageTextForWriting() { if (messageText == null) { - // Should never happen: - // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false + // Happens the first time messageText is requested messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE); } messageText.setLength(0); @@ -241,7 +246,7 @@ public String getFormattedMessage() { */ @Override public String getFormat() { - return null; + return messageFormat; } /** @@ -252,6 +257,15 @@ public Object[] getParameters() { return parameters == null ? null : Arrays.copyOf(parameters, parameterCount); } + @Override + public void forEachParameter(final ParameterConsumer action, final S state) { + if (parameters != null) { + for (short i = 0; i < parameterCount; i++) { + action.accept(parameters[i], i, state); + } + } + } + /** * @see ReusableMessage#getThrowable() */ @@ -291,11 +305,10 @@ public short getParameterCount() { @Override public Message memento() { - if (message != null) { - return message; + if (message == null) { + message = new MementoMessage(String.valueOf(messageText), messageFormat, getParameters()); } - final Object[] params = parameters == null ? new Object[0] : Arrays.copyOf(parameters, parameterCount); - return new ParameterizedMessage(messageText.toString(), params); + return message; } @Override @@ -342,6 +355,10 @@ public ThrowableProxy getThrownProxy() { return thrownProxy; } + public void setSource(final StackTraceElement source) { + this.source = source; + } + /** * Returns the StackTraceElement for the caller. This will be the entry that occurs right * before the first occurrence of FQCN as a class name. @@ -365,11 +382,6 @@ public ReadOnlyStringMap getContextData() { return contextData; } - @Override - public Map getContextMap() { - return contextData.toMap(); - } - public void setContextData(final StringMap mutableContextData) { this.contextData = mutableContextData; } @@ -474,7 +486,7 @@ public void initializeBuilder(final Log4jLogEvent.Builder builder) { .setLoggerFqcn(loggerFqcn) // .setLoggerName(loggerName) // .setMarker(marker) // - .setMessage(getNonNullImmutableMessage()) // ensure non-null & immutable + .setMessage(memento()) // ensure non-null & immutable .setNanoTime(nanoTime) // .setSource(source) // .setThreadId(threadId) // @@ -485,8 +497,4 @@ public void initializeBuilder(final Log4jLogEvent.Builder builder) { .setInstant(instant) // ; } - - private Message getNonNullImmutableMessage() { - return message != null ? message : new SimpleMessage(String.valueOf(messageText)); - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java index 0ecefedb62a..3020455c06d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java @@ -16,8 +16,6 @@ */ package org.apache.logging.log4j.core.impl; -import java.util.List; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.ThreadContext; @@ -25,21 +23,33 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.async.ThreadNameCachingStrategy; import org.apache.logging.log4j.core.config.Property; -import org.apache.logging.log4j.core.util.Clock; -import org.apache.logging.log4j.core.util.ClockFactory; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.NanoClock; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Inject; import org.apache.logging.log4j.util.StringMap; +import java.util.List; + /** * Garbage-free LogEventFactory that reuses a single mutable log event. * @since 2.6 */ public class ReusableLogEventFactory implements LogEventFactory { private static final ThreadNameCachingStrategy THREAD_NAME_CACHING_STRATEGY = ThreadNameCachingStrategy.create(); - private static final Clock CLOCK = ClockFactory.getClock(); - private static ThreadLocal mutableLogEventThreadLocal = new ThreadLocal<>(); - private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); + private static final ThreadLocal mutableLogEventThreadLocal = new ThreadLocal<>(); + + private final ContextDataInjector injector; + private final Clock clock; + private final NanoClock nanoClock; + + @Inject + public ReusableLogEventFactory(final ContextDataInjector injector, final Clock clock, final NanoClock nanoClock) { + this.injector = injector; + this.clock = clock; + this.nanoClock = nanoClock; + } /** * Creates a log event. @@ -55,31 +65,40 @@ public class ReusableLogEventFactory implements LogEventFactory { */ @Override public LogEvent createEvent(final String loggerName, final Marker marker, - final String fqcn, final Level level, final Message message, - final List properties, final Throwable t) { - MutableLogEvent result = mutableLogEventThreadLocal.get(); - if (result == null || result.reserved) { - final boolean initThreadLocal = result == null; - result = new MutableLogEvent(); + final String fqcn, final Level level, final Message message, + final List properties, final Throwable t) { + return createEvent(loggerName, marker, fqcn, null, level, message, properties, t); + } - // usually no need to re-initialize thread-specific fields since the event is stored in a ThreadLocal - result.setThreadId(Thread.currentThread().getId()); - result.setThreadName(Thread.currentThread().getName()); // Thread.getName() allocates Objects on each call - result.setThreadPriority(Thread.currentThread().getPriority()); - if (initThreadLocal) { - mutableLogEventThreadLocal.set(result); - } - } + /** + * Creates a log event. + * + * @param loggerName The name of the Logger. + * @param marker An optional Marker. + * @param fqcn The fully qualified class name of the caller. + * @param level The event Level. + * @param message The Message. + * @param properties Properties to be added to the log event. + * @param t An optional Throwable. + * @return The LogEvent. + */ + @Override + public LogEvent createEvent(final String loggerName, final Marker marker, + final String fqcn, final StackTraceElement location, final Level level, final Message message, + final List properties, final Throwable t) { + MutableLogEvent result = getOrCreateMutableLogEvent(); result.reserved = true; - result.clear(); // ensure any previously cached values (thrownProxy, source, etc.) are cleared + // No need to clear here, values are cleared in release when reserved is set to false. + // If the event was dirty we'd create a new one. result.setLoggerName(loggerName); result.setMarker(marker); result.setLoggerFqcn(fqcn); result.setLevel(level == null ? Level.OFF : level); result.setMessage(message); - result.initTime(CLOCK, Log4jLogEvent.getNanoClock()); + result.initTime(clock, nanoClock); result.setThrown(t); + result.setSource(location); result.setContextData(injector.injectContextData(properties, (StringMap) result.getContextData())); result.setContextStack(ThreadContext.getDepth() == 0 ? ThreadContext.EMPTY_STACK : ThreadContext.cloneStack());// mutable copy @@ -90,6 +109,24 @@ public LogEvent createEvent(final String loggerName, final Marker marker, return result; } + private static MutableLogEvent getOrCreateMutableLogEvent() { + MutableLogEvent result = mutableLogEventThreadLocal.get(); + return result == null || result.reserved ? createInstance(result) : result; + } + + private static MutableLogEvent createInstance(MutableLogEvent existing) { + MutableLogEvent result = new MutableLogEvent(); + + // usually no need to re-initialize thread-specific fields since the event is stored in a ThreadLocal + result.setThreadId(Thread.currentThread().getId()); + result.setThreadName(Thread.currentThread().getName()); // Thread.getName() allocates Objects on each call + result.setThreadPriority(Thread.currentThread().getPriority()); + if (existing == null) { + mutableLogEventThreadLocal.set(result); + } + return result; + } + /** * Switches the {@code reserved} flag off if the specified event is a MutableLogEvent, otherwise does nothing. * This flag is used internally to verify that a reusable log event is no longer in use and can be reused. @@ -98,7 +135,9 @@ public LogEvent createEvent(final String loggerName, final Marker marker, */ public static void release(final LogEvent logEvent) { // LOG4J2-1583 if (logEvent instanceof MutableLogEvent) { - ((MutableLogEvent) logEvent).reserved = false; + final MutableLogEvent mutableLogEvent = (MutableLogEvent) logEvent; + mutableLogEvent.clear(); + mutableLogEvent.reserved = false; } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java index 25f1d031419..9fc5dbcee82 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java @@ -16,15 +16,22 @@ */ package org.apache.logging.log4j.core.impl; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentLinkedDeque; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.core.ContextDataInjector; import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.util.ContextDataProvider; import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap; import org.apache.logging.log4j.util.ReadOnlyStringMap; +import org.apache.logging.log4j.util.ServiceRegistry; import org.apache.logging.log4j.util.StringMap; /** @@ -44,6 +51,36 @@ */ public class ThreadContextDataInjector { + /** + * ContextDataProviders loaded via OSGi. + */ + public static Collection contextDataProviders = + new ConcurrentLinkedDeque<>(); + + private static final List SERVICE_PROVIDERS = getServiceProviders(); + + /** + * Previously this method allowed ContextDataProviders to be loaded eagerly, now they + * are loaded when this class is initialized. + * + * @deprecated no-op + */ + @Deprecated + public static void initServiceProviders() {} + + private static List getServiceProviders() { + final List providers = new ArrayList<>(); + final List services = ServiceRegistry.getInstance() + .getServices(ContextDataProvider.class, MethodHandles.lookup(), null); + for (final ContextDataProvider provider : services) { + if (providers.stream().noneMatch((p) -> p.getClass().isAssignableFrom(provider.getClass()))) { + providers.add(provider); + } + } + return Collections.unmodifiableList(providers); + } + + /** * Default {@code ContextDataInjector} for the legacy {@code Map}-based ThreadContext (which is * also the ThreadContext implementation used for web applications). @@ -52,24 +89,39 @@ public class ThreadContextDataInjector { */ public static class ForDefaultThreadContextMap implements ContextDataInjector { + private final List providers; + + public ForDefaultThreadContextMap() { + providers = getProviders(); + } + /** * Puts key-value pairs from both the specified list of properties as well as the thread context into the * specified reusable StringMap. * * @param props list of configuration properties, may be {@code null} - * @param ignore a {@code StringMap} instance from the log event + * @param contextData a {@code StringMap} instance from the log event * @return a {@code StringMap} combining configuration properties with thread context data */ @Override - public StringMap injectContextData(final List props, final StringMap ignore) { + public StringMap injectContextData(final List props, final StringMap contextData) { - final Map copy = ThreadContext.getImmutableContext(); + final Map copy; + + if (providers.size() == 1) { + copy = providers.get(0).supplyContextData(); + } else { + copy = new HashMap<>(); + for (final ContextDataProvider provider : providers) { + copy.putAll(provider.supplyContextData()); + } + } // The DefaultThreadContextMap stores context data in a Map. // This is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy. - // If there are no configuration properties returning a thin wrapper around the copy + // If there are no configuration properties or providers returning a thin wrapper around the copy // is faster than copying the elements into the LogEvent's reusable StringMap. - if (props == null || props.isEmpty()) { + if ((props == null || props.isEmpty())) { // this will replace the LogEvent's context data with the returned instance. // NOTE: must mark as frozen or downstream components may attempt to modify (UnsupportedOperationEx) return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : frozenStringMap(copy); @@ -114,6 +166,12 @@ public ReadOnlyStringMap rawContextData() { * This injector always puts key-value pairs into the specified reusable StringMap. */ public static class ForGarbageFreeThreadContextMap implements ContextDataInjector { + private final List providers; + + public ForGarbageFreeThreadContextMap() { + this.providers = getProviders(); + } + /** * Puts key-value pairs from both the specified list of properties as well as the thread context into the * specified reusable StringMap. @@ -128,9 +186,9 @@ public StringMap injectContextData(final List props, final StringMap r // StringMap. We cannot return the ThreadContext's internal data structure because it may be modified later // and such modifications should not be reflected in the log event. copyProperties(props, reusable); - - final ReadOnlyStringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData(); - reusable.putAll(immutableCopy); + for (int i = 0; i < providers.size(); ++i) { + reusable.putAll(providers.get(i).supplyStringMap()); + } return reusable; } @@ -149,6 +207,11 @@ public ReadOnlyStringMap rawContextData() { * specified reusable StringMap. */ public static class ForCopyOnWriteThreadContextMap implements ContextDataInjector { + private final List providers; + + public ForCopyOnWriteThreadContextMap() { + this.providers = getProviders(); + } /** * If there are no configuration properties, this injector will return the thread context's internal data * structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the @@ -162,17 +225,25 @@ public static class ForCopyOnWriteThreadContextMap implements ContextDataInjecto public StringMap injectContextData(final List props, final StringMap ignore) { // If there are no configuration properties we want to just return the ThreadContext's StringMap: // it is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy. - final StringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData(); - if (props == null || props.isEmpty()) { - return immutableCopy; // this will replace the LogEvent's context data with the returned instance + if (providers.size() == 1 && (props == null || props.isEmpty())) { + // this will replace the LogEvent's context data with the returned instance + return providers.get(0).supplyStringMap(); + } + int count = props == null ? 0 : props.size(); + final StringMap[] maps = new StringMap[providers.size()]; + for (int i = 0; i < providers.size(); ++i) { + maps[i] = providers.get(i).supplyStringMap(); + count += maps[i].size(); } // However, if the list of Properties is non-empty we need to combine the properties and the ThreadContext // data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined // and others not, so the LogEvent's context data may have been replaced with an immutable copy from // the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it. - final StringMap result = ContextDataFactory.createContextData(props.size() + immutableCopy.size()); + final StringMap result = ContextDataFactory.createContextData(count); copyProperties(props, result); - result.putAll(immutableCopy); + for (final StringMap map : maps) { + result.putAll(map); + } return result; } @@ -196,4 +267,12 @@ public static void copyProperties(final List properties, final StringM } } } + + private static List getProviders() { + final List providers = + new ArrayList<>(contextDataProviders.size() + SERVICE_PROVIDERS.size()); + providers.addAll(contextDataProviders); + providers.addAll(SERVICE_PROVIDERS); + return providers; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java new file mode 100644 index 00000000000..230123e3cbd --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.core.util.ContextDataProvider; +import org.apache.logging.log4j.util.StringMap; + +import java.util.Map; + +/** + * ContextDataProvider for ThreadContext data. + */ +public class ThreadContextDataProvider implements ContextDataProvider { + + @Override + public Map supplyContextData() { + return ThreadContext.getImmutableContext(); + } + + @Override + public StringMap supplyStringMap() { + return ThreadContext.getThreadContextMap().getReadOnlyContextData(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java index fe557add60e..1e4d3d889d5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java @@ -17,22 +17,16 @@ package org.apache.logging.log4j.core.impl; import java.io.Serializable; -import java.net.URL; -import java.security.CodeSource; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Stack; import org.apache.logging.log4j.core.pattern.PlainTextRenderer; import org.apache.logging.log4j.core.pattern.TextRenderer; -import org.apache.logging.log4j.core.util.Loader; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.LoaderUtil; import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.Strings; @@ -54,29 +48,6 @@ */ public class ThrowableProxy implements Serializable { - private static final String TAB = "\t"; - private static final String CAUSED_BY_LABEL = "Caused by: "; - private static final String SUPPRESSED_LABEL = "Suppressed: "; - private static final String WRAPPED_BY_LABEL = "Wrapped by: "; - - /** - * Cached StackTracePackageElement and ClassLoader. - *

    - * Consider this class private. - *

    - */ - static class CacheEntry { - private final ExtendedClassInfo element; - private final ClassLoader loader; - - public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) { - this.element = element; - this.loader = loader; - } - } - - private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0]; - private static final char EOL = '\n'; private static final String EOL_STR = String.valueOf(EOL); @@ -99,18 +70,20 @@ public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) { private final transient Throwable throwable; + static final ThrowableProxy[] EMPTY_ARRAY = {}; + /** * For JSON and XML IO via Jackson. */ @SuppressWarnings("unused") - private ThrowableProxy() { + ThrowableProxy() { this.throwable = null; this.name = null; - this.extendedStackTrace = null; + this.extendedStackTrace = ExtendedStackTraceElement.EMPTY_ARRAY; this.causeProxy = null; this.message = null; this.localizedMessage = null; - this.suppressedProxies = EMPTY_THROWABLE_PROXY_ARRAY; + this.suppressedProxies = ThrowableProxy.EMPTY_ARRAY; } /** @@ -128,19 +101,19 @@ public ThrowableProxy(final Throwable throwable) { * @param throwable The Throwable to wrap, must not be null. * @param visited The set of visited suppressed exceptions. */ - private ThrowableProxy(final Throwable throwable, final Set visited) { + ThrowableProxy(final Throwable throwable, final Set visited) { this.throwable = throwable; this.name = throwable.getClass().getName(); this.message = throwable.getMessage(); this.localizedMessage = throwable.getLocalizedMessage(); - final Map map = new HashMap<>(); - final Stack> stack = StackLocatorUtil.getCurrentStackTrace(); - this.extendedStackTrace = this.toExtendedStackTrace(stack, map, null, throwable.getStackTrace()); + final Map map = new HashMap<>(); + final Deque> stack = StackLocatorUtil.getCurrentStackTrace(); + this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, null, throwable.getStackTrace()); final Throwable throwableCause = throwable.getCause(); final Set causeVisited = new HashSet<>(1); this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause, visited, causeVisited); - this.suppressedProxies = this.toSuppressedProxies(throwable, visited); + this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(throwable, visited); } /** @@ -153,7 +126,8 @@ private ThrowableProxy(final Throwable throwable, final Set visited) * @param suppressedVisited TODO * @param causeVisited TODO */ - private ThrowableProxy(final Throwable parent, final Stack> stack, final Map map, + private ThrowableProxy(final Throwable parent, final Deque> stack, + final Map map, final Throwable cause, final Set suppressedVisited, final Set causeVisited) { causeVisited.add(cause); @@ -161,11 +135,11 @@ private ThrowableProxy(final Throwable parent, final Stack> stack, fina this.name = cause.getClass().getName(); this.message = this.throwable.getMessage(); this.localizedMessage = this.throwable.getLocalizedMessage(); - this.extendedStackTrace = this.toExtendedStackTrace(stack, map, parent.getStackTrace(), cause.getStackTrace()); + this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, parent.getStackTrace(), cause.getStackTrace()); final Throwable causeCause = cause.getCause(); this.causeProxy = causeCause == null || causeVisited.contains(causeCause) ? null : new ThrowableProxy(parent, stack, map, causeCause, suppressedVisited, causeVisited); - this.suppressedProxies = this.toSuppressedProxies(cause, suppressedVisited); + this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(cause, suppressedVisited); } @Override @@ -206,111 +180,6 @@ public boolean equals(final Object obj) { return true; } - private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause, - final List ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) { - formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator); - } - - private void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel, - final ThrowableProxy throwableProxy, final List ignorePackages, - final TextRenderer textRenderer, final String suffix, String lineSeparator) { - if (throwableProxy == null) { - return; - } - textRenderer.render(prefix, sb, "Prefix"); - textRenderer.render(causeLabel, sb, "CauseLabel"); - throwableProxy.renderOn(sb, textRenderer); - renderSuffix(suffix, sb, textRenderer); - textRenderer.render(lineSeparator, sb, "Text"); - this.formatElements(sb, prefix, throwableProxy.commonElementCount, - throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator); - this.formatSuppressed(sb, prefix + TAB, throwableProxy.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator); - this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); - } - - void renderOn(final StringBuilder output, final TextRenderer textRenderer) { - final String msg = this.message; - textRenderer.render(this.name, output, "Name"); - if (msg != null) { - textRenderer.render(": ", output, "NameMessageSeparator"); - textRenderer.render(msg, output, "Message"); - } - } - - private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies, - final List ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) { - if (suppressedProxies == null) { - return; - } - for (final ThrowableProxy suppressedProxy : suppressedProxies) { - formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator); - } - } - - private void formatElements(final StringBuilder sb, final String prefix, final int commonCount, - final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace, - final List ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) { - if (ignorePackages == null || ignorePackages.isEmpty()) { - for (final ExtendedStackTraceElement element : extStackTrace) { - this.formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator); - } - } else { - int count = 0; - for (int i = 0; i < extStackTrace.length; ++i) { - if (!this.ignoreElement(causedTrace[i], ignorePackages)) { - if (count > 0) { - appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator); - count = 0; - } - this.formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator); - } else { - ++count; - } - } - if (count > 0) { - appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator); - } - } - if (commonCount != 0) { - textRenderer.render(prefix, sb, "Prefix"); - textRenderer.render("\t... ", sb, "More"); - textRenderer.render(Integer.toString(commonCount), sb, "More"); - textRenderer.render(" more", sb, "More"); - renderSuffix(suffix, sb, textRenderer); - textRenderer.render(lineSeparator, sb, "Text"); - } - } - - private void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) { - if (!suffix.isEmpty()) { - textRenderer.render(" ", sb, "Suffix"); - textRenderer.render(suffix, sb, "Suffix"); - } - } - - private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count, - final TextRenderer textRenderer, final String suffix, String lineSeparator) { - textRenderer.render(prefix, sb, "Prefix"); - if (count == 1) { - textRenderer.render("\t... ", sb, "Suppressed"); - } else { - textRenderer.render("\t... suppressed ", sb, "Suppressed"); - textRenderer.render(Integer.toString(count), sb, "Suppressed"); - textRenderer.render(" lines", sb, "Suppressed"); - } - renderSuffix(suffix, sb, textRenderer); - textRenderer.render(lineSeparator, sb, "Text"); - } - - private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb, - final String prefix, final TextRenderer textRenderer, final String suffix, String lineSeparator) { - textRenderer.render(prefix, sb, "Prefix"); - textRenderer.render("\tat ", sb, "At"); - extStackTraceElement.renderOn(sb, textRenderer); - renderSuffix(suffix, sb, textRenderer); - textRenderer.render(lineSeparator, sb, "Text"); - } - /** * Formats the specified Throwable. * @param sb StringBuilder to contain the formatted Throwable. @@ -359,17 +228,7 @@ public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, fi @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { - final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null; - if (caused != null) { - this.formatWrapper(sb, cause.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); - sb.append(WRAPPED_BY_LABEL); - renderSuffix(suffix, sb, textRenderer); - } - cause.renderOn(sb, textRenderer); - renderSuffix(suffix, sb, textRenderer); - textRenderer.render(lineSeparator, sb, "Text"); - this.formatElements(sb, Strings.EMPTY, cause.commonElementCount, - cause.getThrowable().getStackTrace(), cause.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator); + ThrowableProxyRenderer.formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, lineSeparator); } public ThrowableProxy getCauseProxy() { @@ -420,16 +279,7 @@ public String getCauseStackTraceAsString(final List ignorePackages, fina */ public String getCauseStackTraceAsString(final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { final StringBuilder sb = new StringBuilder(); - if (this.causeProxy != null) { - this.formatWrapper(sb, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); - sb.append(WRAPPED_BY_LABEL); - renderSuffix(suffix, sb, textRenderer); - } - this.renderOn(sb, textRenderer); - renderSuffix(suffix, sb, textRenderer); - textRenderer.render(lineSeparator, sb, "Text"); - this.formatElements(sb, Strings.EMPTY, 0, this.throwable.getStackTrace(), this.extendedStackTrace, - ignorePackages, textRenderer, suffix, lineSeparator); + ThrowableProxyRenderer.formatCauseStackTrace(this, sb, ignorePackages, textRenderer, suffix, lineSeparator); return sb.toString(); } @@ -443,6 +293,17 @@ public int getCommonElementCount() { return this.commonElementCount; } + /** + * Set the value of {@link ThrowableProxy#commonElementCount}. + * + * Method is package-private, to be used internally for initialization. + * + * @param value New value of commonElementCount. + */ + void setCommonElementCount(final int value) { + this.commonElementCount = value; + } + /** * Gets the stack trace including packaging information. * @@ -505,18 +366,23 @@ public String getExtendedStackTraceAsString(final List ignorePackages, f */ public String getExtendedStackTraceAsString(final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { final StringBuilder sb = new StringBuilder(1024); - textRenderer.render(name, sb, "Name"); - textRenderer.render(": ", sb, "NameMessageSeparator"); - textRenderer.render(this.message, sb, "Message"); - renderSuffix(suffix, sb, textRenderer); - textRenderer.render(lineSeparator, sb, "Text"); - final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null; - this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator); - this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator); - this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); + formatExtendedStackTraceTo(sb, ignorePackages, textRenderer, suffix, lineSeparator); return sb.toString(); } + /** + * Formats the stack trace including packaging information. + * + * @param sb Destination. + * @param ignorePackages List of packages to be ignored in the trace. + * @param textRenderer The message renderer. + * @param suffix Append this to the end of each stack frame. + * @param lineSeparator The end-of-line separator. + */ + public void formatExtendedStackTraceTo(final StringBuilder sb, final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + ThrowableProxyRenderer.formatExtendedStackTraceTo(this, sb, ignorePackages, textRenderer, suffix, lineSeparator); + } + public String getLocalizedMessage() { return this.localizedMessage; } @@ -586,196 +452,9 @@ public int hashCode() { return result; } - private boolean ignoreElement(final StackTraceElement element, final List ignorePackages) { - if (ignorePackages != null) { - final String className = element.getClassName(); - for (final String pkg : ignorePackages) { - if (className.startsWith(pkg)) { - return true; - } - } - } - return false; - } - - /** - * Loads classes not located via Reflection.getCallerClass. - * - * @param lastLoader The ClassLoader that loaded the Class that called this Class. - * @param className The name of the Class. - * @return The Class object for the Class or null if it could not be located. - */ - private Class loadClass(final ClassLoader lastLoader, final String className) { - // XXX: this is overly complicated - Class clazz; - if (lastLoader != null) { - try { - clazz = lastLoader.loadClass(className); - if (clazz != null) { - return clazz; - } - } catch (final Throwable ignore) { - // Ignore exception. - } - } - try { - clazz = LoaderUtil.loadClass(className); - } catch (final ClassNotFoundException | NoClassDefFoundError e) { - return loadClass(className); - } catch (final SecurityException e) { - return null; - } - return clazz; - } - - private Class loadClass(final String className) { - try { - return Loader.loadClass(className, this.getClass().getClassLoader()); - } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) { - return null; - } - } - - /** - * Construct the CacheEntry from the Class's information. - * - * @param stackTraceElement The stack trace element - * @param callerClass The Class. - * @param exact True if the class was obtained via Reflection.getCallerClass. - * @return The CacheEntry. - */ - private CacheEntry toCacheEntry(final StackTraceElement stackTraceElement, final Class callerClass, - final boolean exact) { - String location = "?"; - String version = "?"; - ClassLoader lastLoader = null; - if (callerClass != null) { - try { - final CodeSource source = callerClass.getProtectionDomain().getCodeSource(); - if (source != null) { - final URL locationURL = source.getLocation(); - if (locationURL != null) { - final String str = locationURL.toString().replace('\\', '/'); - int index = str.lastIndexOf("/"); - if (index >= 0 && index == str.length() - 1) { - index = str.lastIndexOf("/", index - 1); - location = str.substring(index + 1); - } else { - location = str.substring(index + 1); - } - } - } - } catch (final Exception ex) { - // Ignore the exception. - } - final Package pkg = callerClass.getPackage(); - if (pkg != null) { - final String ver = pkg.getImplementationVersion(); - if (ver != null) { - version = ver; - } - } - try { - lastLoader = callerClass.getClassLoader(); - } catch (final SecurityException e) { - lastLoader = null; - } - } - return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader); - } - - /** - * Resolve all the stack entries in this stack trace that are not common with the parent. - * - * @param stack The callers Class stack. - * @param map The cache of CacheEntry objects. - * @param rootTrace The first stack trace resolve or null. - * @param stackTrace The stack trace being resolved. - * @return The StackTracePackageElement array. - */ - ExtendedStackTraceElement[] toExtendedStackTrace(final Stack> stack, final Map map, - final StackTraceElement[] rootTrace, - final StackTraceElement[] stackTrace) { - int stackLength; - if (rootTrace != null) { - int rootIndex = rootTrace.length - 1; - int stackIndex = stackTrace.length - 1; - while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) { - --rootIndex; - --stackIndex; - } - this.commonElementCount = stackTrace.length - 1 - stackIndex; - stackLength = stackIndex + 1; - } else { - this.commonElementCount = 0; - stackLength = stackTrace.length; - } - final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength]; - Class clazz = stack.isEmpty() ? null : stack.peek(); - ClassLoader lastLoader = null; - for (int i = stackLength - 1; i >= 0; --i) { - final StackTraceElement stackTraceElement = stackTrace[i]; - final String className = stackTraceElement.getClassName(); - // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke() - // and its implementation. The Throwable might also contain stack entries that are no longer - // present as those methods have returned. - ExtendedClassInfo extClassInfo; - if (clazz != null && className.equals(clazz.getName())) { - final CacheEntry entry = this.toCacheEntry(stackTraceElement, clazz, true); - extClassInfo = entry.element; - lastLoader = entry.loader; - stack.pop(); - clazz = stack.isEmpty() ? null : stack.peek(); - } else { - final CacheEntry cacheEntry = map.get(className); - if (cacheEntry != null) { - final CacheEntry entry = cacheEntry; - extClassInfo = entry.element; - if (entry.loader != null) { - lastLoader = entry.loader; - } - } else { - final CacheEntry entry = this.toCacheEntry(stackTraceElement, - this.loadClass(lastLoader, className), false); - extClassInfo = entry.element; - map.put(stackTraceElement.toString(), entry); - if (entry.loader != null) { - lastLoader = entry.loader; - } - } - } - extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo); - } - return extStackTrace; - } - @Override public String toString() { final String msg = this.message; return msg != null ? this.name + ": " + msg : this.name; } - - private ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set suppressedVisited) { - try { - final Throwable[] suppressed = thrown.getSuppressed(); - if (suppressed == null) { - return EMPTY_THROWABLE_PROXY_ARRAY; - } - final List proxies = new ArrayList<>(suppressed.length); - if (suppressedVisited == null) { - suppressedVisited = new HashSet<>(proxies.size()); - } - for (int i = 0; i < suppressed.length; i++) { - final Throwable candidate = suppressed[i]; - if (!suppressedVisited.contains(candidate)) { - suppressedVisited.add(candidate); - proxies.add(new ThrowableProxy(candidate, suppressedVisited)); - } - } - return proxies.toArray(new ThrowableProxy[proxies.size()]); - } catch (final Exception e) { - StatusLogger.getLogger().error(e); - } - return null; - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java new file mode 100644 index 00000000000..66b701447f4 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import java.net.URL; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.core.util.Loader; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.LoaderUtil; + +/** + * {@link ThrowableProxyHelper} provides utilities required to initialize a new {@link ThrowableProxy} + * instance. + */ +class ThrowableProxyHelper { + + private ThrowableProxyHelper() { + // Utility Class + } + + /** + * Cached StackTracePackageElement and ClassLoader. + *

    + * Consider this class private. + *

    + */ + static final class CacheEntry { + private final ExtendedClassInfo element; + private final ClassLoader loader; + + private CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) { + this.element = element; + this.loader = loader; + } + } + + /** + * Resolve all the stack entries in this stack trace that are not common with the parent. + * + * @param src Instance for which to build an extended stack trace. + * @param stack The callers Class stack. + * @param map The cache of CacheEntry objects. + * @param rootTrace The first stack trace resolve or null. + * @param stackTrace The stack trace being resolved. + * @return The StackTracePackageElement array. + */ + static ExtendedStackTraceElement[] toExtendedStackTrace( + final ThrowableProxy src, + final Deque> stack, final Map map, + final StackTraceElement[] rootTrace, + final StackTraceElement[] stackTrace) { + final int stackLength; + if (rootTrace != null) { + int rootIndex = rootTrace.length - 1; + int stackIndex = stackTrace.length - 1; + while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) { + --rootIndex; + --stackIndex; + } + src.setCommonElementCount(stackTrace.length - 1 - stackIndex); + stackLength = stackIndex + 1; + } else { + src.setCommonElementCount(0); + stackLength = stackTrace.length; + } + final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength]; + Class clazz = stack.isEmpty() ? null : stack.peek(); + ClassLoader lastLoader = null; + for (int i = stackLength - 1; i >= 0; --i) { + final StackTraceElement stackTraceElement = stackTrace[i]; + final String className = stackTraceElement.getClassName(); + // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke() + // and its implementation. The Throwable might also contain stack entries that are no longer + // present as those methods have returned. + final ExtendedClassInfo extClassInfo; + if (clazz != null && className.equals(clazz.getName())) { + final CacheEntry entry = toCacheEntry(clazz, true); + extClassInfo = entry.element; + lastLoader = entry.loader; + stack.pop(); + clazz = stack.isEmpty() ? null : stack.peek(); + } else { + final CacheEntry cacheEntry = map.get(className); + if (cacheEntry != null) { + final CacheEntry entry = cacheEntry; + extClassInfo = entry.element; + if (entry.loader != null) { + lastLoader = entry.loader; + } + } else { + final CacheEntry entry = toCacheEntry(ThrowableProxyHelper.loadClass(lastLoader, className), false); + extClassInfo = entry.element; + map.put(className, entry); + if (entry.loader != null) { + lastLoader = entry.loader; + } + } + } + extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo); + } + return extStackTrace; + } + + static ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set suppressedVisited) { + try { + final Throwable[] suppressed = thrown.getSuppressed(); + if (suppressed == null || suppressed.length == 0) { + return ThrowableProxy.EMPTY_ARRAY; + } + final List proxies = new ArrayList<>(suppressed.length); + if (suppressedVisited == null) { + suppressedVisited = new HashSet<>(suppressed.length); + } + for (int i = 0; i < suppressed.length; i++) { + final Throwable candidate = suppressed[i]; + if (suppressedVisited.add(candidate)) { + proxies.add(new ThrowableProxy(candidate, suppressedVisited)); + } + } + return proxies.toArray(ThrowableProxy.EMPTY_ARRAY); + } catch (final Exception e) { + StatusLogger.getLogger().error(e); + } + return null; + } + + /** + * Construct the CacheEntry from the Class's information. + * + * @param callerClass The Class. + * @param exact True if the class was obtained via Reflection.getCallerClass. + * @return The CacheEntry. + */ + private static CacheEntry toCacheEntry(final Class callerClass, final boolean exact) { + String location = "?"; + String version = "?"; + ClassLoader lastLoader = null; + if (callerClass != null) { + try { + final CodeSource source = callerClass.getProtectionDomain().getCodeSource(); + if (source != null) { + final URL locationURL = source.getLocation(); + if (locationURL != null) { + final String str = locationURL.toString().replace('\\', '/'); + int index = str.lastIndexOf("/"); + if (index >= 0 && index == str.length() - 1) { + index = str.lastIndexOf("/", index - 1); + location = str.substring(index + 1); + } else { + location = str.substring(index + 1); + } + } + } + } catch (final Exception ex) { + // Ignore the exception. + } + final Package pkg = callerClass.getPackage(); + if (pkg != null) { + final String ver = pkg.getImplementationVersion(); + if (ver != null) { + version = ver; + } + } + try { + lastLoader = callerClass.getClassLoader(); + } catch (final SecurityException e) { + lastLoader = null; + } + } + return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader); + } + + + /** + * Loads classes not located via Reflection.getCallerClass. + * + * @param lastLoader The ClassLoader that loaded the Class that called this Class. + * @param className The name of the Class. + * @return The Class object for the Class or null if it could not be located. + */ + private static Class loadClass(final ClassLoader lastLoader, final String className) { + // XXX: this is overly complicated + Class clazz; + if (lastLoader != null) { + try { + clazz = lastLoader.loadClass(className); + if (clazz != null) { + return clazz; + } + } catch (final Throwable ignore) { + // Ignore exception. + } + } + try { + clazz = LoaderUtil.loadClass(className); + } catch (final ClassNotFoundException | NoClassDefFoundError e) { + return loadClass(className); + } catch (final SecurityException e) { + return null; + } + return clazz; + } + + private static Class loadClass(final String className) { + try { + return Loader.loadClass(className, ThrowableProxyHelper.class.getClassLoader()); + } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) { + return null; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java new file mode 100644 index 00000000000..f13943a5245 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.impl; + +import org.apache.logging.log4j.core.pattern.TextRenderer; +import org.apache.logging.log4j.util.Strings; + +import java.util.List; + +/** + * {@link ThrowableProxyRenderer} is an internal utility providing the code to render a {@link ThrowableProxy} + * to a {@link StringBuilder}. + */ +class ThrowableProxyRenderer { + + private static final String TAB = "\t"; + private static final String CAUSED_BY_LABEL = "Caused by: "; + private static final String SUPPRESSED_LABEL = "Suppressed: "; + private static final String WRAPPED_BY_LABEL = "Wrapped by: "; + + private ThrowableProxyRenderer() { + // Utility Class + } + + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + static void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List ignorePackages, + final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null; + if (caused != null) { + formatWrapper(sb, cause.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator); + sb.append(WRAPPED_BY_LABEL); + renderSuffix(suffix, sb, textRenderer); + } + renderOn(cause, sb, textRenderer); + renderSuffix(suffix, sb, textRenderer); + textRenderer.render(lineSeparator, sb, "Text"); + formatElements(sb, Strings.EMPTY, cause.getCommonElementCount(), + cause.getThrowable().getStackTrace(), cause.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator); + } + + private static void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause, + final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator); + } + + private static void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel, + final ThrowableProxy throwableProxy, final List ignorePackages, + final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + if (throwableProxy == null) { + return; + } + textRenderer.render(prefix, sb, "Prefix"); + textRenderer.render(causeLabel, sb, "CauseLabel"); + renderOn(throwableProxy, sb, textRenderer); + renderSuffix(suffix, sb, textRenderer); + textRenderer.render(lineSeparator, sb, "Text"); + formatElements(sb, prefix, throwableProxy.getCommonElementCount(), + throwableProxy.getStackTrace(), throwableProxy.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator); + formatSuppressed(sb, prefix + TAB, throwableProxy.getSuppressedProxies(), ignorePackages, textRenderer, suffix, lineSeparator); + formatCause(sb, prefix, throwableProxy.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator); + } + + private static void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies, + final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + if (suppressedProxies == null) { + return; + } + for (final ThrowableProxy suppressedProxy : suppressedProxies) { + formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator); + } + } + + private static void formatElements(final StringBuilder sb, final String prefix, final int commonCount, + final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace, + final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + if (ignorePackages == null || ignorePackages.isEmpty()) { + for (final ExtendedStackTraceElement element : extStackTrace) { + formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator); + } + } else { + int count = 0; + for (int i = 0; i < extStackTrace.length; ++i) { + if (!ignoreElement(causedTrace[i], ignorePackages)) { + if (count > 0) { + appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator); + count = 0; + } + formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator); + } else { + ++count; + } + } + if (count > 0) { + appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator); + } + } + if (commonCount != 0) { + textRenderer.render(prefix, sb, "Prefix"); + textRenderer.render("\t... ", sb, "More"); + textRenderer.render(Integer.toString(commonCount), sb, "More"); + textRenderer.render(" more", sb, "More"); + renderSuffix(suffix, sb, textRenderer); + textRenderer.render(lineSeparator, sb, "Text"); + } + } + + private static void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) { + if (!suffix.isEmpty()) { + textRenderer.render(" ", sb, "Suffix"); + textRenderer.render(suffix, sb, "Suffix"); + } + } + + private static void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count, + final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + textRenderer.render(prefix, sb, "Prefix"); + if (count == 1) { + textRenderer.render("\t... ", sb, "Suppressed"); + } else { + textRenderer.render("\t... suppressed ", sb, "Suppressed"); + textRenderer.render(Integer.toString(count), sb, "Suppressed"); + textRenderer.render(" lines", sb, "Suppressed"); + } + renderSuffix(suffix, sb, textRenderer); + textRenderer.render(lineSeparator, sb, "Text"); + } + + private static void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb, + final String prefix, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + textRenderer.render(prefix, sb, "Prefix"); + textRenderer.render("\tat ", sb, "At"); + extStackTraceElement.renderOn(sb, textRenderer); + renderSuffix(suffix, sb, textRenderer); + textRenderer.render(lineSeparator, sb, "Text"); + } + + private static boolean ignoreElement(final StackTraceElement element, final List ignorePackages) { + if (ignorePackages != null) { + final String className = element.getClassName(); + for (final String pkg : ignorePackages) { + if (className.startsWith(pkg)) { + return true; + } + } + } + return false; + } + + /** + * Formats the stack trace including packaging information. + * + * @param src ThrowableProxy instance to format + * @param sb Destination. + * @param ignorePackages List of packages to be ignored in the trace. + * @param textRenderer The message renderer. + * @param suffix Append this to the end of each stack frame. + * @param lineSeparator The end-of-line separator. + */ + static void formatExtendedStackTraceTo(final ThrowableProxy src, final StringBuilder sb, final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + textRenderer.render(src.getName(), sb, "Name"); + textRenderer.render(": ", sb, "NameMessageSeparator"); + textRenderer.render(src.getMessage(), sb, "Message"); + renderSuffix(suffix, sb, textRenderer); + textRenderer.render(lineSeparator, sb, "Text"); + final StackTraceElement[] causedTrace = src.getThrowable() != null ? src.getThrowable().getStackTrace() : null; + formatElements(sb, Strings.EMPTY, 0, causedTrace, src.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator); + formatSuppressed(sb, TAB, src.getSuppressedProxies(), ignorePackages, textRenderer, suffix, lineSeparator); + formatCause(sb, Strings.EMPTY, src.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator); + } + + /** + * Formats the Throwable that is the cause of the
    src
    Throwable. + * + * @param src Throwable whose cause to render + * @param sb Destination to render the formatted Throwable that caused this Throwable onto. + * @param ignorePackages The List of packages to be suppressed from the stack trace. + * @param textRenderer The text renderer. + * @param suffix Append this to the end of each stack frame. + * @param lineSeparator The end-of-line separator. + */ + static void formatCauseStackTrace(final ThrowableProxy src, final StringBuilder sb, final List ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { + final ThrowableProxy causeProxy = src.getCauseProxy(); + if (causeProxy != null) { + formatWrapper(sb, causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); + sb.append(WRAPPED_BY_LABEL); + ThrowableProxyRenderer.renderSuffix(suffix, sb, textRenderer); + } + renderOn(src, sb, textRenderer); + ThrowableProxyRenderer.renderSuffix(suffix, sb, textRenderer); + textRenderer.render(lineSeparator, sb, "Text"); + ThrowableProxyRenderer.formatElements(sb, Strings.EMPTY, 0, src.getStackTrace(), src.getExtendedStackTrace(), + ignorePackages, textRenderer, suffix, lineSeparator); + } + + private static void renderOn(final ThrowableProxy src, final StringBuilder output, final TextRenderer textRenderer) { + final String msg = src.getMessage(); + textRenderer.render(src.getName(), output, "Name"); + if (msg != null) { + textRenderer.render(": ", output, "NameMessageSeparator"); + textRenderer.render(msg, output, "Message"); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ExtendedStackTraceElementMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ExtendedStackTraceElementMixIn.java deleted file mode 100644 index 84df3d60750..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ExtendedStackTraceElementMixIn.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import java.io.Serializable; - -import org.apache.logging.log4j.core.impl.ExtendedClassInfo; -import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; - -/** - * Mix-in for {@link ExtendedStackTraceElement}. - */ -@JsonPropertyOrder({ "class", "method", "file", "line", "exact", "location", "version" }) -abstract class ExtendedStackTraceElementMixIn implements Serializable { - - private static final long serialVersionUID = 1L; - - @JsonCreator - public ExtendedStackTraceElementMixIn( - // @formatter:off - @JsonProperty("class") final String declaringClass, - @JsonProperty("method") final String methodName, - @JsonProperty("file") final String fileName, - @JsonProperty("line") final int lineNumber, - @JsonProperty("exact") final boolean exact, - @JsonProperty("location") final String location, - @JsonProperty("version") final String version - // @formatter:on - ) { - // empty - } - - @JsonProperty("class") - @JacksonXmlProperty(localName = "class", isAttribute = true) - public abstract String getClassName(); - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - public abstract boolean getExact(); - - @JsonIgnore - public abstract ExtendedClassInfo getExtraClassInfo(); - - @JsonProperty("file") - @JacksonXmlProperty(localName = "file", isAttribute = true) - public abstract String getFileName(); - - @JsonProperty("line") - @JacksonXmlProperty(localName = "line", isAttribute = true) - public abstract int getLineNumber(); - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - public abstract String getLocation(); - - @JsonProperty("method") - @JacksonXmlProperty(localName = "method", isAttribute = true) - public abstract String getMethodName(); - - @JsonIgnore - abstract StackTraceElement getStackTraceElement(); - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - public abstract String getVersion(); - - @JsonIgnore - public abstract boolean isNativeMethod(); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java deleted file mode 100644 index 3bc34470452..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.ThreadContext.ContextStack; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; -import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.ObjectMessage; - -import com.fasterxml.jackson.databind.Module.SetupContext; -import com.fasterxml.jackson.databind.module.SimpleModule; -import org.apache.logging.log4j.core.time.Instant; - -/** - * Initialization utils. - *

    - * Consider this class private. - *

    - */ -class Initializers { - - /** - * Used to set up {@link SetupContext} from different {@link SimpleModule}s. - */ - static class SetupContextInitializer { - - void setupModule(final SetupContext context, final boolean includeStacktrace, final boolean stacktraceAsString) { - // JRE classes: we cannot edit those with Jackson annotations - context.setMixInAnnotations(StackTraceElement.class, StackTraceElementMixIn.class); - // Log4j API classes: we do not want to edit those with Jackson annotations because the API module should not depend on Jackson. - context.setMixInAnnotations(Marker.class, MarkerMixIn.class); - context.setMixInAnnotations(Level.class, LevelMixIn.class); - context.setMixInAnnotations(Instant.class, InstantMixIn.class); - context.setMixInAnnotations(LogEvent.class, LogEventWithContextListMixIn.class); - // Log4j Core classes: we do not want to bring in Jackson at runtime if we do not have to. - context.setMixInAnnotations(ExtendedStackTraceElement.class, ExtendedStackTraceElementMixIn.class); - context.setMixInAnnotations(ThrowableProxy.class, - includeStacktrace ? (stacktraceAsString ? ThrowableProxyWithStacktraceAsStringMixIn.class : ThrowableProxyMixIn.class ) : ThrowableProxyWithoutStacktraceMixIn.class); - } - } - - /** - * Used to set up {@link SetupContext} from different {@link SimpleModule}s. - * Differs from SetupContextInitializer by installing {@code LogEventJsonMixIn} for LogEvents, - * not {@code LogEventMixIn}, so it handles ThreadContext serialization differently. - */ - static class SetupContextJsonInitializer { - - void setupModule(final SetupContext context, final boolean includeStacktrace, final boolean stacktraceAsString) { - // JRE classes: we cannot edit those with Jackson annotations - context.setMixInAnnotations(StackTraceElement.class, StackTraceElementMixIn.class); - // Log4j API classes: we do not want to edit those with Jackson annotations because the API module should not depend on Jackson. - context.setMixInAnnotations(Marker.class, MarkerMixIn.class); - context.setMixInAnnotations(Level.class, LevelMixIn.class); - context.setMixInAnnotations(Instant.class, InstantMixIn.class); - context.setMixInAnnotations(LogEvent.class, LogEventJsonMixIn.class); // different ThreadContext handling - // Log4j Core classes: we do not want to bring in Jackson at runtime if we do not have to. - context.setMixInAnnotations(ExtendedStackTraceElement.class, ExtendedStackTraceElementMixIn.class); - context.setMixInAnnotations(ThrowableProxy.class, - includeStacktrace ? (stacktraceAsString ? ThrowableProxyWithStacktraceAsStringMixIn.class : ThrowableProxyMixIn.class ) : ThrowableProxyWithoutStacktraceMixIn.class); - } - } - - /** - * Used to set up {@link SimpleModule} from different {@link SimpleModule} subclasses. - */ - static class SimpleModuleInitializer { - void initialize(final SimpleModule simpleModule, final boolean objectMessageAsJsonObject) { - // Workaround because mix-ins do not work for classes that already have a built-in deserializer. - // See Jackson issue 429. - simpleModule.addDeserializer(StackTraceElement.class, new Log4jStackTraceElementDeserializer()); - simpleModule.addDeserializer(ContextStack.class, new MutableThreadContextStackDeserializer()); - if (objectMessageAsJsonObject) { - simpleModule.addSerializer(ObjectMessage.class, new ObjectMessageSerializer()); - } - simpleModule.addSerializer(Message.class, new MessageSerializer()); - } - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/InstantMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/InstantMixIn.java deleted file mode 100644 index 216534b0f4d..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/InstantMixIn.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.core.time.Instant; - -/** - * Jackson mix-in for {@link Instant}. - *

    - * Consider this class private. - *

    - * @see Marker - */ -@JsonIgnoreProperties({ "epochMillisecond", "nanoOfMillisecond" }) -abstract class InstantMixIn { - - @JsonCreator - InstantMixIn( - // @formatter:off - @JsonProperty("epochSecond") final long epochSecond, - @JsonProperty("nanoOfSecond") final int nanoOfSecond) - // @formatter:on - { - // empty - } - - @JsonProperty("epochSecond") - @JacksonXmlProperty(localName = "epochSecond", isAttribute = true) - abstract long getEpochSecond(); - - @JsonProperty("nanoOfSecond") - @JacksonXmlProperty(localName = "nanoOfSecond", isAttribute = true) - abstract int getNanoOfSecond(); -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jStackTraceElementDeserializer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jStackTraceElementDeserializer.java deleted file mode 100644 index 936ace19d2d..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jStackTraceElementDeserializer.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; - -/** - * Copy and edit the Jackson (Apache License 2.0) class to use Log4j attribute names. Does not work as of Jackson 2.3.2. - *

    - * Consider this class private. - *

    - */ -public final class Log4jStackTraceElementDeserializer extends StdScalarDeserializer { - private static final long serialVersionUID = 1L; - - /** - * Constructs a new initialized instance. - */ - public Log4jStackTraceElementDeserializer() { - super(StackTraceElement.class); - } - - @Override - public StackTraceElement deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, - JsonProcessingException { - JsonToken t = jp.getCurrentToken(); - // Must get an Object - if (t == JsonToken.START_OBJECT) { - String className = null, methodName = null, fileName = null; - int lineNumber = -1; - - while ((t = jp.nextValue()) != JsonToken.END_OBJECT) { - final String propName = jp.getCurrentName(); - if ("class".equals(propName)) { - className = jp.getText(); - } else if ("file".equals(propName)) { - fileName = jp.getText(); - } else if ("line".equals(propName)) { - if (t.isNumeric()) { - lineNumber = jp.getIntValue(); - } else { - // An XML number always comes in a string since there is no syntax help as with JSON. - try { - lineNumber = Integer.parseInt(jp.getText().trim()); - } catch (final NumberFormatException e) { - throw JsonMappingException.from(jp, "Non-numeric token (" + t + ") for property 'line'", e); - } - } - } else if ("method".equals(propName)) { - methodName = jp.getText(); - } else if ("nativeMethod".equals(propName)) { - // no setter, not passed via constructor: ignore - } else { - this.handleUnknownProperty(jp, ctxt, this._valueClass, propName); - } - } - return new StackTraceElement(className, methodName, fileName, lineNumber); - } - throw ctxt.mappingException(this._valueClass, t); - } -} \ No newline at end of file diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventJsonMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventJsonMixIn.java deleted file mode 100644 index bbb024de965..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventJsonMixIn.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import java.util.Map; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.ThreadContext.ContextStack; -import org.apache.logging.log4j.core.time.Instant; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.apache.logging.log4j.message.Message; - -import com.fasterxml.jackson.annotation.JsonFilter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; - -@JsonRootName(XmlConstants.ELT_EVENT) -@JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT) -@JsonFilter("org.apache.logging.log4j.core.impl.Log4jLogEvent") -@JsonPropertyOrder({ "timeMillis", XmlConstants.ELT_INSTANT, "threadName", "level", "loggerName", "marker", "message", "thrown", XmlConstants.ELT_CONTEXT_MAP, - JsonConstants.ELT_CONTEXT_STACK, "loggerFQCN", "Source", "endOfBatch" }) -abstract class LogEventJsonMixIn implements LogEvent { - - private static final long serialVersionUID = 1L; - -// @JsonProperty(JsonConstants.ELT_CONTEXT_MAP) -// @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_MAP) -// @JsonSerialize(using = MapSerializer.class) -// @JsonDeserialize(using = MapDeserializer.class) - @Override - @JsonIgnore -// @JsonProperty(JsonConstants.ELT_CONTEXT_MAP) -// @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_MAP) - public abstract Map getContextMap(); - - @JsonProperty(JsonConstants.ELT_CONTEXT_MAP) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_MAP) - @JsonSerialize(using = ContextDataSerializer.class) - @JsonDeserialize(using = ContextDataDeserializer.class) - //@JsonIgnore - @Override - public abstract ReadOnlyStringMap getContextData(); - - @JsonProperty(JsonConstants.ELT_CONTEXT_STACK) - @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_STACK) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_STACK_ITEM) - @Override - public abstract ContextStack getContextStack(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract Level getLevel(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract String getLoggerFqcn(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract String getLoggerName(); - - @JsonProperty(JsonConstants.ELT_MARKER) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_MARKER) - @Override - public abstract Marker getMarker(); - - @JsonProperty(JsonConstants.ELT_MESSAGE) - @JsonDeserialize(using = SimpleMessageDeserializer.class) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_MESSAGE) - @Override - public abstract Message getMessage(); - - @JsonProperty(JsonConstants.ELT_SOURCE) - @JsonDeserialize(using = Log4jStackTraceElementDeserializer.class) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SOURCE) - @Override - public abstract StackTraceElement getSource(); - - @Override - @JsonProperty("threadId") - @JacksonXmlProperty(isAttribute = true, localName = "threadId") - public abstract long getThreadId(); - - @Override - @JsonProperty("thread") - @JacksonXmlProperty(isAttribute = true, localName = "thread") - public abstract String getThreadName(); - - @Override - @JsonProperty("threadPriority") - @JacksonXmlProperty(isAttribute = true, localName = "threadPriority") - public abstract int getThreadPriority(); - - @JsonIgnore - @Override - public abstract Throwable getThrown(); - - @JsonProperty(JsonConstants.ELT_THROWN) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_THROWN) - @Override - public abstract ThrowableProxy getThrownProxy(); - - @JsonIgnore // ignore from 2.11 -// @JsonProperty() -// @JacksonXmlProperty(isAttribute = true) - @Override - public abstract long getTimeMillis(); - - @JsonProperty(JsonConstants.ELT_INSTANT) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_INSTANT) - @Override - public abstract Instant getInstant(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract boolean isEndOfBatch(); - - @JsonIgnore - @Override - public abstract boolean isIncludeLocation(); - - @Override - public abstract void setEndOfBatch(boolean endOfBatch); - - @Override - public abstract void setIncludeLocation(boolean locationRequired); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventWithContextListMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventWithContextListMixIn.java deleted file mode 100644 index 12b61fa7962..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/LogEventWithContextListMixIn.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import java.util.Map; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.ThreadContext.ContextStack; -import org.apache.logging.log4j.core.time.Instant; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.apache.logging.log4j.message.Message; - -import com.fasterxml.jackson.annotation.JsonFilter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; - -@JsonRootName(XmlConstants.ELT_EVENT) -@JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT) -@JsonFilter("org.apache.logging.log4j.core.impl.Log4jLogEvent") -@JsonPropertyOrder({ "timeMillis", XmlConstants.ELT_INSTANT, "threadName", "level", "loggerName", "marker", "message", "thrown", XmlConstants.ELT_CONTEXT_MAP, - JsonConstants.ELT_CONTEXT_STACK, "loggerFQCN", "Source", "endOfBatch" }) -abstract class LogEventWithContextListMixIn implements LogEvent { - - private static final long serialVersionUID = 1L; - -// @JsonProperty(JsonConstants.ELT_CONTEXT_MAP) -// @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_MAP) -// @JsonSerialize(using = ListOfMapEntrySerializer.class) -// @JsonDeserialize(using = ListOfMapEntryDeserializer.class) - @Override - @JsonIgnore - public abstract Map getContextMap(); - - @JsonProperty(JsonConstants.ELT_CONTEXT_MAP) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_MAP) - @JsonSerialize(using = ContextDataAsEntryListSerializer.class) - @JsonDeserialize(using = ContextDataAsEntryListDeserializer.class) -// @JsonIgnore - @Override - public abstract ReadOnlyStringMap getContextData(); - - @JsonProperty(JsonConstants.ELT_CONTEXT_STACK) - @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_STACK) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_STACK_ITEM) - @Override - public abstract ContextStack getContextStack(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract Level getLevel(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract String getLoggerFqcn(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract String getLoggerName(); - - @JsonProperty(JsonConstants.ELT_MARKER) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_MARKER) - @Override - public abstract Marker getMarker(); - - @JsonProperty(JsonConstants.ELT_MESSAGE) - @JsonSerialize(using = MessageSerializer.class) - @JsonDeserialize(using = SimpleMessageDeserializer.class) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_MESSAGE) - @Override - public abstract Message getMessage(); - - @JsonProperty(JsonConstants.ELT_SOURCE) - @JsonDeserialize(using = Log4jStackTraceElementDeserializer.class) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SOURCE) - @Override - public abstract StackTraceElement getSource(); - - @Override - @JsonProperty("threadId") - @JacksonXmlProperty(isAttribute = true, localName = "threadId") - public abstract long getThreadId(); - - @Override - @JsonProperty("thread") - @JacksonXmlProperty(isAttribute = true, localName = "thread") - public abstract String getThreadName(); - - @Override - @JsonProperty("threadPriority") - @JacksonXmlProperty(isAttribute = true, localName = "threadPriority") - public abstract int getThreadPriority(); - - @JsonIgnore - @Override - public abstract Throwable getThrown(); - - @JsonProperty(JsonConstants.ELT_THROWN) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_THROWN) - @Override - public abstract ThrowableProxy getThrownProxy(); - - @JsonIgnore // ignore from 2.11 -// @JsonProperty() -// @JacksonXmlProperty(isAttribute = true) - @Override - public abstract long getTimeMillis(); - - @JsonProperty(JsonConstants.ELT_INSTANT) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_INSTANT) - @Override - public abstract Instant getInstant(); - - @JsonProperty() - @JacksonXmlProperty(isAttribute = true) - @Override - public abstract boolean isEndOfBatch(); - - @JsonIgnore - @Override - public abstract boolean isIncludeLocation(); - - @Override - public abstract void setEndOfBatch(boolean endOfBatch); - - @Override - public abstract void setIncludeLocation(boolean locationRequired); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixIn.java deleted file mode 100644 index 9a3b0223f80..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixIn.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; - -/** - * Jackson mix-in for {@link StackTraceElement}. - *

    - * Consider this class private. - *

    - * - * @see StackTraceElement - */ -@JsonIgnoreProperties("nativeMethod") -abstract class StackTraceElementMixIn { - @JsonCreator - StackTraceElementMixIn( - // @formatter:off - @JsonProperty("class") final String declaringClass, - @JsonProperty("method") final String methodName, - @JsonProperty("file") final String fileName, - @JsonProperty("line") final int lineNumber) - // @formatter:on - { - // empty - } - - @JsonProperty("class") - @JacksonXmlProperty(localName = "class", isAttribute = true) - abstract String getClassName(); - - @JsonProperty("file") - @JacksonXmlProperty(localName = "file", isAttribute = true) - abstract String getFileName(); - - @JsonProperty("line") - @JacksonXmlProperty(localName = "line", isAttribute = true) - abstract int getLineNumber(); - - @JsonProperty("method") - @JacksonXmlProperty(localName = "method", isAttribute = true) - abstract String getMethodName(); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyMixIn.java deleted file mode 100644 index a36c1b41858..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyMixIn.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; -import org.apache.logging.log4j.core.impl.ThrowableProxy; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; - -/** - * Mix-in for {@link ThrowableProxy}. - */ -abstract class ThrowableProxyMixIn { - - @JsonProperty(JsonConstants.ELT_CAUSE) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CAUSE) - private ThrowableProxyMixIn causeProxy; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private int commonElementCount; - - @JsonProperty(JsonConstants.ELT_EXTENDED_STACK_TRACE) - @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EXTENDED_STACK_TRACE) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EXTENDED_STACK_TRACE_ITEM) - private ExtendedStackTraceElement[] extendedStackTrace; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String localizedMessage; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String message; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String name; - - @JsonIgnore - private transient Throwable throwable; - - @JsonIgnore - public abstract String getCauseStackTraceAsString(); - - @JsonIgnore - public abstract String getExtendedStackTraceAsString(); - - @JsonIgnore - public abstract StackTraceElement[] getStackTrace(); - - @JsonProperty(JsonConstants.ELT_SUPPRESSED) - @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED_ITEM) - public abstract ThrowableProxy[] getSuppressedProxies(); - - @JsonIgnore - public abstract String getSuppressedStackTrace(); - - @JsonIgnore - public abstract Throwable getThrowable(); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithStacktraceAsStringMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithStacktraceAsStringMixIn.java deleted file mode 100644 index f169d35082d..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithStacktraceAsStringMixIn.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; -import org.apache.logging.log4j.core.impl.ThrowableProxy; - -/** - * Mix-in for {@link org.apache.logging.log4j.core.impl.ThrowableProxy}. - */ -abstract class ThrowableProxyWithStacktraceAsStringMixIn { - - @JsonProperty(JsonConstants.ELT_CAUSE) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CAUSE) - private ThrowableProxyWithStacktraceAsStringMixIn causeProxy; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private int commonElementCount; - - @JsonIgnore - private ExtendedStackTraceElement[] extendedStackTrace; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String localizedMessage; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String message; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String name; - - @JsonIgnore - private transient Throwable throwable; - - @JsonIgnore - public abstract String getCauseStackTraceAsString(); - - @JsonProperty(JsonConstants.ELT_EXTENDED_STACK_TRACE) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EXTENDED_STACK_TRACE) - public abstract String getExtendedStackTraceAsString(); - - @JsonIgnore - public abstract StackTraceElement[] getStackTrace(); - - @JsonProperty(JsonConstants.ELT_SUPPRESSED) - @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED_ITEM) - public abstract ThrowableProxy[] getSuppressedProxies(); - - @JsonIgnore - public abstract String getSuppressedStackTrace(); - - @JsonIgnore - public abstract Throwable getThrowable(); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithoutStacktraceMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithoutStacktraceMixIn.java deleted file mode 100644 index 264bef8bbd2..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithoutStacktraceMixIn.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.jackson; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; -import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; -import org.apache.logging.log4j.core.impl.ThrowableProxy; - -/** - * Mix-in for {@link ThrowableProxy}. - */ -abstract class ThrowableProxyWithoutStacktraceMixIn { - - @JsonProperty(JsonConstants.ELT_CAUSE) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CAUSE) - private ThrowableProxyWithoutStacktraceMixIn causeProxy; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private int commonElementCount; - - @JsonIgnore - private ExtendedStackTraceElement[] extendedStackTrace; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String localizedMessage; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String message; - - @JsonProperty - @JacksonXmlProperty(isAttribute = true) - private String name; - - @JsonIgnore - private transient Throwable throwable; - - @JsonIgnore - public abstract String getCauseStackTraceAsString(); - - @JsonIgnore - public abstract String getExtendedStackTraceAsString(); - - @JsonIgnore - public abstract StackTraceElement[] getStackTrace(); - - @JsonProperty(JsonConstants.ELT_SUPPRESSED) - @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED) - @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED_ITEM) - public abstract ThrowableProxy[] getSuppressedProxies(); - - @JsonIgnore - public abstract String getSuppressedStackTrace(); - - @JsonIgnore - public abstract Throwable getThrowable(); - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/package-info.java deleted file mode 100644 index 585da42b8a1..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -/** - * Classes and interfaces for serializing and deserializing Log4j 2 log events to XML and JSON using the Jackson - * library. - */ -package org.apache.logging.log4j.core.jackson; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdmin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdmin.java index 5a173ead364..caaa4330395 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdmin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdmin.java @@ -91,7 +91,7 @@ public String getFilter() { public String[] getAppenderRefs() { return asyncAppender.getAppenderRefStrings(); } - + /** * Returns {@code true} if this AsyncAppender will take a snapshot of the stack with * every log event to determine the class and method where the logging call @@ -102,7 +102,7 @@ public String[] getAppenderRefs() { public boolean isIncludeLocation() { return asyncAppender.isIncludeLocation(); } - + /** * Returns {@code true} if this AsyncAppender will block when the queue is full, * or {@code false} if events are dropped when the queue is full. @@ -112,7 +112,7 @@ public boolean isIncludeLocation() { public boolean isBlocking() { return asyncAppender.isBlocking(); } - + /** * Returns the name of the appender that any errors are logged to or {@code null}. * @return the name of the appender that any errors are logged to or {@code null} @@ -121,12 +121,12 @@ public boolean isBlocking() { public String getErrorRef() { return asyncAppender.getErrorRef(); } - + @Override public int getQueueCapacity() { return asyncAppender.getQueueCapacity(); } - + @Override public int getQueueRemainingCapacity() { return asyncAppender.getQueueRemainingCapacity(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdminMBean.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdminMBean.java index 37dad2e11b9..7348145a180 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdminMBean.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/AsyncAppenderAdminMBean.java @@ -27,7 +27,7 @@ public interface AsyncAppenderAdminMBean { *

    * You can find all registered AsyncAppenderAdmin MBeans like this: *

    - * + * *
          * MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
          * String pattern = String.format(AsyncAppenderAdminMBean.PATTERN, "*", "*");
    @@ -38,21 +38,21 @@ public interface AsyncAppenderAdminMBean {
          * and appender name may be quoted. When AsyncAppenderAdmin MBeans are
          * registered, their ObjectNames are created using this pattern as follows:
          * 

    - * + * *
          * String ctxName = Server.escape(loggerContext.getName());
          * String appenderName = Server.escape(appender.getName());
          * String name = String.format(PATTERN, ctxName, appenderName);
          * ObjectName objectName = new ObjectName(name);
          * 
    - * + * * @see Server#escape(String) */ String PATTERN = Server.DOMAIN + ":type=%s,component=AsyncAppenders,name=%s"; /** * Returns the name of the instrumented {@code AsyncAppender}. - * + * * @return the name of the AsyncAppender */ String getName(); @@ -60,7 +60,7 @@ public interface AsyncAppenderAdminMBean { /** * Returns the result of calling {@code toString} on the {@code Layout} * object of the instrumented {@code AsyncAppender}. - * + * * @return the {@code Layout} of the instrumented {@code AsyncAppender} as a * string */ @@ -69,7 +69,7 @@ public interface AsyncAppenderAdminMBean { /** * Returns how exceptions thrown on the instrumented {@code AsyncAppender} * are handled. - * + * * @return {@code true} if any exceptions thrown by the AsyncAppender will * be logged or {@code false} if such exceptions are re-thrown. */ @@ -78,7 +78,7 @@ public interface AsyncAppenderAdminMBean { /** * Returns the result of calling {@code toString} on the error handler of * this appender, or {@code "null"} if no error handler was set. - * + * * @return result of calling {@code toString} on the error handler of this * appender, or {@code "null"} */ @@ -87,7 +87,7 @@ public interface AsyncAppenderAdminMBean { /** * Returns a string description of all filters configured for the * instrumented {@code AsyncAppender}. - * + * * @return a string description of all configured filters for this appender */ String getFilter(); @@ -95,7 +95,7 @@ public interface AsyncAppenderAdminMBean { /** * Returns a String array with the appender refs configured for the * instrumented {@code AsyncAppender}. - * + * * @return the appender refs for the instrumented {@code AsyncAppender}. */ String[] getAppenderRefs(); @@ -104,7 +104,7 @@ public interface AsyncAppenderAdminMBean { * Returns {@code true} if this AsyncAppender will take a snapshot of the * stack with every log event to determine the class and method where the * logging call was made. - * + * * @return {@code true} if location is included with every event, * {@code false} otherwise */ @@ -113,19 +113,19 @@ public interface AsyncAppenderAdminMBean { /** * Returns {@code true} if this AsyncAppender will block when the queue is * full, or {@code false} if events are dropped when the queue is full. - * + * * @return whether this AsyncAppender will block or drop events when the * queue is full. */ boolean isBlocking(); - + /** * Returns the name of the appender that any errors are logged to or {@code null}. * @return the name of the appender that any errors are logged to or {@code null} */ String getErrorRef(); - + int getQueueCapacity(); - + int getQueueRemainingCapacity(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdmin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdmin.java index fb41b04cb72..53b6079a0f9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdmin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/ContextSelectorAdmin.java @@ -32,7 +32,7 @@ public class ContextSelectorAdmin implements ContextSelectorAdminMBean { /** * Constructs a new {@code ContextSelectorAdmin}. - * + * * @param contextName name of the LoggerContext under which to register this * ContextSelectorAdmin. Note that the ContextSelector may be * registered multiple times, once for each LoggerContext. In web @@ -55,7 +55,7 @@ public ContextSelectorAdmin(final String contextName, final ContextSelector sele /** * Returns the {@code ObjectName} of this mbean. - * + * * @return the {@code ObjectName} * @see ContextSelectorAdminMBean#PATTERN */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdmin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdmin.java index a2a9288b787..794a72c0abc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdmin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdmin.java @@ -16,6 +16,18 @@ */ package org.apache.logging.log4j.core.jmx; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.util.Closer; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; + +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.management.NotificationBroadcasterSupport; +import javax.management.ObjectName; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.ByteArrayInputStream; @@ -36,19 +48,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; -import javax.management.MBeanNotificationInfo; -import javax.management.Notification; -import javax.management.NotificationBroadcasterSupport; -import javax.management.ObjectName; - -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.util.Closer; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Strings; - /** * Implementation of the {@code LoggerContextAdminMBean} interface. */ @@ -132,7 +131,8 @@ public void setConfigLocationUri(final String configLocation) throws URISyntaxEx LOGGER.debug("Opening config URL {}", configURL); configSource = new ConfigurationSource(configURL.openStream(), configURL); } - final Configuration config = ConfigurationFactory.getInstance().getConfiguration(loggerContext, configSource); + final Configuration config = + loggerContext.getInjector().getInstance(ConfigurationFactory.KEY).getConfiguration(loggerContext, configSource); loggerContext.start(config); LOGGER.debug("Completed remote request to reconfigure."); } @@ -197,7 +197,8 @@ public void setConfigText(final String configText, final String charsetName) { try { final InputStream in = new ByteArrayInputStream(configText.getBytes(charsetName)); final ConfigurationSource source = new ConfigurationSource(in); - final Configuration updated = ConfigurationFactory.getInstance().getConfiguration(loggerContext, source); + final Configuration updated = + loggerContext.getInjector().getInstance(ConfigurationFactory.KEY).getConfiguration(loggerContext, source); loggerContext.start(updated); LOGGER.debug("Completed remote request to reconfigure from config text."); } catch (final Exception ex) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdminMBean.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdminMBean.java index 7526982c162..6cb65d6489c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdminMBean.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/LoggerContextAdminMBean.java @@ -32,7 +32,7 @@ public interface LoggerContextAdminMBean { *

    * You can find all registered LoggerContextAdmin MBeans like this: *

    - * + * *
          * MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
          * String pattern = String.format(LoggerContextAdminMBean.PATTERN, "*");
    @@ -43,13 +43,13 @@ public interface LoggerContextAdminMBean {
          * may be quoted. When LoggerContextAdmin MBeans are registered, their
          * ObjectNames are created using this pattern as follows:
          * 

    - * + * *
          * String ctxName = Server.escape(loggerContext.getName());
          * String name = String.format(PATTERN, ctxName);
          * ObjectName objectName = new ObjectName(name);
          * 
    - * + * * @see Server#escape(String) */ String PATTERN = Server.DOMAIN + ":type=%s"; @@ -69,21 +69,21 @@ public interface LoggerContextAdminMBean { /** * Returns the status of the instrumented {@code LoggerContext}. - * + * * @return the LoggerContext status. */ String getStatus(); /** * Returns the name of the instrumented {@code LoggerContext}. - * + * * @return the name of the instrumented {@code LoggerContext}. */ String getName(); /** * Returns the configuration location URI as a String. - * + * * @return the configuration location */ String getConfigLocationUri(); @@ -91,7 +91,7 @@ public interface LoggerContextAdminMBean { /** * Sets the configuration location to the specified URI. This will cause the * instrumented {@code LoggerContext} to reconfigure. - * + * * @param configLocation location of the configuration file in * {@link java.net.URI} format. * @throws URISyntaxException if the format of the specified @@ -105,7 +105,7 @@ public interface LoggerContextAdminMBean { * configuration file or the text that was last set with a call to * {@code setConfigText}. If reading a file, this method assumes the file's * character encoding is UTF-8. - * + * * @return the configuration text * @throws IOException if a problem occurred reading the contents of the * config file. @@ -116,7 +116,7 @@ public interface LoggerContextAdminMBean { * Returns the configuration text, which may be the contents of the * configuration file or the text that was last set with a call to * {@code setConfigText}. - * + * * @param charsetName the encoding to use to convert the file's bytes into * the resulting string. * @return the configuration text @@ -129,7 +129,7 @@ public interface LoggerContextAdminMBean { * Sets the configuration text. This does not replace the contents of the * configuration file, but does cause the instrumented * {@code LoggerContext} to be reconfigured with the specified text. - * + * * @param configText the configuration text in XML or JSON format * @param charsetName name of the {@code Charset} used to convert the * specified configText to bytes @@ -140,7 +140,7 @@ public interface LoggerContextAdminMBean { /** * Returns the name of the Configuration of the instrumented LoggerContext. - * + * * @return the Configuration name */ String getConfigName(); @@ -148,7 +148,7 @@ public interface LoggerContextAdminMBean { /** * Returns the class name of the {@code Configuration} of the instrumented * LoggerContext. - * + * * @return the class name of the {@code Configuration}. */ String getConfigClassName(); @@ -156,14 +156,14 @@ public interface LoggerContextAdminMBean { /** * Returns a string description of all Filters configured in the * {@code Configuration} of the instrumented LoggerContext. - * + * * @return a string description of all Filters configured */ String getConfigFilter(); /** * Returns a map with configured properties. - * + * * @return a map with configured properties. */ Map getConfigProperties(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdmin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdmin.java index 15a9e564ddb..b40d2f4bb33 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdmin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdmin.java @@ -34,28 +34,28 @@ public static RingBufferAdmin forAsyncLogger(final RingBuffer ringBuffer, fin return new RingBufferAdmin(ringBuffer, name); } - public static RingBufferAdmin forAsyncLoggerConfig(final RingBuffer ringBuffer, + public static RingBufferAdmin forAsyncLoggerConfig(final RingBuffer ringBuffer, final String contextName, final String configName) { final String ctxName = Server.escape(contextName); final String cfgName = Server.escape(configName); final String name = String.format(PATTERN_ASYNC_LOGGER_CONFIG, ctxName, cfgName); return new RingBufferAdmin(ringBuffer, name); } - + protected RingBufferAdmin(final RingBuffer ringBuffer, final String mbeanName) { - this.ringBuffer = ringBuffer; + this.ringBuffer = ringBuffer; try { objectName = new ObjectName(mbeanName); } catch (final Exception e) { throw new IllegalStateException(e); } } - + @Override public long getBufferSize() { return ringBuffer == null ? 0 : ringBuffer.getBufferSize(); } - + @Override public long getRemainingCapacity() { return ringBuffer == null ? 0 : ringBuffer.remainingCapacity(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdminMBean.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdminMBean.java index 052dcc8c027..83a8278d6d1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdminMBean.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/RingBufferAdminMBean.java @@ -35,7 +35,7 @@ public interface RingBufferAdminMBean { *
    */ String PATTERN_ASYNC_LOGGER = Server.DOMAIN + ":type=%s,component=AsyncLoggerRingBuffer"; - + /** * ObjectName pattern ({@value}) for RingBufferAdmin MBeans that instrument * {@code AsyncLoggerConfig} ring buffers. @@ -56,7 +56,7 @@ public interface RingBufferAdminMBean { * Returns the number of slots that the ring buffer was configured with. * Disruptor ring buffers are bounded-size data structures, this number does * not change during the life of the ring buffer. - * + * * @return the number of slots that the ring buffer was configured with */ long getBufferSize(); @@ -64,7 +64,7 @@ public interface RingBufferAdminMBean { /** * Returns the number of available slots in the ring buffer. May vary wildly * between invocations. - * + * * @return the number of available slots in the ring buffer */ long getRemainingCapacity(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java index 09bcd48f8ce..5b782a921ed 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/Server.java @@ -23,7 +23,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; - import javax.management.InstanceAlreadyExistsException; import javax.management.InstanceNotFoundException; import javax.management.MBeanRegistrationException; @@ -39,11 +38,12 @@ import org.apache.logging.log4j.core.async.AsyncLoggerContext; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.core.impl.Log4jProperties; import org.apache.logging.log4j.core.selector.ContextSelector; -import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.util.Log4jThreadFactory; import org.apache.logging.log4j.spi.LoggerContextFactory; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Constants; import org.apache.logging.log4j.util.PropertiesUtil; /** @@ -54,12 +54,11 @@ */ public final class Server { + private static final String CONTEXT_NAME_ALL = "*"; /** * The domain part, or prefix ({@value}) of the {@code ObjectName} of all MBeans that instrument Log4J2 components. */ public static final String DOMAIN = "org.apache.logging.log4j2"; - private static final String PROPERTY_DISABLE_JMX = "log4j2.disable.jmx"; - private static final String PROPERTY_ASYNC_NOTIF = "log4j2.jmx.notify.async"; private static final String THREAD_NAME_PREFIX = "jmx.notif"; private static final StatusLogger LOGGER = StatusLogger.getLogger(); static final Executor executor = isJmxDisabled() ? null : createExecutor(); @@ -75,8 +74,8 @@ private Server() { * @see
    LOG4J2-938 */ private static ExecutorService createExecutor() { - final boolean defaultAsync = !Constants.IS_WEB_APP; - final boolean async = PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_ASYNC_NOTIF, defaultAsync); + final boolean defaultAsync = !Constants.isWebApp(); + final boolean async = PropertiesUtil.getProperties().getBooleanProperty(Log4jProperties.JMX_NOTIFY_ASYNC, defaultAsync); return async ? Executors.newFixedThreadPool(1, Log4jThreadFactory.createDaemonThreadFactory(THREAD_NAME_PREFIX)) : null; } @@ -127,7 +126,7 @@ public static String escape(final String name) { } private static boolean isJmxDisabled() { - return PropertiesUtil.getProperties().getBooleanProperty(PROPERTY_DISABLE_JMX); + return PropertiesUtil.getProperties().getBooleanProperty(Log4jProperties.JMX_DISABLED); } public static void reregisterMBeansAfterReconfigure() { @@ -136,8 +135,7 @@ public static void reregisterMBeansAfterReconfigure() { LOGGER.debug("JMX disabled for Log4j2. Not registering MBeans."); return; } - final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); - reregisterMBeansAfterReconfigure(mbs); + reregisterMBeansAfterReconfigure(ManagementFactory.getPlatformMBeanServer()); } public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) { @@ -160,7 +158,7 @@ public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) { for (final LoggerContext ctx : contexts) { LOGGER.trace("Reregistering context ({}/{}): '{}' {}", ++i, contexts.size(), ctx.getName(), ctx); // first unregister the context and all nested loggers, - // appenders, statusLogger, contextSelector, ringbuffers... + // appenders, statusLogger, contextSelector, ring buffers... unregisterLoggerContext(ctx.getName(), mbs); final LoggerContextAdmin mbean = new LoggerContextAdmin(ctx, executor); @@ -169,8 +167,8 @@ public static void reregisterMBeansAfterReconfigure(final MBeanServer mbs) { if (ctx instanceof AsyncLoggerContext) { final RingBufferAdmin rbmbean = ((AsyncLoggerContext) ctx).createRingBufferAdmin(); if (rbmbean.getBufferSize() > 0) { - // don't register if Disruptor not started (DefaultConfiguration: config not found) - register(mbs, rbmbean, rbmbean.getObjectName()); + // don't register if Disruptor not started (DefaultConfiguration: config not found) + register(mbs, rbmbean, rbmbean.getObjectName()); } } @@ -198,8 +196,7 @@ public static void unregisterMBeans() { LOGGER.debug("JMX disabled for Log4j2. Not unregistering MBeans."); return; } - final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); - unregisterMBeans(mbs); + unregisterMBeans(ManagementFactory.getPlatformMBeanServer()); } /** @@ -208,14 +205,16 @@ public static void unregisterMBeans() { * @param mbs the MBean server to unregister from. */ public static void unregisterMBeans(final MBeanServer mbs) { - unregisterStatusLogger("*", mbs); - unregisterContextSelector("*", mbs); - unregisterContexts(mbs); - unregisterLoggerConfigs("*", mbs); - unregisterAsyncLoggerRingBufferAdmins("*", mbs); - unregisterAsyncLoggerConfigRingBufferAdmins("*", mbs); - unregisterAppenders("*", mbs); - unregisterAsyncAppenders("*", mbs); + if (mbs != null) { + unregisterStatusLogger(CONTEXT_NAME_ALL, mbs); + unregisterContextSelector(CONTEXT_NAME_ALL, mbs); + unregisterContexts(mbs); + unregisterLoggerConfigs(CONTEXT_NAME_ALL, mbs); + unregisterAsyncLoggerRingBufferAdmins(CONTEXT_NAME_ALL, mbs); + unregisterAsyncLoggerConfigRingBufferAdmins(CONTEXT_NAME_ALL, mbs); + unregisterAppenders(CONTEXT_NAME_ALL, mbs); + unregisterAsyncAppenders(CONTEXT_NAME_ALL, mbs); + } } /** @@ -226,8 +225,7 @@ public static void unregisterMBeans(final MBeanServer mbs) { private static ContextSelector getContextSelector() { final LoggerContextFactory factory = LogManager.getFactory(); if (factory instanceof Log4jContextFactory) { - final ContextSelector selector = ((Log4jContextFactory) factory).getSelector(); - return selector; + return ((Log4jContextFactory) factory).getSelector(); } return null; } @@ -255,7 +253,7 @@ public static void unregisterLoggerContext(final String loggerContextName) { * @param mbs the MBean Server to unregister the instrumented objects from */ public static void unregisterLoggerContext(final String contextName, final MBeanServer mbs) { - final String search = String.format(LoggerContextAdminMBean.PATTERN, escape(contextName), "*"); + final String search = String.format(LoggerContextAdminMBean.PATTERN, escape(contextName)); unregisterAllMatching(search, mbs); // unregister context mbean // now unregister all MBeans associated with this logger context @@ -284,12 +282,12 @@ private static void registerContextSelector(final String contextName, final Cont } private static void unregisterStatusLogger(final String contextName, final MBeanServer mbs) { - final String search = String.format(StatusLoggerAdminMBean.PATTERN, escape(contextName), "*"); + final String search = String.format(StatusLoggerAdminMBean.PATTERN, escape(contextName)); unregisterAllMatching(search, mbs); } private static void unregisterContextSelector(final String contextName, final MBeanServer mbs) { - final String search = String.format(ContextSelectorAdminMBean.PATTERN, escape(contextName), "*"); + final String search = String.format(ContextSelectorAdminMBean.PATTERN, escape(contextName)); unregisterAllMatching(search, mbs); } @@ -333,13 +331,15 @@ private static void unregisterAllMatching(final String search, final MBeanServer try { final ObjectName pattern = new ObjectName(search); final Set found = mbs.queryNames(pattern, null); - if (found.isEmpty()) { - LOGGER.trace("Unregistering but no MBeans found matching '{}'", search); + if (found == null || found.isEmpty()) { + LOGGER.trace("Unregistering but no MBeans found matching '{}'", search); } else { - LOGGER.trace("Unregistering {} MBeans: {}", found.size(), found); + LOGGER.trace("Unregistering {} MBeans: {}", found.size(), found); } - for (final ObjectName objectName : found) { - mbs.unregisterMBean(objectName); + if (found != null) { + for (final ObjectName objectName : found) { + mbs.unregisterMBean(objectName); + } } } catch (final InstanceNotFoundException ex) { LOGGER.debug("Could not unregister MBeans for " + search + ". Ignoring " + ex); @@ -385,7 +385,15 @@ private static void registerAppenders(final LoggerContext ctx, final MBeanServer private static void register(final MBeanServer mbs, final Object mbean, final ObjectName objectName) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { + if (mbs.isRegistered(objectName)) { + try { + mbs.unregisterMBean(objectName); + } catch (MBeanRegistrationException | InstanceNotFoundException ex) { + LOGGER.trace("Failed to unregister MBean {}", objectName); + } + } LOGGER.debug("Registering MBean {}", objectName); mbs.registerMBean(mbean, objectName); } + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java index a602f4ebd80..dbd7a6b2f12 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdmin.java @@ -44,7 +44,7 @@ public class StatusLoggerAdmin extends NotificationBroadcasterSupport implements /** * Constructs a new {@code StatusLoggerAdmin} with the {@code Executor} to * be used for sending {@code Notification}s asynchronously to listeners. - * + * * @param contextName name of the LoggerContext under which to register this * StatusLoggerAdmin. Note that the StatusLogger may be * registered multiple times, once for each LoggerContext. In web @@ -132,7 +132,7 @@ public String getContextName() { /* * (non-Javadoc) - * + * * @see * org.apache.logging.log4j.status.StatusListener#log(org.apache.logging * .log4j.status.StatusData) @@ -150,7 +150,7 @@ public void log(final StatusData data) { /** * Returns the {@code ObjectName} of this mbean. - * + * * @return the {@code ObjectName} * @see StatusLoggerAdminMBean#PATTERN */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdminMBean.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdminMBean.java index 91a6063f490..e6510e6ddd1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdminMBean.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jmx/StatusLoggerAdminMBean.java @@ -68,7 +68,7 @@ public interface StatusLoggerAdminMBean { * @return the ObjectName of this StatusLogger MBean */ ObjectName getObjectName(); - + /** * Returns a list with the most recent {@code StatusData} objects in the * status history. The list has up to 200 entries by default but the length diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java deleted file mode 100644 index 7a04100d69f..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.impl.MutableLogEvent; -import org.apache.logging.log4j.core.jackson.XmlConstants; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.core.util.StringBuilderWriter; -import org.apache.logging.log4j.util.Strings; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; - -abstract class AbstractJacksonLayout extends AbstractStringLayout { - - protected static final String DEFAULT_EOL = "\r\n"; - protected static final String COMPACT_EOL = Strings.EMPTY; - - public static abstract class Builder> extends AbstractStringLayout.Builder { - - @PluginBuilderAttribute - private boolean eventEol; - - @PluginBuilderAttribute - private boolean compact; - - @PluginBuilderAttribute - private boolean complete; - - @PluginBuilderAttribute - private boolean locationInfo; - - @PluginBuilderAttribute - private boolean properties; - - @PluginBuilderAttribute - private boolean includeStacktrace = true; - - @PluginBuilderAttribute - private boolean stacktraceAsString = false; - - @PluginBuilderAttribute - private boolean includeNullDelimiter = false; - - @PluginElement("AdditionalField") - private KeyValuePair[] additionalFields; - - protected String toStringOrNull(final byte[] header) { - return header == null ? null : new String(header, Charset.defaultCharset()); - } - - public boolean getEventEol() { - return eventEol; - } - - public boolean isCompact() { - return compact; - } - - public boolean isComplete() { - return complete; - } - - public boolean isLocationInfo() { - return locationInfo; - } - - public boolean isProperties() { - return properties; - } - - /** - * If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true". - * @return If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true". - */ - public boolean isIncludeStacktrace() { - return includeStacktrace; - } - - public boolean isStacktraceAsString() { - return stacktraceAsString; - } - - public boolean isIncludeNullDelimiter() { return includeNullDelimiter; } - - public KeyValuePair[] getAdditionalFields() { - return additionalFields; - } - - public B setEventEol(final boolean eventEol) { - this.eventEol = eventEol; - return asBuilder(); - } - - public B setCompact(final boolean compact) { - this.compact = compact; - return asBuilder(); - } - - public B setComplete(final boolean complete) { - this.complete = complete; - return asBuilder(); - } - - public B setLocationInfo(final boolean locationInfo) { - this.locationInfo = locationInfo; - return asBuilder(); - } - - public B setProperties(final boolean properties) { - this.properties = properties; - return asBuilder(); - } - - /** - * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". - * @param includeStacktrace If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". - * @return this builder - */ - public B setIncludeStacktrace(final boolean includeStacktrace) { - this.includeStacktrace = includeStacktrace; - return asBuilder(); - } - - /** - * Whether to format the stacktrace as a string, and not a nested object (optional, defaults to false). - * - * @return this builder - */ - public B setStacktraceAsString(final boolean stacktraceAsString) { - this.stacktraceAsString = stacktraceAsString; - return asBuilder(); - } - - /** - * Whether to include NULL byte as delimiter after each event (optional, default to false). - * - * @return this builder - */ - public B setIncludeNullDelimiter(final boolean includeNullDelimiter) { - this.includeNullDelimiter = includeNullDelimiter; - return asBuilder(); - } - - /** - * Additional fields to set on each log event. - * - * @return this builder - */ - public B setAdditionalFields(KeyValuePair[] additionalFields) { - this.additionalFields = additionalFields; - return asBuilder(); - } - } - - protected final String eol; - protected final ObjectWriter objectWriter; - protected final boolean compact; - protected final boolean complete; - protected final boolean includeNullDelimiter; - protected final ResolvableKeyValuePair[] additionalFields; - - @Deprecated - protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer, - final Serializer footerSerializer) { - this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false); - } - - @Deprecated - protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer, - final Serializer footerSerializer, final boolean includeNullDelimiter) { - this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, includeNullDelimiter, null); - } - - protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer, - final Serializer footerSerializer, final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields) { - super(config, charset, headerSerializer, footerSerializer); - this.objectWriter = objectWriter; - this.compact = compact; - this.complete = complete; - this.eol = compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL; - this.includeNullDelimiter = includeNullDelimiter; - this.additionalFields = prepareAdditionalFields(config, additionalFields); - } - - protected static boolean valueNeedsLookup(final String value) { - return value != null && value.contains("${"); - } - - private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config, final KeyValuePair[] additionalFields) { - if (additionalFields == null || additionalFields.length == 0) { - // No fields set - return new ResolvableKeyValuePair[0]; - } - - // Convert to specific class which already determines whether values needs lookup during serialization - final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length]; - - for (int i = 0; i < additionalFields.length; i++) { - ResolvableKeyValuePair resolvable = resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]); - - // Validate - if (config == null && resolvable.valueNeedsLookup) { - throw new IllegalArgumentException("configuration needs to be set when there are additional fields with variables"); - } - } - - return resolvableFields; - } - - /** - * Formats a {@link org.apache.logging.log4j.core.LogEvent}. - * - * @param event The LogEvent. - * @return The XML representation of the LogEvent. - */ - @Override - public String toSerializable(final LogEvent event) { - final StringBuilderWriter writer = new StringBuilderWriter(); - try { - toSerializable(event, writer); - return writer.toString(); - } catch (final IOException e) { - // Should this be an ISE or IAE? - LOGGER.error(e); - return Strings.EMPTY; - } - } - - private static LogEvent convertMutableToLog4jEvent(final LogEvent event) { - // TODO Jackson-based layouts have certain filters set up for Log4jLogEvent. - // TODO Need to set up the same filters for MutableLogEvent but don't know how... - // This is a workaround. - return event instanceof MutableLogEvent - ? ((MutableLogEvent) event).createMemento() - : event; - } - - protected Object wrapLogEvent(final LogEvent event) { - if (additionalFields.length > 0) { - // Construct map for serialization - note that we are intentionally using original LogEvent - Map additionalFieldsMap = resolveAdditionalFields(event); - // This class combines LogEvent with AdditionalFields during serialization - return new LogEventWithAdditionalFields(event, additionalFieldsMap); - } else { - // No additional fields, return original object - return event; - } - } - - private Map resolveAdditionalFields(LogEvent logEvent) { - // Note: LinkedHashMap retains order - final Map additionalFieldsMap = new LinkedHashMap<>(additionalFields.length); - final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor(); - - // Go over each field - for (ResolvableKeyValuePair pair : additionalFields) { - if (pair.valueNeedsLookup) { - // Resolve value - additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value)); - } else { - // Plain text value - additionalFieldsMap.put(pair.key, pair.value); - } - } - - return additionalFieldsMap; - } - - public void toSerializable(final LogEvent event, final Writer writer) - throws JsonGenerationException, JsonMappingException, IOException { - objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event))); - writer.write(eol); - if (includeNullDelimiter) { - writer.write('\0'); - } - markEvent(); - } - - @JsonRootName(XmlConstants.ELT_EVENT) - @JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT) - public static class LogEventWithAdditionalFields { - - private final Object logEvent; - private final Map additionalFields; - - public LogEventWithAdditionalFields(Object logEvent, Map additionalFields) { - this.logEvent = logEvent; - this.additionalFields = additionalFields; - } - - @JsonUnwrapped - public Object getLogEvent() { - return logEvent; - } - - @JsonAnyGetter - @SuppressWarnings("unused") - public Map getAdditionalFields() { - return additionalFields; - } - } - - protected static class ResolvableKeyValuePair { - - final String key; - final String value; - final boolean valueNeedsLookup; - - ResolvableKeyValuePair(KeyValuePair pair) { - this.key = pair.getKey(); - this.value = pair.getValue(); - this.valueNeedsLookup = AbstractJacksonLayout.valueNeedsLookup(this.value); - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java index c8bb1bea180..490d5abd958 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java @@ -16,18 +16,18 @@ */ package org.apache.logging.log4j.core.layout; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; import org.apache.logging.log4j.status.StatusLogger; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + /** * Abstract base class for Layouts. * @@ -111,20 +111,6 @@ public B setHeader(final byte[] header) { */ protected final byte[] header; - /** - * Constructs a layout with an optional header and footer. - * - * @param header - * The header to include when the stream is opened. May be null. - * @param footer - * The footer to add when the stream is closed. May be null. - * @deprecated Use {@link #AbstractLayout(Configuration, byte[], byte[])} - */ - @Deprecated - public AbstractLayout(final byte[] header, final byte[] footer) { - this(null, header, footer); - } - /** * Constructs a layout with an optional header and footer. * @@ -185,7 +171,8 @@ protected void markEvent() { * Subclasses can override this method to provide a garbage-free implementation. For text-based layouts, * {@code AbstractStringLayout} provides various convenience methods to help with this: *

    - *
    @Plugin(name = "MyLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
    +     * 
    @Category(Node.CATEGORY)
    +     * @Plugin(value = "MyLayout", elementType = Layout.ELEMENT_TYPE, printObject = true)
          * public final class MyLayout extends AbstractStringLayout {
          *     @Override
          *     public void encode(LogEvent event, ByteBufferDestination destination) {
    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
    index 88e541cd9e6..78c6bef1b35 100644
    --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
    +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
    @@ -16,7 +16,6 @@
      */
     package org.apache.logging.log4j.core.layout;
     
    -import java.io.UnsupportedEncodingException;
     import java.nio.charset.Charset;
     import java.nio.charset.StandardCharsets;
     
    @@ -24,11 +23,13 @@
     import org.apache.logging.log4j.core.StringLayout;
     import org.apache.logging.log4j.core.config.Configuration;
     import org.apache.logging.log4j.core.config.LoggerConfig;
    -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
    -import org.apache.logging.log4j.core.config.plugins.PluginElement;
     import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
    +import org.apache.logging.log4j.core.impl.Log4jProperties;
     import org.apache.logging.log4j.core.util.Constants;
     import org.apache.logging.log4j.core.util.StringEncoder;
    +import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
    +import org.apache.logging.log4j.plugins.PluginElement;
    +import org.apache.logging.log4j.spi.AbstractLogger;
     import org.apache.logging.log4j.util.PropertiesUtil;
     import org.apache.logging.log4j.util.StringBuilders;
     import org.apache.logging.log4j.util.Strings;
    @@ -40,10 +41,6 @@
      * performance: all characters are simply cast to bytes.
      * 

    */ -/* - * Implementation note: prefer String.getBytes(String) to String.getBytes(Charset) for performance reasons. See - * https://issues.apache.org/jira/browse/LOG4J2-935 for details. - */ public abstract class AbstractStringLayout extends AbstractLayout implements StringLayout { public abstract static class Builder> extends AbstractLayout.Builder { @@ -86,15 +83,26 @@ public B setHeaderSerializer(final Serializer headerSerializer) { } - public interface Serializer { + public interface Serializer extends Serializer2 { String toSerializable(final LogEvent event); + + default boolean requiresLocation() { + return false; + } + + @Override + default StringBuilder toSerializable(final LogEvent event, final StringBuilder builder) { + builder.append(toSerializable(event)); + return builder; + } } /** * Variation of {@link Serializer} that avoids allocating temporary objects. + * As of 2.13 this interface was merged into the Serializer interface. * @since 2.6 */ - public interface Serializer2 { + public interface Serializer2 { StringBuilder toSerializable(final LogEvent event, final StringBuilder builder); } @@ -104,7 +112,7 @@ public interface Serializer2 { protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; protected static final int MAX_STRING_BUILDER_SIZE = Math.max(DEFAULT_STRING_BUILDER_SIZE, - size("log4j.layoutStringBuilder.maxSize", 2 * 1024)); + size(Log4jProperties.GC_LAYOUT_STRING_BUILDER_MAX_SIZE, 2 * 1024)); private static final ThreadLocal threadLocal = new ThreadLocal<>(); @@ -114,6 +122,10 @@ public interface Serializer2 { * @return a {@code StringBuilder} */ protected static StringBuilder getStringBuilder() { + if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-2368 + // Recursive logging may clobber the cached StringBuilder. + return new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); + } StringBuilder result = threadLocal.get(); if (result == null) { result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); @@ -124,18 +136,6 @@ protected static StringBuilder getStringBuilder() { return result; } - // LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them. - private static boolean isPreJava8() { - final String version = System.getProperty("java.version"); - final String[] parts = version.split("\\."); - try { - final int major = Integer.parseInt(parts[1]); - return major < 8; - } catch (final Exception ex) { - return true; - } - } - private static int size(final String property, final int defaultValue) { return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); } @@ -148,17 +148,12 @@ protected static void trimToMaxSize(final StringBuilder stringBuilder) { /** * The charset for the formatted message. */ - // LOG4J2-1099: Charset cannot be final due to serialization needs, so we serialize as Charset name instead - private transient Charset charset; - - private final String charsetName; + private final Charset charset; private final Serializer footerSerializer; private final Serializer headerSerializer; - private final boolean useCustomEncoding; - protected AbstractStringLayout(final Charset charset) { this(charset, (byte[]) null, (byte[]) null); } @@ -175,9 +170,6 @@ protected AbstractStringLayout(final Charset aCharset, final byte[] header, fina this.headerSerializer = null; this.footerSerializer = null; this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; - this.charsetName = this.charset.name(); - useCustomEncoding = isPreJava8() - && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; } @@ -195,21 +187,11 @@ protected AbstractStringLayout(final Configuration config, final Charset aCharse this.headerSerializer = headerSerializer; this.footerSerializer = footerSerializer; this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; - this.charsetName = this.charset.name(); - useCustomEncoding = isPreJava8() - && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; } protected byte[] getBytes(final String s) { - if (useCustomEncoding) { // rely on branch prediction to eliminate this check if false - return StringEncoder.encodeSingleByteChars(s); - } - try { // LOG4J2-935: String.getBytes(String) gives better performance - return s.getBytes(charsetName); - } catch (final UnsupportedEncodingException e) { - return s.getBytes(charset); - } + return s.getBytes(charset); } @Override @@ -254,7 +236,8 @@ public Serializer getHeaderSerializer() { } private DefaultLogEventFactory getLogEventFactory() { - return DefaultLogEventFactory.getInstance(); + // TODO: inject this + return DefaultLogEventFactory.newInstance(); } /** @@ -271,7 +254,7 @@ protected Encoder getStringBuilderEncoder() { protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { final String serializable = serializeToString(serializer); - if (serializer == null) { + if (serializable == null) { return defaultValue; } return StringEncoder.toBytes(serializable, getCharset()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java deleted file mode 100644 index fe079fb590f..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.io.IOException; -import java.nio.charset.Charset; - -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.QuoteMode; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * A Comma-Separated Value (CSV) layout to log event parameters. - * The event message is currently ignored. - * - *

    - * Best used with: - *

    - *

    - * {@code logger.debug(new ObjectArrayMessage(1, 2, "Bob"));} - *

    - * - * Depends on Apache Commons CSV 1.4. - * - * @since 2.4 - */ -@Plugin(name = "CsvParameterLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) -public class CsvParameterLayout extends AbstractCsvLayout { - - public static AbstractCsvLayout createDefaultLayout() { - return new CsvParameterLayout(null, Charset.forName(DEFAULT_CHARSET), CSVFormat.valueOf(DEFAULT_FORMAT), null, null); - } - - public static AbstractCsvLayout createLayout(final CSVFormat format) { - return new CsvParameterLayout(null, Charset.forName(DEFAULT_CHARSET), format, null, null); - } - - @PluginFactory - public static AbstractCsvLayout createLayout( - // @formatter:off - @PluginConfiguration final Configuration config, - @PluginAttribute(value = "format", defaultString = DEFAULT_FORMAT) final String format, - @PluginAttribute("delimiter") final Character delimiter, - @PluginAttribute("escape") final Character escape, - @PluginAttribute("quote") final Character quote, - @PluginAttribute("quoteMode") final QuoteMode quoteMode, - @PluginAttribute("nullString") final String nullString, - @PluginAttribute("recordSeparator") final String recordSeparator, - @PluginAttribute(value = "charset", defaultString = DEFAULT_CHARSET) final Charset charset, - @PluginAttribute("header") final String header, - @PluginAttribute("footer") final String footer) - // @formatter:on - { - - final CSVFormat csvFormat = createFormat(format, delimiter, escape, quote, quoteMode, nullString, recordSeparator); - return new CsvParameterLayout(config, charset, csvFormat, header, footer); - } - - public CsvParameterLayout(final Configuration config, final Charset charset, final CSVFormat csvFormat, final String header, final String footer) { - super(config, charset, csvFormat, header, footer); - } - - @Override - public String toSerializable(final LogEvent event) { - final Message message = event.getMessage(); - final Object[] parameters = message.getParameters(); - final StringBuilder buffer = getStringBuilder(); - try { - getFormat().printRecord(buffer, parameters); - return buffer.toString(); - } catch (final IOException e) { - StatusLogger.getLogger().error(message, e); - return getFormat().getCommentMarker() + " " + e; - } - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java index 45c5f4f8983..610a70ade05 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java @@ -16,38 +16,45 @@ */ package org.apache.logging.log4j.core.layout; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Map; -import java.util.zip.DeflaterOutputStream; -import java.util.zip.GZIPOutputStream; - import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.layout.internal.ExcludeChecker; +import org.apache.logging.log4j.core.layout.internal.IncludeChecker; +import org.apache.logging.log4j.core.layout.internal.ListChecker; import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.net.Severity; import org.apache.logging.log4j.core.util.JsonUtils; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.message.MapMessage; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.StringBuilderFormattable; import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.TriConsumer; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + /** * Lays out events in the Graylog Extended Log Format (GELF) 1.1. *

    @@ -58,7 +65,8 @@ * * @see GELF specification */ -@Plugin(name = "GelfLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin public final class GelfLayout extends AbstractStringLayout { public enum CompressionType { @@ -97,10 +105,16 @@ public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) th private final String host; private final boolean includeStacktrace; private final boolean includeThreadContext; + private final boolean includeMapMessage; private final boolean includeNullDelimiter; + private final boolean includeNewLineDelimiter; + private final boolean omitEmptyFields; + private final PatternLayout layout; + private final FieldWriter mdcWriter; + private final FieldWriter mapWriter; public static class Builder> extends AbstractStringLayout.Builder - implements org.apache.logging.log4j.core.util.Builder { + implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute private String host; @@ -123,6 +137,39 @@ public static class Builder> extends AbstractStringLayout.B @PluginBuilderAttribute private boolean includeNullDelimiter = false; + @PluginBuilderAttribute + private boolean includeNewLineDelimiter = false; + + @PluginBuilderAttribute + private String threadContextIncludes = null; + + @PluginBuilderAttribute + private String threadContextExcludes = null; + + @PluginBuilderAttribute + private String mapMessageIncludes = null; + + @PluginBuilderAttribute + private String mapMessageExcludes = null; + + @PluginBuilderAttribute + private boolean includeMapMessage = true; + + @PluginBuilderAttribute + private boolean omitEmptyFields = false; + + @PluginBuilderAttribute + private String messagePattern = null; + + @PluginBuilderAttribute + private String threadContextPrefix = ""; + + @PluginBuilderAttribute + private String mapPrefix = ""; + + @PluginElement("PatternSelector") + private PatternSelector patternSelector = null; + public Builder() { super(); setCharset(StandardCharsets.UTF_8); @@ -130,8 +177,58 @@ public Builder() { @Override public GelfLayout build() { + final ListChecker mdcChecker = createChecker(threadContextExcludes, threadContextIncludes); + final ListChecker mapChecker = createChecker(mapMessageExcludes, mapMessageIncludes); + PatternLayout patternLayout = null; + if (messagePattern != null && patternSelector != null) { + LOGGER.error("A message pattern and PatternSelector cannot both be specified on GelfLayout, " + + "ignoring message pattern"); + messagePattern = null; + } + if (messagePattern != null) { + patternLayout = PatternLayout.newBuilder().setPattern(messagePattern) + .setAlwaysWriteExceptions(includeStacktrace) + .setConfiguration(getConfiguration()) + .build(); + } + if (patternSelector != null) { + patternLayout = PatternLayout.newBuilder().setPatternSelector(patternSelector) + .setAlwaysWriteExceptions(includeStacktrace) + .setConfiguration(getConfiguration()) + .build(); + } return new GelfLayout(getConfiguration(), host, additionalFields, compressionType, compressionThreshold, - includeStacktrace, includeThreadContext, includeNullDelimiter); + includeStacktrace, includeThreadContext, includeMapMessage, includeNullDelimiter, + includeNewLineDelimiter, omitEmptyFields, mdcChecker, mapChecker, patternLayout, + threadContextPrefix, mapPrefix); + } + + private ListChecker createChecker(final String excludes, final String includes) { + ListChecker checker = null; + if (excludes != null) { + final String[] array = excludes.split(Patterns.COMMA_SEPARATOR); + if (array.length > 0) { + final List excludeList = new ArrayList<>(array.length); + for (final String str : array) { + excludeList.add(str.trim()); + } + checker = new ExcludeChecker(excludeList); + } + } + if (includes != null) { + final String[] array = includes.split(Patterns.COMMA_SEPARATOR); + if (array.length > 0) { + final List includeList = new ArrayList<>(array.length); + for (final String str : array) { + includeList.add(str.trim()); + } + checker = new IncludeChecker(includeList); + } + } + if (checker == null) { + checker = ListChecker.NOOP_CHECKER; + } + return checker; } public String getHost() { @@ -156,6 +253,10 @@ public boolean isIncludeThreadContext() { public boolean isIncludeNullDelimiter() { return includeNullDelimiter; } + public boolean isIncludeNewLineDelimiter() { + return includeNewLineDelimiter; + } + public KeyValuePair[] getAdditionalFields() { return additionalFields; } @@ -222,6 +323,16 @@ public B setIncludeNullDelimiter(final boolean includeNullDelimiter) { return asBuilder(); } + /** + * Whether to include newline (LF) as delimiter after each event (optional, default to false). + * + * @return this builder + */ + public B setIncludeNewLineDelimiter(final boolean includeNewLineDelimiter) { + this.includeNewLineDelimiter = includeNewLineDelimiter; + return asBuilder(); + } + /** * Additional fields to set on each log event. * @@ -231,19 +342,108 @@ public B setAdditionalFields(final KeyValuePair[] additionalFields) { this.additionalFields = additionalFields; return asBuilder(); } - } - /** - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - public GelfLayout(final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType, - final int compressionThreshold, final boolean includeStacktrace) { - this(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, true, false); + /** + * The pattern to use to format the message. + * @param pattern the pattern string. + * @return this builder + */ + public B setMessagePattern(final String pattern) { + this.messagePattern = pattern; + return asBuilder(); + } + + /** + * The PatternSelector to use to format the message. + * @param patternSelector the PatternSelector. + * @return this builder + */ + public B setPatternSelector(final PatternSelector patternSelector) { + this.patternSelector = patternSelector; + return asBuilder(); + } + + /** + * A comma separated list of thread context keys to include; + * @param mdcIncludes the list of keys. + * @return this builder + */ + public B setMdcIncludes(final String mdcIncludes) { + this.threadContextIncludes = mdcIncludes; + return asBuilder(); + } + + /** + * A comma separated list of thread context keys to include; + * @param mdcExcludes the list of keys. + * @return this builder + */ + public B setMdcExcludes(final String mdcExcludes) { + this.threadContextExcludes = mdcExcludes; + return asBuilder(); + } + + /** + * Whether to include MapMessage fields as additional fields (optional, default to true). + * + * @return this builder + */ + public B setIncludeMapMessage(final boolean includeMapMessage) { + this.includeMapMessage = includeMapMessage; + return asBuilder(); + } + + /** + * A comma separated list of thread context keys to include; + * @param mapMessageIncludes the list of keys. + * @return this builder + */ + public B setMapMessageIncludes(final String mapMessageIncludes) { + this.mapMessageIncludes = mapMessageIncludes; + return asBuilder(); + } + + /** + * A comma separated list of MapMessage keys to exclude; + * @param mapMessageExcludes the list of keys. + * @return this builder + */ + public B setMapMessageExcludes(final String mapMessageExcludes) { + this.mapMessageExcludes = mapMessageExcludes; + return asBuilder(); + } + + /** + * The String to prefix the ThreadContext attributes. + * @param prefix The prefix value. Null values will be ignored. + * @return this builder. + */ + public B setThreadContextPrefix(final String prefix) { + if (prefix != null) { + this.threadContextPrefix = prefix; + } + return asBuilder(); + } + + /** + * The String to prefix the MapMessage attributes. + * @param prefix The prefix value. Null values will be ignored. + * @return this builder. + */ + public B setMapPrefix(final String prefix) { + if (prefix != null) { + this.mapPrefix = prefix; + } + return asBuilder(); + } } - private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType, - final int compressionThreshold, final boolean includeStacktrace, final boolean includeThreadContext, final boolean includeNullDelimiter) { + private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields, + final CompressionType compressionType, final int compressionThreshold, final boolean includeStacktrace, + final boolean includeThreadContext, final boolean includeMapMessage, final boolean includeNullDelimiter, + final boolean includeNewLineDelimiter, final boolean omitEmptyFields, final ListChecker mdcChecker, + final ListChecker mapChecker, final PatternLayout patternLayout, final String mdcPrefix, + final String mapPrefix) { super(config, StandardCharsets.UTF_8, null, null); this.host = host != null ? host : NetUtils.getLocalHostname(); this.additionalFields = additionalFields != null ? additionalFields : new KeyValuePair[0]; @@ -258,31 +458,43 @@ private GelfLayout(final Configuration config, final String host, final KeyValue this.compressionThreshold = compressionThreshold; this.includeStacktrace = includeStacktrace; this.includeThreadContext = includeThreadContext; + this.includeMapMessage = includeMapMessage; this.includeNullDelimiter = includeNullDelimiter; + this.includeNewLineDelimiter = includeNewLineDelimiter; + this.omitEmptyFields = omitEmptyFields; if (includeNullDelimiter && compressionType != CompressionType.OFF) { throw new IllegalArgumentException("null delimiter cannot be used with compression"); } + this.mdcWriter = new FieldWriter(mdcChecker, mdcPrefix); + this.mapWriter = new FieldWriter(mapChecker, mapPrefix); + this.layout = patternLayout; } - /** - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - public static GelfLayout createLayout( - //@formatter:off - @PluginAttribute("host") final String host, - @PluginElement("AdditionalField") final KeyValuePair[] additionalFields, - @PluginAttribute(value = "compressionType", - defaultString = "GZIP") final CompressionType compressionType, - @PluginAttribute(value = "compressionThreshold", - defaultInt = COMPRESSION_THRESHOLD) final int compressionThreshold, - @PluginAttribute(value = "includeStacktrace", - defaultBoolean = true) final boolean includeStacktrace) { - // @formatter:on - return new GelfLayout(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, true, false); + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("host=").append(host); + sb.append(", compressionType=").append(compressionType.toString()); + sb.append(", compressionThreshold=").append(compressionThreshold); + sb.append(", includeStackTrace=").append(includeStacktrace); + sb.append(", includeThreadContext=").append(includeThreadContext); + sb.append(", includeNullDelimiter=").append(includeNullDelimiter); + sb.append(", includeNewLineDelimiter=").append(includeNewLineDelimiter); + final String threadVars = mdcWriter.getChecker().toString(); + if (threadVars.length() > 0) { + sb.append(", ").append(threadVars); + } + final String mapVars = mapWriter.getChecker().toString(); + if (mapVars.length() > 0) { + sb.append(", ").append(mapVars); + } + if (layout != null) { + sb.append(", PatternLayout{").append(layout.toString()).append("}"); + } + return sb.toString(); } - @PluginBuilderFactory + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } @@ -294,7 +506,7 @@ public Map getContentFormat() { @Override public String getContentType() { - return JsonLayout.CONTENT_TYPE + "; charset=" + this.getCharset(); + return "application/json; charset=" + this.getCharset(); } @Override @@ -315,6 +527,11 @@ public void encode(final LogEvent event, final ByteBufferDestination destination helper.encode(text, destination); } + @Override + public boolean requiresLocation() { + return Objects.nonNull(layout) && layout.requiresLocation(); + } + private byte[] compress(final byte[] bytes) { try { final ByteArrayOutputStream baos = new ByteArrayOutputStream(compressionThreshold / 8); @@ -359,25 +576,37 @@ private StringBuilder toText(final LogEvent event, final StringBuilder builder, if (additionalFields.length > 0) { final StrSubstitutor strSubstitutor = getConfiguration().getStrSubstitutor(); for (final KeyValuePair additionalField : additionalFields) { - builder.append(QU); - JsonUtils.quoteAsString(additionalField.getKey(), builder); - builder.append("\":\""); final String value = valueNeedsLookup(additionalField.getValue()) - ? strSubstitutor.replace(event, additionalField.getValue()) - : additionalField.getValue(); - JsonUtils.quoteAsString(toNullSafeString(value), builder); - builder.append(QC); + ? strSubstitutor.replace(event, additionalField.getValue()) + : additionalField.getValue(); + if (Strings.isNotEmpty(value) || !omitEmptyFields) { + builder.append(QU); + JsonUtils.quoteAsString(additionalField.getKey(), builder); + builder.append("\":\""); + JsonUtils.quoteAsString(toNullSafeString(value), builder); + builder.append(QC); + } } } if (includeThreadContext) { - event.getContextData().forEach(WRITE_KEY_VALUES_INTO, builder); + event.getContextData().forEach(mdcWriter, builder); } - if (event.getThrown() != null) { + if (includeMapMessage && event.getMessage() instanceof MapMessage) { + ((MapMessage) event.getMessage()).forEach((key, value) -> mapWriter.accept(key, value, builder)); + } + + if (event.getThrown() != null || layout != null) { builder.append("\"full_message\":\""); - if (includeStacktrace) { - JsonUtils.quoteAsString(formatThrowable(event.getThrown()), builder); + if (layout != null) { + final StringBuilder messageBuffer = getMessageStringBuilder(); + layout.serialize(event, messageBuffer); + JsonUtils.quoteAsString(messageBuffer, builder); } else { - JsonUtils.quoteAsString(event.getThrown().toString(), builder); + if (includeStacktrace) { + JsonUtils.quoteAsString(formatThrowable(event.getThrown()), builder); + } else { + JsonUtils.quoteAsString(event.getThrown().toString(), builder); + } } builder.append(QC); } @@ -385,7 +614,7 @@ private StringBuilder toText(final LogEvent event, final StringBuilder builder, builder.append("\"short_message\":\""); final Message message = event.getMessage(); if (message instanceof CharSequence) { - JsonUtils.quoteAsString(((CharSequence)message), builder); + JsonUtils.quoteAsString(((CharSequence) message), builder); } else if (gcFree && message instanceof StringBuilderFormattable) { final StringBuilder messageBuffer = getMessageStringBuilder(); try { @@ -402,6 +631,9 @@ private StringBuilder toText(final LogEvent event, final StringBuilder builder, if (includeNullDelimiter) { builder.append('\0'); } + if (includeNewLineDelimiter) { + builder.append('\n'); + } return builder; } @@ -409,16 +641,31 @@ private static boolean valueNeedsLookup(final String value) { return value != null && value.contains("${"); } - private static final TriConsumer WRITE_KEY_VALUES_INTO = new TriConsumer() { + private class FieldWriter implements TriConsumer { + private final ListChecker checker; + private final String prefix; + + FieldWriter(final ListChecker checker, final String prefix) { + this.checker = checker; + this.prefix = prefix; + } + @Override public void accept(final String key, final Object value, final StringBuilder stringBuilder) { - stringBuilder.append(QU); - JsonUtils.quoteAsString(key, stringBuilder); - stringBuilder.append("\":\""); - JsonUtils.quoteAsString(toNullSafeString(String.valueOf(value)), stringBuilder); - stringBuilder.append(QC); + final String stringValue = String.valueOf(value); + if (checker.check(key) && (Strings.isNotEmpty(stringValue) || !omitEmptyFields)) { + stringBuilder.append(QU); + JsonUtils.quoteAsString(Strings.concat(prefix, key), stringBuilder); + stringBuilder.append("\":\""); + JsonUtils.quoteAsString(toNullSafeString(stringValue), stringBuilder); + stringBuilder.append(QC); + } + } + + public ListChecker getChecker() { + return checker; } - }; + } private static final ThreadLocal messageStringBuilder = new ThreadLocal<>(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java index 8a09e4aa3f9..5d61ee7e2f2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java @@ -16,6 +16,18 @@ */ package org.apache.logging.log4j.core.layout; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.pattern.DatePatternConverter; +import org.apache.logging.log4j.core.util.Transform; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.Strings; + import java.io.IOException; import java.io.InterruptedIOException; import java.io.LineNumberReader; @@ -26,19 +38,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.LoggerConfig; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.util.Transform; -import org.apache.logging.log4j.util.Strings; +import java.util.Date; /** * Outputs events as rows in an HTML table on an HTML page. @@ -47,7 +47,8 @@ * characters could result in corrupted log files. *

    */ -@Plugin(name = "HtmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin public final class HtmlLayout extends AbstractStringLayout { /** @@ -59,6 +60,7 @@ public final class HtmlLayout extends AbstractStringLayout { private static final String REGEXP = Strings.LINE_SEPARATOR.equals("\n") ? "\n" : Strings.LINE_SEPARATOR + "|\n"; private static final String DEFAULT_TITLE = "Log4j Log Messages"; private static final String DEFAULT_CONTENT_TYPE = "text/html"; + private static final String DEFAULT_DATE_PATTERN = "JVM_ELAPSE_TIME"; private final long jvmStartTime = ManagementFactory.getRuntimeMXBean().getStartTime(); @@ -69,15 +71,16 @@ public final class HtmlLayout extends AbstractStringLayout { private final String font; private final String fontSize; private final String headerSize; + private final DatePatternConverter datePatternConverter; /**Possible font sizes */ - public static enum FontSize { + public enum FontSize { SMALLER("smaller"), XXSMALL("xx-small"), XSMALL("x-small"), SMALL("small"), MEDIUM("medium"), LARGE("large"), XLARGE("x-large"), XXLARGE("xx-large"), LARGER("larger"); private final String size; - private FontSize(final String size) { + FontSize(final String size) { this.size = size; } @@ -100,7 +103,7 @@ public FontSize larger() { } private HtmlLayout(final boolean locationInfo, final String title, final String contentType, final Charset charset, - final String font, final String fontSize, final String headerSize) { + final String font, final String fontSize, final String headerSize, final String datePattern, final String timezone) { super(charset); this.locationInfo = locationInfo; this.title = title; @@ -108,6 +111,8 @@ private HtmlLayout(final boolean locationInfo, final String title, final String this.font = font; this.fontSize = fontSize; this.headerSize = headerSize; + this.datePatternConverter = DEFAULT_DATE_PATTERN.equals(datePattern) ? null + : DatePatternConverter.newInstance(new String[] {datePattern, timezone}); } /** @@ -124,6 +129,11 @@ public boolean isLocationInfo() { return locationInfo; } + @Override + public boolean requiresLocation() { + return locationInfo; + } + private String addCharsetToContentType(final String contentType) { if (contentType == null) { return DEFAULT_CONTENT_TYPE + "; charset=" + getCharset(); @@ -144,7 +154,12 @@ public String toSerializable(final LogEvent event) { sbuf.append(Strings.LINE_SEPARATOR).append("").append(Strings.LINE_SEPARATOR); sbuf.append(""); - sbuf.append(event.getTimeMillis() - jvmStartTime); + + if (datePatternConverter == null) { + sbuf.append(event.getTimeMillis() - jvmStartTime); + } else { + datePatternConverter.format(event, sbuf); + } sbuf.append("").append(Strings.LINE_SEPARATOR); final String escapedThread = Transform.escapeHtmlTags(event.getThreadName()); @@ -294,7 +309,7 @@ public byte[] getHeader() { appendLs(sbuf, ""); appendLs(sbuf, ""); appendLs(sbuf, "
    "); - appendLs(sbuf, "Log session start time " + new java.util.Date() + "
    "); + appendLs(sbuf, "Log session start time " + new Date() + "
    "); appendLs(sbuf, "
    "); appendLs(sbuf, ""); @@ -324,33 +339,6 @@ public byte[] getFooter() { return getBytes(sbuf.toString()); } - /** - * Creates an HTML Layout. - * @param locationInfo If "true", location information will be included. The default is false. - * @param title The title to include in the file header. If none is specified the default title will be used. - * @param contentType The content type. Defaults to "text/html". - * @param charset The character set to use. If not specified, the default will be used. - * @param fontSize The font size of the text. - * @param font The font to use for the text. - * @return An HTML Layout. - */ - @PluginFactory - public static HtmlLayout createLayout( - @PluginAttribute(value = "locationInfo") final boolean locationInfo, - @PluginAttribute(value = "title", defaultString = DEFAULT_TITLE) final String title, - @PluginAttribute("contentType") String contentType, - @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset, - @PluginAttribute("fontSize") String fontSize, - @PluginAttribute(value = "fontName", defaultString = DEFAULT_FONT_FAMILY) final String font) { - final FontSize fs = FontSize.getFontSize(fontSize); - fontSize = fs.getFontSize(); - final String headerSize = fs.larger().getFontSize(); - if (contentType == null) { - contentType = DEFAULT_CONTENT_TYPE + "; charset=" + charset; - } - return new HtmlLayout(locationInfo, title, contentType, charset, font, fontSize, headerSize); - } - /** * Creates an HTML Layout using the default settings. * @@ -360,12 +348,12 @@ public static HtmlLayout createDefaultLayout() { return newBuilder().build(); } - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute private boolean locationInfo = false; @@ -385,39 +373,55 @@ public static class Builder implements org.apache.logging.log4j.core.util.Builde @PluginBuilderAttribute private String fontName = DEFAULT_FONT_FAMILY; + @PluginBuilderAttribute + private String datePattern = DEFAULT_DATE_PATTERN; + + @PluginBuilderAttribute + private String timezone = null; // null means default timezone + private Builder() { } - public Builder withLocationInfo(final boolean locationInfo) { + public Builder setLocationInfo(final boolean locationInfo) { this.locationInfo = locationInfo; return this; } - public Builder withTitle(final String title) { + public Builder setTitle(final String title) { this.title = title; return this; } - public Builder withContentType(final String contentType) { + public Builder setContentType(final String contentType) { this.contentType = contentType; return this; } - public Builder withCharset(final Charset charset) { + public Builder setCharset(final Charset charset) { this.charset = charset; return this; } - public Builder withFontSize(final FontSize fontSize) { + public Builder setFontSize(final FontSize fontSize) { this.fontSize = fontSize; return this; } - public Builder withFontName(final String fontName) { + public Builder setFontName(final String fontName) { this.fontName = fontName; return this; } + public Builder setDatePattern(final String datePattern) { + this.datePattern = datePattern; + return this; + } + + public Builder setTimezone(final String timezone) { + this.timezone = timezone; + return this; + } + @Override public HtmlLayout build() { // TODO: extract charset from content-type @@ -425,7 +429,7 @@ public HtmlLayout build() { contentType = DEFAULT_CONTENT_TYPE + "; charset=" + charset; } return new HtmlLayout(locationInfo, title, contentType, charset, fontName, fontSize.getFontSize(), - fontSize.larger().getFontSize()); + fontSize.larger().getFontSize(), datePattern, timezone); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java deleted file mode 100644 index b4c914c183e..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.util.HashSet; -import java.util.Set; - -import javax.xml.stream.XMLStreamException; - -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.jackson.JsonConstants; -import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; -import org.apache.logging.log4j.core.jackson.Log4jXmlObjectMapper; -import org.apache.logging.log4j.core.jackson.Log4jYamlObjectMapper; -import org.apache.logging.log4j.core.jackson.XmlConstants; -import org.codehaus.stax2.XMLStreamWriter2; - -import com.fasterxml.jackson.core.PrettyPrinter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.core.util.MinimalPrettyPrinter; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter; - -abstract class JacksonFactory { - - static class JSON extends JacksonFactory { - - private final boolean encodeThreadContextAsList; - private final boolean includeStacktrace; - private final boolean stacktraceAsString; - private final boolean objectMessageAsJsonObject; - - public JSON(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString, final boolean objectMessageAsJsonObject) { - this.encodeThreadContextAsList = encodeThreadContextAsList; - this.includeStacktrace = includeStacktrace; - this.stacktraceAsString = stacktraceAsString; - this.objectMessageAsJsonObject = objectMessageAsJsonObject; - } - - @Override - protected String getPropertNameForContextMap() { - return JsonConstants.ELT_CONTEXT_MAP; - } - - @Override - protected String getPropertNameForSource() { - return JsonConstants.ELT_SOURCE; - } - - @Override - protected String getPropertNameForNanoTime() { - return JsonConstants.ELT_NANO_TIME; - } - - @Override - protected PrettyPrinter newCompactPrinter() { - return new MinimalPrettyPrinter(); - } - - @Override - protected ObjectMapper newObjectMapper() { - return new Log4jJsonObjectMapper(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject); - } - - @Override - protected PrettyPrinter newPrettyPrinter() { - return new DefaultPrettyPrinter(); - } - - } - - static class XML extends JacksonFactory { - - static final int DEFAULT_INDENT = 1; - - private final boolean includeStacktrace; - private final boolean stacktraceAsString; - - - public XML(final boolean includeStacktrace, final boolean stacktraceAsString) { - this.includeStacktrace = includeStacktrace; - this.stacktraceAsString = stacktraceAsString; - } - - @Override - protected String getPropertNameForContextMap() { - return XmlConstants.ELT_CONTEXT_MAP; - } - - @Override - protected String getPropertNameForSource() { - return XmlConstants.ELT_SOURCE; - } - - @Override - protected String getPropertNameForNanoTime() { - return JsonConstants.ELT_NANO_TIME; - } - - @Override - protected PrettyPrinter newCompactPrinter() { - // Yes, null is the proper answer. - return null; - } - - @Override - protected ObjectMapper newObjectMapper() { - return new Log4jXmlObjectMapper(includeStacktrace, stacktraceAsString); - } - - @Override - protected PrettyPrinter newPrettyPrinter() { - return new Log4jXmlPrettyPrinter(DEFAULT_INDENT); - } - } - - static class YAML extends JacksonFactory { - - private final boolean includeStacktrace; - private final boolean stacktraceAsString; - - - public YAML(final boolean includeStacktrace, final boolean stacktraceAsString) { - this.includeStacktrace = includeStacktrace; - this.stacktraceAsString = stacktraceAsString; - } - - @Override - protected String getPropertNameForContextMap() { - return JsonConstants.ELT_CONTEXT_MAP; - } - - @Override - protected String getPropertNameForSource() { - return JsonConstants.ELT_SOURCE; - } - - @Override - protected String getPropertNameForNanoTime() { - return JsonConstants.ELT_NANO_TIME; - } - - @Override - protected PrettyPrinter newCompactPrinter() { - return new MinimalPrettyPrinter(); - } - - @Override - protected ObjectMapper newObjectMapper() { - return new Log4jYamlObjectMapper(false, includeStacktrace, stacktraceAsString); - } - - @Override - protected PrettyPrinter newPrettyPrinter() { - return new DefaultPrettyPrinter(); - } - } - - /** - * When <Event>s are written into a XML file; the "Event" object is not the root element, but an element named - * <Events> created using {@link XmlLayout#getHeader()} and {@link XmlLayout#getFooter()} methods. - *

    - * {@link com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter} is used to print the Event object into - * XML; hence it assumes <Event> tag as the root element, so it prints the <Event> tag without any - * indentation. To add an indentation to the <Event> tag; hence an additional indentation for any - * sub-elements, this class is written. As an additional task, to avoid the blank line printed after the ending - * </Event> tag, {@link #writePrologLinefeed(XMLStreamWriter2)} method is also overridden. - *

    - */ - static class Log4jXmlPrettyPrinter extends DefaultXmlPrettyPrinter { - - private static final long serialVersionUID = 1L; - - Log4jXmlPrettyPrinter(final int nesting) { - _nesting = nesting; - } - - @Override - public void writePrologLinefeed(final XMLStreamWriter2 sw) throws XMLStreamException { - // nothing - } - - /** - * Sets the nesting level to 1 rather than 0, so the "Event" tag will get indentation of next level below root. - */ - @Override - public DefaultXmlPrettyPrinter createInstance() { - return new Log4jXmlPrettyPrinter(XML.DEFAULT_INDENT); - } - - } - - abstract protected String getPropertNameForContextMap(); - - abstract protected String getPropertNameForSource(); - - abstract protected String getPropertNameForNanoTime(); - - abstract protected PrettyPrinter newCompactPrinter(); - - abstract protected ObjectMapper newObjectMapper(); - - abstract protected PrettyPrinter newPrettyPrinter(); - - ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact) { - final SimpleFilterProvider filters = new SimpleFilterProvider(); - final Set except = new HashSet<>(2); - if (!locationInfo) { - except.add(this.getPropertNameForSource()); - } - if (!properties) { - except.add(this.getPropertNameForContextMap()); - } - except.add(this.getPropertNameForNanoTime()); - filters.addFilter(Log4jLogEvent.class.getName(), SimpleBeanPropertyFilter.serializeAllExcept(except)); - final ObjectWriter writer = this.newObjectMapper().writer(compact ? this.newCompactPrinter() : this.newPrettyPrinter()); - return writer.with(filters); - } - -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java deleted file mode 100644 index 607ec43baf7..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.util.KeyValuePair; - -/** - * Appends a series of JSON events as strings serialized as bytes. - * - *

    Complete well-formed JSON vs. fragment JSON

    - *

    - * If you configure {@code complete="true"}, the appender outputs a well-formed JSON document. By default, with - * {@code complete="false"}, you should include the output as an external file in a separate file to form a - * well-formed JSON document. - *

    - *

    - * If {@code complete="false"}, the appender does not write the JSON open array character "[" at the start - * of the document, "]" and the end, nor comma "," between records. - *

    - *

    Encoding

    - *

    - * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise - * events containing non ASCII characters could result in corrupted log files. - *

    - *

    Pretty vs. compact JSON

    - *

    - * By default, the JSON layout is not compact (a.k.a. "pretty") with {@code compact="false"}, which means the - * appender uses end-of-line characters and indents lines to format the text. If {@code compact="true"}, then no - * end-of-line or indentation is used. Message content may contain, of course, escaped end-of-lines. - *

    - *

    Additional Fields

    - *

    - * This property allows addition of custom fields into generated JSON. - * {@code } inserts {@code "foo":"bar"} directly - * into JSON output. Supports Lookup expressions. - *

    - */ -@Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) -public final class JsonLayout extends AbstractJacksonLayout { - - private static final String DEFAULT_FOOTER = "]"; - - private static final String DEFAULT_HEADER = "["; - - static final String CONTENT_TYPE = "application/json"; - - public static class Builder> extends AbstractJacksonLayout.Builder - implements org.apache.logging.log4j.core.util.Builder { - - @PluginBuilderAttribute - private boolean propertiesAsList; - - @PluginBuilderAttribute - private boolean objectMessageAsJsonObject; - - @PluginElement("AdditionalField") - private KeyValuePair[] additionalFields; - - public Builder() { - super(); - setCharset(StandardCharsets.UTF_8); - } - - @Override - public JsonLayout build() { - final boolean encodeThreadContextAsList = isProperties() && propertiesAsList; - final String headerPattern = toStringOrNull(getHeader()); - final String footerPattern = toStringOrNull(getFooter()); - return new JsonLayout(getConfiguration(), isLocationInfo(), isProperties(), encodeThreadContextAsList, - isComplete(), isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), - isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), - getAdditionalFields(), getObjectMessageAsJsonObject()); - } - - public boolean isPropertiesAsList() { - return propertiesAsList; - } - - public B setPropertiesAsList(final boolean propertiesAsList) { - this.propertiesAsList = propertiesAsList; - return asBuilder(); - } - - public boolean getObjectMessageAsJsonObject() { - return objectMessageAsJsonObject; - } - - public B setObjectMessageAsJsonObject(final boolean objectMessageAsJsonObject) { - this.objectMessageAsJsonObject = objectMessageAsJsonObject; - return asBuilder(); - } - - @Override - public KeyValuePair[] getAdditionalFields() { - return additionalFields; - } - - @Override - public B setAdditionalFields(KeyValuePair[] additionalFields) { - this.additionalFields = additionalFields; - return asBuilder(); - } - } - - /** - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - protected JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, - final boolean encodeThreadContextAsList, - final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, - final String footerPattern, final Charset charset, final boolean includeStacktrace) { - super(config, new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace, false, false).newWriter( - locationInfo, properties, compact), - charset, compact, complete, eventEol, - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), - false, null); - } - - private JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, - final boolean encodeThreadContextAsList, - final boolean complete, final boolean compact, final boolean eventEol, - final String headerPattern, final String footerPattern, final Charset charset, - final boolean includeStacktrace, final boolean stacktraceAsString, - final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields, final boolean objectMessageAsJsonObject) { - super(config, new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject).newWriter( - locationInfo, properties, compact), - charset, compact, complete, eventEol, - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), - includeNullDelimiter, - additionalFields); - } - - /** - * Returns appropriate JSON header. - * - * @return a byte array containing the header, opening the JSON array. - */ - @Override - public byte[] getHeader() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - final String str = serializeToString(getHeaderSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - /** - * Returns appropriate JSON footer. - * - * @return a byte array containing the footer, closing the JSON array. - */ - @Override - public byte[] getFooter() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - buf.append(this.eol); - final String str = serializeToString(getFooterSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - @Override - public Map getContentFormat() { - final Map result = new HashMap<>(); - result.put("version", "2.0"); - return result; - } - - /** - * @return The content type. - */ - @Override - public String getContentType() { - return CONTENT_TYPE + "; charset=" + this.getCharset(); - } - - /** - * Creates a JSON Layout. - * @param config - * The plugin configuration. - * @param locationInfo - * If "true", includes the location information in the generated JSON. - * @param properties - * If "true", includes the thread context map in the generated JSON. - * @param propertiesAsList - * If true, the thread context map is included as a list of map entry objects, where each entry has - * a "key" attribute (whose value is the key) and a "value" attribute (whose value is the value). - * Defaults to false, in which case the thread context map is included as a simple map of key-value - * pairs. - * @param complete - * If "true", includes the JSON header and footer, and comma between records. - * @param compact - * If "true", does not use end-of-lines and indentation, defaults to "false". - * @param eventEol - * If "true", forces an EOL after each log event (even if compact is "true"), defaults to "false". This - * allows one even per line, even in compact mode. - * @param headerPattern - * The header pattern, defaults to {@code "["} if null. - * @param footerPattern - * The header pattern, defaults to {@code "]"} if null. - * @param charset - * The character set to use, if {@code null}, uses "UTF-8". - * @param includeStacktrace - * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". - * @return A JSON Layout. - * - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - public static JsonLayout createLayout( - final Configuration config, - final boolean locationInfo, - final boolean properties, - final boolean propertiesAsList, - final boolean complete, - final boolean compact, - final boolean eventEol, - final String headerPattern, - final String footerPattern, - final Charset charset, - final boolean includeStacktrace) { - final boolean encodeThreadContextAsList = properties && propertiesAsList; - return new JsonLayout(config, locationInfo, properties, encodeThreadContextAsList, complete, compact, eventEol, - headerPattern, footerPattern, charset, includeStacktrace, false, false, null, false); - } - - @PluginBuilderFactory - public static > B newBuilder() { - return new Builder().asBuilder(); - } - - /** - * Creates a JSON Layout using the default settings. Useful for testing. - * - * @return A JSON Layout. - */ - public static JsonLayout createDefaultLayout() { - return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, false, - DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null, false); - } - - @Override - public void toSerializable(final LogEvent event, final Writer writer) throws IOException { - if (complete && eventCount > 0) { - writer.append(", "); - } - super.toSerializable(event, writer); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java new file mode 100644 index 00000000000..6c058762db7 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.pattern.PatternFormatter; +import org.apache.logging.log4j.core.pattern.PatternParser; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Selects the pattern to use based on the Level in the LogEvent. + */ +@Configurable(elementType = PatternSelector.ELEMENT_TYPE, printObject = true) +@Plugin +public class LevelPatternSelector implements PatternSelector{ + + /** + * Custom MarkerPatternSelector builder. Use the {@link LevelPatternSelector#newBuilder() builder factory method} to create this. + */ + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + + @PluginElement("PatternMatch") + private PatternMatch[] properties; + + @PluginBuilderAttribute("defaultPattern") + private String defaultPattern; + + @PluginBuilderAttribute(value = "alwaysWriteExceptions") + private boolean alwaysWriteExceptions = true; + + @PluginBuilderAttribute(value = "disableAnsi") + private boolean disableAnsi; + + @PluginBuilderAttribute(value = "noConsoleNoAnsi") + private boolean noConsoleNoAnsi; + + @PluginConfiguration + private Configuration configuration; + + @Override + public LevelPatternSelector build() { + if (defaultPattern == null) { + defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; + } + if (properties == null || properties.length == 0) { + LOGGER.warn("No marker patterns were provided with PatternMatch"); + return null; + } + return new LevelPatternSelector(properties, defaultPattern, alwaysWriteExceptions, disableAnsi, + noConsoleNoAnsi, configuration); + } + + public Builder setProperties(final PatternMatch[] properties) { + this.properties = properties; + return this; + } + + public Builder setDefaultPattern(final String defaultPattern) { + this.defaultPattern = defaultPattern; + return this; + } + + public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { + this.alwaysWriteExceptions = alwaysWriteExceptions; + return this; + } + + public Builder setDisableAnsi(final boolean disableAnsi) { + this.disableAnsi = disableAnsi; + return this; + } + + public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { + this.noConsoleNoAnsi = noConsoleNoAnsi; + return this; + } + + public Builder setConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + + } + + private final Map formatterMap = new HashMap<>(); + + private final Map patternMap = new HashMap<>(); + + private final PatternFormatter[] defaultFormatters; + + private final String defaultPattern; + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final boolean requiresLocation; + + /** + * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version. + */ + @Deprecated + public LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern, + final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi, + final Configuration config) { + this(properties, defaultPattern, alwaysWriteExceptions, false, noConsoleNoAnsi, config); + } + + private LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern, + final boolean alwaysWriteExceptions, final boolean disableAnsi, + final boolean noConsoleNoAnsi, final Configuration config) { + boolean needsLocation = false; + final PatternParser parser = PatternLayout.createPatternParser(config); + for (final PatternMatch property : properties) { + try { + final List list = parser.parse(property.getPattern(), alwaysWriteExceptions, + disableAnsi, noConsoleNoAnsi); + final PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]); + formatterMap.put(property.getKey(), formatters); + for (int i = 0; !needsLocation && i < formatters.length; ++i) { + needsLocation = formatters[i].requiresLocation(); + } + + patternMap.put(property.getKey(), property.getPattern()); + } catch (final RuntimeException ex) { + throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex); + } + } + try { + final List list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi, + noConsoleNoAnsi); + defaultFormatters = list.toArray(new PatternFormatter[0]); + this.defaultPattern = defaultPattern; + for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) { + needsLocation = defaultFormatters[i].requiresLocation(); + } + } catch (final RuntimeException ex) { + throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex); + } + requiresLocation = needsLocation; + } + + @Override + public boolean requiresLocation() { + return requiresLocation; + } + + @Override + public PatternFormatter[] getFormatters(final LogEvent event) { + final Level level = event.getLevel(); + if (level == null) { + return defaultFormatters; + } + for (final String key : formatterMap.keySet()) { + if (level.name().equalsIgnoreCase(key)) { + return formatterMap.get(key); + } + } + return defaultFormatters; + } + + /** + * Creates a builder for a custom ScriptPatternSelector. + * + * @return a ScriptPatternSelector builder. + */ + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Deprecated, use {@link #newBuilder()} instead. + * @param properties + * @param defaultPattern + * @param alwaysWriteExceptions + * @param noConsoleNoAnsi + * @param configuration + * @return a new MarkerPatternSelector. + * @deprecated Use {@link #newBuilder()} instead. + */ + @Deprecated + public static LevelPatternSelector createSelector( + final PatternMatch[] properties, + final String defaultPattern, + final boolean alwaysWriteExceptions, + final boolean noConsoleNoAnsi, + final Configuration configuration) { + final Builder builder = newBuilder(); + builder.setProperties(properties); + builder.setDefaultPattern(defaultPattern); + builder.setAlwaysWriteExceptions(alwaysWriteExceptions); + builder.setNoConsoleNoAnsi(noConsoleNoAnsi); + builder.setConfiguration(configuration); + return builder.build(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + boolean first = true; + for (final Map.Entry entry : patternMap.entrySet()) { + if (!first) { + sb.append(", "); + } + sb.append("key=\"").append(entry.getKey()).append("\", pattern=\"").append(entry.getValue()).append("\""); + first = false; + } + if (!first) { + sb.append(", "); + } + sb.append("default=\"").append(defaultPattern).append("\""); + return sb.toString(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java index b01dc5e3989..b47ad6c7b62 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java @@ -20,18 +20,19 @@ import java.util.HashMap; import java.util.Map; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.util.KeyValuePair; import org.apache.logging.log4j.message.StructuredDataId; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; /** * A LoggerFields container. */ -@Plugin(name = "LoggerFields", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin public final class LoggerFields { private final Map map; @@ -71,10 +72,10 @@ public String toString() { */ @PluginFactory public static LoggerFields createLoggerFields( - @PluginElement("LoggerFields") final KeyValuePair[] keyValuePairs, - @PluginAttribute("sdId") final String sdId, - @PluginAttribute("enterpriseId") final String enterpriseId, - @PluginAttribute(value = "discardIfAllFieldsAreEmpty") final boolean discardIfAllFieldsAreEmpty) { + @PluginElement final KeyValuePair[] keyValuePairs, + @PluginAttribute final String sdId, + @PluginAttribute final String enterpriseId, + @PluginAttribute final boolean discardIfAllFieldsAreEmpty) { final Map map = new HashMap<>(); for (final KeyValuePair keyValuePair : keyValuePairs) { @@ -88,8 +89,7 @@ public StructuredDataId getSdId() { if (enterpriseId == null || sdId == null) { return null; } - final int eId = Integer.parseInt(enterpriseId); - return new StructuredDataId(sdId, eId, null, null); + return new StructuredDataId(sdId, enterpriseId, null, null); } public boolean getDiscardIfAllFieldsAreEmpty() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java index 719af9d0271..afab1498440 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java @@ -16,47 +16,48 @@ */ package org.apache.logging.log4j.core.layout; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.pattern.PatternFormatter; import org.apache.logging.log4j.core.pattern.PatternParser; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Selects the pattern to use based on the Marker in the LogEvent. */ -@Plugin(name = "MarkerPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = PatternSelector.ELEMENT_TYPE, printObject = true) +@Plugin public class MarkerPatternSelector implements PatternSelector { /** * Custom MarkerPatternSelector builder. Use the {@link MarkerPatternSelector#newBuilder() builder factory method} to create this. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginElement("PatternMatch") private PatternMatch[] properties; - + @PluginBuilderAttribute("defaultPattern") private String defaultPattern; - - @PluginBuilderAttribute(value = "alwaysWriteExceptions") + + @PluginBuilderAttribute(value = "alwaysWriteExceptions") private boolean alwaysWriteExceptions = true; - + @PluginBuilderAttribute(value = "disableAnsi") private boolean disableAnsi; - + @PluginBuilderAttribute(value = "noConsoleNoAnsi") private boolean noConsoleNoAnsi; @@ -107,7 +108,7 @@ public Builder setConfiguration(final Configuration configuration) { } } - + private final Map formatterMap = new HashMap<>(); private final Map patternMap = new HashMap<>(); @@ -116,28 +117,24 @@ public Builder setConfiguration(final Configuration configuration) { private final String defaultPattern; - private static Logger LOGGER = StatusLogger.getLogger(); + private static final Logger LOGGER = StatusLogger.getLogger(); - - /** - * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version. - */ - @Deprecated - public MarkerPatternSelector(final PatternMatch[] properties, final String defaultPattern, - final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi, - final Configuration config) { - this(properties, defaultPattern, alwaysWriteExceptions, false, noConsoleNoAnsi, config); - } + private final boolean requiresLocation; private MarkerPatternSelector(final PatternMatch[] properties, final String defaultPattern, final boolean alwaysWriteExceptions, final boolean disableAnsi, final boolean noConsoleNoAnsi, final Configuration config) { final PatternParser parser = PatternLayout.createPatternParser(config); + boolean needsLocation = false; for (final PatternMatch property : properties) { try { final List list = parser.parse(property.getPattern(), alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi); - formatterMap.put(property.getKey(), list.toArray(new PatternFormatter[list.size()])); + final PatternFormatter[] formatters = list.toArray(new PatternFormatter[list.size()]); + formatterMap.put(property.getKey(), formatters); + for (int i = 0; !needsLocation && i < formatters.length; ++i) { + needsLocation = formatters[i].requiresLocation(); + } patternMap.put(property.getKey(), property.getPattern()); } catch (final RuntimeException ex) { throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex); @@ -148,9 +145,18 @@ private MarkerPatternSelector(final PatternMatch[] properties, final String defa noConsoleNoAnsi); defaultFormatters = list.toArray(new PatternFormatter[list.size()]); this.defaultPattern = defaultPattern; + for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) { + needsLocation = defaultFormatters[i].requiresLocation(); + } } catch (final RuntimeException ex) { throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex); } + requiresLocation = needsLocation; + } + + @Override + public boolean requiresLocation() { + return requiresLocation; } @Override @@ -172,37 +178,11 @@ public PatternFormatter[] getFormatters(final LogEvent event) { * * @return a ScriptPatternSelector builder. */ - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - /** - * Deprecated, use {@link #newBuilder()} instead. - * @param properties - * @param defaultPattern - * @param alwaysWriteExceptions - * @param noConsoleNoAnsi - * @param configuration - * @return a new MarkerPatternSelector. - * @deprecated Use {@link #newBuilder()} instead. - */ - @Deprecated - public static MarkerPatternSelector createSelector( - final PatternMatch[] properties, - final String defaultPattern, - final boolean alwaysWriteExceptions, - final boolean noConsoleNoAnsi, - final Configuration configuration) { - final Builder builder = newBuilder(); - builder.setProperties(properties); - builder.setDefaultPattern(defaultPattern); - builder.setAlwaysWriteExceptions(alwaysWriteExceptions); - builder.setNoConsoleNoAnsi(noConsoleNoAnsi); - builder.setConfiguration(configuration); - return builder.build(); - } - @Override public String toString() { final StringBuilder sb = new StringBuilder(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java index 2a8296c1dd7..46a5189f154 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java @@ -20,10 +20,10 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; /** * Formats a {@link LogEvent} in its {@link Message} form. @@ -32,7 +32,8 @@ * {@link org.apache.logging.log4j.message.StringMapMessage} to a JMS {@link javax.jms.MapMessage}. *

    */ -@Plugin(name = "MessageLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin public class MessageLayout extends AbstractLayout { public MessageLayout() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java index 57cbb9c8390..8af7ec90379 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java @@ -26,25 +26,26 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.pattern.FormattingInfo; import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; import org.apache.logging.log4j.core.pattern.PatternFormatter; import org.apache.logging.log4j.core.pattern.PatternParser; import org.apache.logging.log4j.core.pattern.RegexReplacement; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; import org.apache.logging.log4j.util.Strings; /** * A flexible layout configurable with pattern string. *

    - * The goal of this class is to {@link org.apache.logging.log4j.core.Layout#toByteArray format} a {@link LogEvent} and + * The goal of this class is to {@link Layout#toByteArray format} a {@link LogEvent} and * return the results. The format of the result depends on the conversion pattern. *

    *

    @@ -55,7 +56,8 @@ * See the Log4j Manual for details on the supported pattern converters. *

    */ -@Plugin(name = "PatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin public final class PatternLayout extends AbstractStringLayout { /** @@ -142,32 +144,9 @@ public static SerializerBuilder newSerializerBuilder() { return new SerializerBuilder(); } - /** - * Deprecated, use {@link #newSerializerBuilder()} instead. - * - * @param configuration - * @param replace - * @param pattern - * @param defaultPattern - * @param patternSelector - * @param alwaysWriteExceptions - * @param noConsoleNoAnsi - * @return a new Serializer. - * @deprecated Use {@link #newSerializerBuilder()} instead. - */ - @Deprecated - public static Serializer createSerializer(final Configuration configuration, final RegexReplacement replace, - final String pattern, final String defaultPattern, final PatternSelector patternSelector, - final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi) { - final SerializerBuilder builder = newSerializerBuilder(); - builder.setAlwaysWriteExceptions(alwaysWriteExceptions); - builder.setConfiguration(configuration); - builder.setDefaultPattern(defaultPattern); - builder.setNoConsoleNoAnsi(noConsoleNoAnsi); - builder.setPattern(pattern); - builder.setPatternSelector(patternSelector); - builder.setReplace(replace); - return builder.build(); + @Override + public boolean requiresLocation() { + return eventSerializer.requiresLocation(); } /** @@ -209,13 +188,13 @@ public String toSerializable(final LogEvent event) { return eventSerializer.toSerializable(event); } + public void serialize(final LogEvent event, final StringBuilder stringBuilder) { + eventSerializer.toSerializable(event, stringBuilder); + } + @Override public void encode(final LogEvent event, final ByteBufferDestination destination) { - if (!(eventSerializer instanceof Serializer2)) { - super.encode(event, destination); - return; - } - final StringBuilder text = toText((Serializer2) eventSerializer, event, getStringBuilder()); + final StringBuilder text = toText(eventSerializer, event, getStringBuilder()); final Encoder encoder = getStringBuilderEncoder(); encoder.encode(text, destination); trimToMaxSize(text); @@ -256,65 +235,59 @@ public String toString() { return patternSelector == null ? conversionPattern : patternSelector.toString(); } - /** - * Creates a pattern layout. - * - * @param pattern - * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. - * @param patternSelector - * Allows different patterns to be used based on some selection criteria. - * @param config - * The Configuration. Some Converters require access to the Interpolator. - * @param replace - * A Regex replacement String. - * @param charset - * The character set. The platform default is used if not specified. - * @param alwaysWriteExceptions - * If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens. - * @param noConsoleNoAnsi - * If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes - * @param headerPattern - * The footer to place at the top of the document, once. - * @param footerPattern - * The footer to place at the bottom of the document, once. - * @return The PatternLayout. - * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version. - */ - @PluginFactory - @Deprecated - public static PatternLayout createLayout( - @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern, - @PluginElement("PatternSelector") final PatternSelector patternSelector, - @PluginConfiguration final Configuration config, - @PluginElement("Replace") final RegexReplacement replace, - // LOG4J2-783 use platform default by default, so do not specify defaultString for charset - @PluginAttribute(value = "charset") final Charset charset, - @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions, - @PluginAttribute(value = "noConsoleNoAnsi") final boolean noConsoleNoAnsi, - @PluginAttribute("header") final String headerPattern, - @PluginAttribute("footer") final String footerPattern) { - return newBuilder() - .withPattern(pattern) - .withPatternSelector(patternSelector) - .withConfiguration(config) - .withRegexReplacement(replace) - .withCharset(charset) - .withAlwaysWriteExceptions(alwaysWriteExceptions) - .withNoConsoleNoAnsi(noConsoleNoAnsi) - .withHeader(headerPattern) - .withFooter(footerPattern) - .build(); + private interface PatternSerializer extends Serializer, Serializer2 {} + + private static final class NoFormatPatternSerializer implements PatternSerializer { + + private final LogEventPatternConverter[] converters; + + private NoFormatPatternSerializer(final PatternFormatter[] formatters) { + this.converters = new LogEventPatternConverter[formatters.length]; + for (int i = 0; i < formatters.length; i++) { + converters[i] = formatters[i].getConverter(); + } + } + + @Override + public String toSerializable(final LogEvent event) { + final StringBuilder sb = getStringBuilder(); + try { + return toSerializable(event, sb).toString(); + } finally { + trimToMaxSize(sb); + } + } + + @Override + public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) { + for (LogEventPatternConverter converter : converters) { + converter.format(event, buffer); + } + return buffer; + } + + @Override + public boolean requiresLocation() { + for (LogEventPatternConverter converter : converters) { + if (converter.requiresLocation()) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return super.toString() + "[converters=" + Arrays.toString(converters) + "]"; + } } - private static class PatternSerializer implements Serializer, Serializer2 { + private static final class PatternFormatterPatternSerializer implements PatternSerializer { private final PatternFormatter[] formatters; - private final RegexReplacement replace; - private PatternSerializer(final PatternFormatter[] formatters, final RegexReplacement replace) { - super(); + private PatternFormatterPatternSerializer(final PatternFormatter[] formatters) { this.formatters = formatters; - this.replace = replace; } @Override @@ -329,33 +302,70 @@ public String toSerializable(final LogEvent event) { @Override public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) { - final int len = formatters.length; - for (int i = 0; i < len; i++) { - formatters[i].format(event, buffer); + for (PatternFormatter formatter : formatters) { + formatter.format(event, buffer); } - if (replace != null) { // creates temporary objects - String str = buffer.toString(); - str = replace.format(str); - buffer.setLength(0); - buffer.append(str); + return buffer; + } + + @Override + public String toString() { + return super.toString() + + "[formatters=" + + Arrays.toString(formatters) + + "]"; + } + } + + private static final class PatternSerializerWithReplacement implements Serializer, Serializer2 { + + private final PatternSerializer delegate; + private final RegexReplacement replace; + + private PatternSerializerWithReplacement(final PatternSerializer delegate, final RegexReplacement replace) { + this.delegate = delegate; + this.replace = replace; + } + + @Override + public String toSerializable(final LogEvent event) { + final StringBuilder sb = getStringBuilder(); + try { + return toSerializable(event, sb).toString(); + } finally { + trimToMaxSize(sb); } + } + + @Override + public StringBuilder toSerializable(final LogEvent event, final StringBuilder buf) { + StringBuilder buffer = delegate.toSerializable(event, buf); + String str = buffer.toString(); + str = replace.format(str); + buffer.setLength(0); + buffer.append(str); return buffer; } + + @Override public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(super.toString()); - builder.append("[formatters="); - builder.append(Arrays.toString(formatters)); - builder.append(", replace="); - builder.append(replace); - builder.append("]"); - return builder.toString(); + return super.toString() + + "[delegate=" + + delegate + + ", replace=" + + replace + + "]"; + } + + @Override + public boolean requiresLocation() { + return delegate.requiresLocation(); } } - public static class SerializerBuilder implements org.apache.logging.log4j.core.util.Builder { + public static class SerializerBuilder implements org.apache.logging.log4j.plugins.util.Builder { private Configuration configuration; private RegexReplacement replace; @@ -377,7 +387,18 @@ public Serializer build() { final List list = parser.parse(pattern == null ? defaultPattern : pattern, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi); final PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]); - return new PatternSerializer(formatters, replace); + boolean hasFormattingInfo = false; + for (PatternFormatter formatter : formatters) { + FormattingInfo info = formatter.getFormattingInfo(); + if (info != null && info != FormattingInfo.getDefault()) { + hasFormattingInfo = true; + break; + } + } + PatternSerializer serializer = hasFormattingInfo + ? new PatternFormatterPatternSerializer(formatters) + : new NoFormatPatternSerializer(formatters); + return replace == null ? serializer : new PatternSerializerWithReplacement(serializer, replace); } catch (final RuntimeException ex) { throw new IllegalArgumentException("Cannot parse pattern '" + pattern + "'", ex); } @@ -427,7 +448,7 @@ public SerializerBuilder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { } - private static class PatternSelectorSerializer implements Serializer, Serializer2 { + private static final class PatternSelectorSerializer implements Serializer, Serializer2 { private final PatternSelector patternSelector; private final RegexReplacement replace; @@ -450,10 +471,8 @@ public String toSerializable(final LogEvent event) { @Override public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) { - final PatternFormatter[] formatters = patternSelector.getFormatters(event); - final int len = formatters.length; - for (int i = 0; i < len; i++) { - formatters[i].format(event, buffer); + for (PatternFormatter formatter : patternSelector.getFormatters(event)) { + formatter.format(event, buffer); } if (replace != null) { // creates temporary objects String str = buffer.toString(); @@ -464,6 +483,11 @@ public StringBuilder toSerializable(final LogEvent event, final StringBuilder bu return buffer; } + @Override + public boolean requiresLocation() { + return patternSelector.requiresLocation(); + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); @@ -498,7 +522,7 @@ public static PatternLayout createDefaultLayout() { * @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern */ public static PatternLayout createDefaultLayout(final Configuration configuration) { - return newBuilder().withConfiguration(configuration).build(); + return newBuilder().setConfiguration(configuration).build(); } /** @@ -506,7 +530,7 @@ public static PatternLayout createDefaultLayout(final Configuration configuratio * * @return a PatternLayout builder. */ - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } @@ -514,7 +538,7 @@ public static Builder newBuilder() { /** * Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this. */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; @@ -551,9 +575,9 @@ private Builder() { } private boolean useAnsiEscapeCodes() { - PropertiesUtil propertiesUtil = PropertiesUtil.getProperties(); - boolean isPlatformSupportsAnsi = !propertiesUtil.isOsWindows(); - boolean isJansiRequested = !propertiesUtil.getBooleanProperty("log4j.skipJansi", true); + final PropertyEnvironment properties = PropertiesUtil.getProperties(); + final boolean isPlatformSupportsAnsi = !properties.isOsWindows(); + final boolean isJansiRequested = !properties.getBooleanProperty(Log4jProperties.JANSI_DISABLED, true); return isPlatformSupportsAnsi || isJansiRequested; } @@ -561,7 +585,7 @@ private boolean useAnsiEscapeCodes() { * @param pattern * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN. */ - public Builder withPattern(final String pattern) { + public Builder setPattern(final String pattern) { this.pattern = pattern; return this; } @@ -570,7 +594,7 @@ public Builder withPattern(final String pattern) { * @param patternSelector * Allows different patterns to be used based on some selection criteria. */ - public Builder withPatternSelector(final PatternSelector patternSelector) { + public Builder setPatternSelector(final PatternSelector patternSelector) { this.patternSelector = patternSelector; return this; } @@ -579,7 +603,7 @@ public Builder withPatternSelector(final PatternSelector patternSelector) { * @param configuration * The Configuration. Some Converters require access to the Interpolator. */ - public Builder withConfiguration(final Configuration configuration) { + public Builder setConfiguration(final Configuration configuration) { this.configuration = configuration; return this; } @@ -588,7 +612,7 @@ public Builder withConfiguration(final Configuration configuration) { * @param regexReplacement * A Regex replacement */ - public Builder withRegexReplacement(final RegexReplacement regexReplacement) { + public Builder setRegexReplacement(final RegexReplacement regexReplacement) { this.regexReplacement = regexReplacement; return this; } @@ -597,7 +621,7 @@ public Builder withRegexReplacement(final RegexReplacement regexReplacement) { * @param charset * The character set. The platform default is used if not specified. */ - public Builder withCharset(final Charset charset) { + public Builder setCharset(final Charset charset) { // LOG4J2-783 if null, use platform default by default if (charset != null) { this.charset = charset; @@ -609,7 +633,7 @@ public Builder withCharset(final Charset charset) { * @param alwaysWriteExceptions * If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens. */ - public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { + public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { this.alwaysWriteExceptions = alwaysWriteExceptions; return this; } @@ -619,7 +643,7 @@ public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { * If {@code "true"} (default is value of system property `log4j.skipJansi`, or `true` if undefined), * do not output ANSI escape codes */ - public Builder withDisableAnsi(final boolean disableAnsi) { + public Builder setDisableAnsi(final boolean disableAnsi) { this.disableAnsi = disableAnsi; return this; } @@ -628,7 +652,7 @@ public Builder withDisableAnsi(final boolean disableAnsi) { * @param noConsoleNoAnsi * If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes */ - public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { + public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { this.noConsoleNoAnsi = noConsoleNoAnsi; return this; } @@ -637,7 +661,7 @@ public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { * @param header * The footer to place at the top of the document, once. */ - public Builder withHeader(final String header) { + public Builder setHeader(final String header) { this.header = header; return this; } @@ -646,7 +670,7 @@ public Builder withHeader(final String header) { * @param footer * The footer to place at the bottom of the document, once. */ - public Builder withFooter(final String footer) { + public Builder setFooter(final String footer) { this.footer = footer; return this; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java index 2c6803d2169..41d1d3e55c7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java @@ -17,20 +17,21 @@ package org.apache.logging.log4j.core.layout; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + import java.io.ObjectStreamException; import java.io.Serializable; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; - /** * PatternMatch configuration item. * * @since 2.4.1 implements {@link Serializable} */ -@Plugin(name = "PatternMatch", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin public final class PatternMatch { private final String key; @@ -67,12 +68,12 @@ public String toString() { return key + '=' + pattern; } - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - public static class Builder implements org.apache.logging.log4j.core.util.Builder, Serializable { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder, Serializable { private static final long serialVersionUID = 1L; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java index 78c9b494708..cd890b760d5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java @@ -27,4 +27,8 @@ public interface PatternSelector { String ELEMENT_TYPE = "patternSelector"; PatternFormatter[] getFormatters(LogEvent event); + + default boolean requiresLocation() { + return false; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java index bd5fb95ab11..f18b58025cf 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java @@ -35,12 +35,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.TlsSyslogFrame; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.internal.ExcludeChecker; +import org.apache.logging.log4j.core.layout.internal.IncludeChecker; +import org.apache.logging.log4j.core.layout.internal.ListChecker; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.net.Priority; import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; @@ -50,12 +48,17 @@ import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter; import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.core.util.ProcessIdUtil; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MessageCollectionMessage; import org.apache.logging.log4j.message.StructuredDataCollectionMessage; import org.apache.logging.log4j.message.StructuredDataId; import org.apache.logging.log4j.message.StructuredDataMessage; -import org.apache.logging.log4j.util.ProcessIdUtil; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.StringBuilders; import org.apache.logging.log4j.util.Strings; @@ -64,13 +67,14 @@ * * @see RFC 5424 */ -@Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin public final class Rfc5424Layout extends AbstractStringLayout { /** - * Not a very good default - it is the Apache Software Foundation's enterprise number. + * The default example enterprise number from RFC5424. */ - public static final int DEFAULT_ENTERPRISE_NUMBER = 18060; + public static final int DEFAULT_ENTERPRISE_NUMBER = 32473; /** * The default event id. */ @@ -84,6 +88,11 @@ public final class Rfc5424Layout extends AbstractStringLayout { */ public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = Pattern.compile("[\\\"\\]\\\\]"); + /** + * For now, avoid too restrictive OID checks to allow for easier transition + */ + public static final Pattern ENTERPRISE_ID_PATTERN = Pattern.compile("\\d+(\\.\\d+)*"); + /** * Default MDC ID: {@value} . */ @@ -98,7 +107,7 @@ public final class Rfc5424Layout extends AbstractStringLayout { private final Facility facility; private final String defaultId; - private final int enterpriseNumber; + private final String enterpriseNumber; private final boolean includeMdc; private final String mdcId; private final StructuredDataId mdcSdId; @@ -112,7 +121,6 @@ public final class Rfc5424Layout extends AbstractStringLayout { private final List mdcIncludes; private final List mdcRequired; private final ListChecker listChecker; - private final ListChecker noopChecker = new NoopChecker(); private final boolean includeNewLine; private final String escapeNewLine; private final boolean useTlsMessageFormat; @@ -124,11 +132,11 @@ public final class Rfc5424Layout extends AbstractStringLayout { private final Map fieldFormatters; private final String procId; - private Rfc5424Layout(final Configuration config, final Facility facility, final String id, final int ein, - final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId, - final String mdcPrefix, final String eventPrefix, final String appName, final String messageId, - final String excludes, final String includes, final String required, final Charset charset, - final String exceptionPattern, final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) { + private Rfc5424Layout(final Configuration config, final Facility facility, final String id, final String ein, + final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId, + final String mdcPrefix, final String eventPrefix, final String appName, final String messageId, + final String excludes, final String includes, final String required, final Charset charset, + final String exceptionPattern, final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) { super(charset); final PatternParser exceptionParser = createPatternParser(config, ThrowablePatternConverter.class); exceptionFormatters = exceptionPattern == null ? null : exceptionParser.parse(exceptionPattern); @@ -138,20 +146,20 @@ private Rfc5424Layout(final Configuration config, final Facility facility, final this.includeMdc = includeMDC; this.includeNewLine = includeNL; this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL); - this.mdcId = id == null ? DEFAULT_MDCID : id; - this.mdcSdId = new StructuredDataId(mdcId, enterpriseNumber, null, null); + this.mdcId = mdcId != null ? mdcId : id == null ? DEFAULT_MDCID : id; + this.mdcSdId = new StructuredDataId(this.mdcId, enterpriseNumber, null, null); this.mdcPrefix = mdcPrefix; this.eventPrefix = eventPrefix; this.appName = appName; this.messageId = messageId; this.useTlsMessageFormat = useTLSMessageFormat; this.localHostName = NetUtils.getLocalHostname(); - ListChecker c = null; + ListChecker checker = null; if (excludes != null) { final String[] array = excludes.split(Patterns.COMMA_SEPARATOR); if (array.length > 0) { - c = new ExcludeChecker(); mdcExcludes = new ArrayList<>(array.length); + checker = new ExcludeChecker(mdcExcludes); for (final String str : array) { mdcExcludes.add(str.trim()); } @@ -164,8 +172,8 @@ private Rfc5424Layout(final Configuration config, final Facility facility, final if (includes != null) { final String[] array = includes.split(Patterns.COMMA_SEPARATOR); if (array.length > 0) { - c = new IncludeChecker(); mdcIncludes = new ArrayList<>(array.length); + checker = new IncludeChecker(mdcIncludes); for (final String str : array) { mdcIncludes.add(str.trim()); } @@ -189,7 +197,7 @@ private Rfc5424Layout(final Configuration config, final Facility facility, final } else { mdcRequired = null; } - this.listChecker = c != null ? c : noopChecker; + this.listChecker = checker != null ? checker : ListChecker.NOOP_CHECKER; final String name = config == null ? null : config.getName(); configName = Strings.isNotEmpty(name) ? name : null; this.fieldFormatters = createFieldFormatters(loggerFields, config); @@ -259,7 +267,7 @@ public Map getContentFormat() { } /** - * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the RFC 5424 Syslog specification. + * Formats a {@link LogEvent} in conformance with the RFC 5424 Syslog specification. * * @param event The LogEvent. * @return The RFC 5424 String representation of the LogEvent. @@ -391,7 +399,7 @@ private void appendStructuredElements(final StringBuilder buffer, final LogEvent if (isStructured) { if (message instanceof MessageCollectionMessage) { - for (StructuredDataMessage data : ((StructuredDataCollectionMessage)message)) { + for (final StructuredDataMessage data : ((StructuredDataCollectionMessage)message)) { addStructuredData(sdElements, data); } } else { @@ -444,7 +452,7 @@ protected List getMdcIncludes() { } private String computeTimeStampString(final long now) { - long last; + final long last; synchronized (this) { last = lastTimestamp; if (now == lastTimestamp) { @@ -513,7 +521,7 @@ private void formatStructuredElement(final String id, final StructuredDataElemen sb.append('['); sb.append(id); if (!mdcSdId.toString().equals(id)) { - appendMap(data.getPrefix(), data.getFields(), sb, noopChecker); + appendMap(data.getPrefix(), data.getFields(), sb, ListChecker.NOOP_CHECKER); } else { appendMap(data.getPrefix(), data.getFields(), sb, checker); } @@ -527,11 +535,11 @@ private String getId(final StructuredDataId id) { } else { sb.append(id.getName()); } - int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber; - if (ein < 0) { + String ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber; + if (StructuredDataId.RESERVED.equals(ein)) { ein = enterpriseNumber; } - if (ein >= 0) { + if (!StructuredDataId.RESERVED.equals(ein)) { sb.append('@').append(ein); } return sb.toString(); @@ -566,43 +574,6 @@ private String escapeSDParams(final String value) { return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0"); } - /** - * Interface used to check keys in a Map. - */ - private interface ListChecker { - boolean check(String key); - } - - /** - * Includes only the listed keys. - */ - private class IncludeChecker implements ListChecker { - @Override - public boolean check(final String key) { - return mdcIncludes.contains(key); - } - } - - /** - * Excludes the listed keys. - */ - private class ExcludeChecker implements ListChecker { - @Override - public boolean check(final String key) { - return !mdcExcludes.contains(key); - } - } - - /** - * Does nothing. - */ - private class NoopChecker implements ListChecker { - @Override - public boolean check(final String key) { - return true; - } - } - @Override public String toString() { final StringBuilder sb = new StringBuilder(); @@ -639,6 +610,7 @@ public String toString() { * @param loggerFields Container for the KeyValuePairs containing the patterns * @param config The Configuration. Some Converters require access to the Interpolator. * @return An Rfc5424Layout. + * @deprecated Use {@link Rfc5424LayoutBuilder instead} */ @PluginFactory public static Rfc5424Layout createLayout( @@ -653,15 +625,15 @@ public static Rfc5424Layout createLayout( @PluginAttribute("eventPrefix") final String eventPrefix, @PluginAttribute(value = "newLine") final boolean newLine, @PluginAttribute("newLineEscape") final String escapeNL, - @PluginAttribute("appName") final String appName, + @PluginAttribute final String appName, @PluginAttribute("messageId") final String msgId, @PluginAttribute("mdcExcludes") final String excludes, @PluginAttribute("mdcIncludes") String includes, @PluginAttribute("mdcRequired") final String required, - @PluginAttribute("exceptionPattern") final String exceptionPattern, + @PluginAttribute final String exceptionPattern, // RFC 5425 - @PluginAttribute(value = "useTlsMessageFormat") final boolean useTlsMessageFormat, - @PluginElement("LoggerFields") final LoggerFields[] loggerFields, + @PluginAttribute final boolean useTlsMessageFormat, + @PluginElement final LoggerFields[] loggerFields, @PluginConfiguration final Configuration config) { // @formatter:on if (includes != null && excludes != null) { @@ -669,11 +641,142 @@ public static Rfc5424Layout createLayout( includes = null; } - return new Rfc5424Layout(config, facility, id, enterpriseNumber, includeMDC, newLine, escapeNL, mdcId, + return new Rfc5424Layout(config, facility, id, String.valueOf(enterpriseNumber), includeMDC, newLine, escapeNL, mdcId, mdcPrefix, eventPrefix, appName, msgId, excludes, includes, required, StandardCharsets.UTF_8, exceptionPattern, useTlsMessageFormat, loggerFields); } + public static class Rfc5424LayoutBuilder { + private Configuration config; + private Facility facility = Facility.LOCAL0; + private String id; + private String ein = String.valueOf(DEFAULT_ENTERPRISE_NUMBER); + private boolean includeMDC = true; + private boolean includeNL; + private String escapeNL; + private String mdcId = DEFAULT_MDCID; + private String mdcPrefix; + private String eventPrefix; + private String appName; + private String messageId; + private String excludes; + private String includes; + private String required; + private Charset charset; + private String exceptionPattern; + private boolean useTLSMessageFormat; + private LoggerFields[] loggerFields; + + public Rfc5424LayoutBuilder setConfig(Configuration config) { + this.config = config; + return this; + } + + public Rfc5424LayoutBuilder setFacility(Facility facility) { + this.facility = facility; + return this; + } + + public Rfc5424LayoutBuilder setId(String id) { + this.id = id; + return this; + } + + public Rfc5424LayoutBuilder setEin(String ein) { + this.ein = ein; + return this; + } + + public Rfc5424LayoutBuilder setIncludeMDC(boolean includeMDC) { + this.includeMDC = includeMDC; + return this; + } + + public Rfc5424LayoutBuilder setIncludeNL(boolean includeNL) { + this.includeNL = includeNL; + return this; + } + + public Rfc5424LayoutBuilder setEscapeNL(String escapeNL) { + this.escapeNL = escapeNL; + return this; + } + + public Rfc5424LayoutBuilder setMdcId(String mdcId) { + this.mdcId = mdcId; + return this; + } + + public Rfc5424LayoutBuilder setMdcPrefix(String mdcPrefix) { + this.mdcPrefix = mdcPrefix; + return this; + } + + public Rfc5424LayoutBuilder setEventPrefix(String eventPrefix) { + this.eventPrefix = eventPrefix; + return this; + } + + public Rfc5424LayoutBuilder setAppName(String appName) { + this.appName = appName; + return this; + } + + public Rfc5424LayoutBuilder setMessageId(String messageId) { + this.messageId = messageId; + return this; + } + + public Rfc5424LayoutBuilder setExcludes(String excludes) { + this.excludes = excludes; + return this; + } + + public Rfc5424LayoutBuilder setIncludes(String includes) { + this.includes = includes; + return this; + } + + public Rfc5424LayoutBuilder setRequired(String required) { + this.required = required; + return this; + } + + public Rfc5424LayoutBuilder setCharset(Charset charset) { + this.charset = charset; + return this; + } + + public Rfc5424LayoutBuilder setExceptionPattern(String exceptionPattern) { + this.exceptionPattern = exceptionPattern; + return this; + } + + public Rfc5424LayoutBuilder setUseTLSMessageFormat(boolean useTLSMessageFormat) { + this.useTLSMessageFormat = useTLSMessageFormat; + return this; + } + + public Rfc5424LayoutBuilder setLoggerFields(LoggerFields[] loggerFields) { + this.loggerFields = loggerFields; + return this; + } + + public Rfc5424Layout build() { + if (includes != null && excludes != null) { + LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored"); + includes = null; + } + + if (ein != null && !ENTERPRISE_ID_PATTERN.matcher(ein).matches()) { + LOGGER.warn(String.format("provided EID %s is not in valid format!", ein)); + return null; + } + + return new Rfc5424Layout(config, facility, id, ein, includeMDC, includeNL, escapeNL, mdcId, mdcPrefix, eventPrefix, appName, messageId, excludes, includes, required, charset, exceptionPattern, useTLSMessageFormat, loggerFields); + } + } + private class FieldFormatter { private final Map> delegateMap; @@ -741,4 +844,21 @@ String getPrefix() { public Facility getFacility() { return facility; } + + public String getDefaultId() { + return defaultId; + } + + public String getEnterpriseNumber() { + return enterpriseNumber; + } + + public boolean isIncludeMdc() { + return includeMdc; + } + + public String getMdcId() { + return mdcId; + } + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java deleted file mode 100644 index 35c6f4b2c1e..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.script.SimpleBindings; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.pattern.PatternFormatter; -import org.apache.logging.log4j.core.pattern.PatternParser; -import org.apache.logging.log4j.core.script.AbstractScript; -import org.apache.logging.log4j.core.script.ScriptRef; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * Selects the pattern to use based on the Marker in the LogEvent. - */ -@Plugin(name = "ScriptPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true) -public class ScriptPatternSelector implements PatternSelector { - - /** - * Custom ScriptPatternSelector builder. Use the {@link #newBuilder() builder factory method} to create this. - */ - public static class Builder implements org.apache.logging.log4j.core.util.Builder { - - @PluginElement("Script") - private AbstractScript script; - - @PluginElement("PatternMatch") - private PatternMatch[] properties; - - @PluginBuilderAttribute("defaultPattern") - private String defaultPattern; - - @PluginBuilderAttribute("alwaysWriteExceptions") - private boolean alwaysWriteExceptions = true; - - @PluginBuilderAttribute("disableAnsi") - private boolean disableAnsi; - - @PluginBuilderAttribute("noConsoleNoAnsi") - private boolean noConsoleNoAnsi; - - @PluginConfiguration - private Configuration configuration; - - private Builder() { - // nothing - } - - @Override - public ScriptPatternSelector build() { - if (script == null) { - LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter"); - return null; - } - if (script instanceof ScriptRef) { - if (configuration.getScriptManager().getScript(script.getName()) == null) { - LOGGER.error("No script with name {} has been declared.", script.getName()); - return null; - } - } - if (defaultPattern == null) { - defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; - } - if (properties == null || properties.length == 0) { - LOGGER.warn("No marker patterns were provided"); - return null; - } - return new ScriptPatternSelector(script, properties, defaultPattern, alwaysWriteExceptions, disableAnsi, - noConsoleNoAnsi, configuration); - } - - public Builder setScript(final AbstractScript script) { - this.script = script; - return this; - } - - public Builder setProperties(final PatternMatch[] properties) { - this.properties = properties; - return this; - } - - public Builder setDefaultPattern(final String defaultPattern) { - this.defaultPattern = defaultPattern; - return this; - } - - public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { - this.alwaysWriteExceptions = alwaysWriteExceptions; - return this; - } - - public Builder setDisableAnsi(final boolean disableAnsi) { - this.disableAnsi = disableAnsi; - return this; - } - - public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { - this.noConsoleNoAnsi = noConsoleNoAnsi; - return this; - } - - public Builder setConfiguration(final Configuration config) { - this.configuration = config; - return this; - } - } - - private final Map formatterMap = new HashMap<>(); - - private final Map patternMap = new HashMap<>(); - - private final PatternFormatter[] defaultFormatters; - - private final String defaultPattern; - - private static Logger LOGGER = StatusLogger.getLogger(); - private final AbstractScript script; - private final Configuration configuration; - - - /** - * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version. - */ - @Deprecated - public ScriptPatternSelector(final AbstractScript script, final PatternMatch[] properties, final String defaultPattern, - final boolean alwaysWriteExceptions, final boolean disableAnsi, - final boolean noConsoleNoAnsi, final Configuration config) { - this.script = script; - this.configuration = config; - if (!(script instanceof ScriptRef)) { - config.getScriptManager().addScript(script); - } - final PatternParser parser = PatternLayout.createPatternParser(config); - for (final PatternMatch property : properties) { - try { - final List list = parser.parse(property.getPattern(), alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi); - formatterMap.put(property.getKey(), list.toArray(new PatternFormatter[list.size()])); - patternMap.put(property.getKey(), property.getPattern()); - } catch (final RuntimeException ex) { - throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex); - } - } - try { - final List list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi); - defaultFormatters = list.toArray(new PatternFormatter[list.size()]); - this.defaultPattern = defaultPattern; - } catch (final RuntimeException ex) { - throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex); - } - } - - @Override - public PatternFormatter[] getFormatters(final LogEvent event) { - final SimpleBindings bindings = new SimpleBindings(); - bindings.putAll(configuration.getProperties()); - bindings.put("substitutor", configuration.getStrSubstitutor()); - bindings.put("logEvent", event); - final Object object = configuration.getScriptManager().execute(script.getName(), bindings); - if (object == null) { - return defaultFormatters; - } - final PatternFormatter[] patternFormatter = formatterMap.get(object.toString()); - - return patternFormatter == null ? defaultFormatters : patternFormatter; - } - - - /** - * Creates a builder for a custom ScriptPatternSelector. - * - * @return a ScriptPatternSelector builder. - */ - @PluginBuilderFactory - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Deprecated, use {@link #newBuilder()} instead. - * - * @param script - * @param properties - * @param defaultPattern - * @param alwaysWriteExceptions - * @param noConsoleNoAnsi - * @param configuration - * @return a new ScriptPatternSelector - * @deprecated Use {@link #newBuilder()} instead. - */ - @Deprecated - public static ScriptPatternSelector createSelector( - final AbstractScript script, - final PatternMatch[] properties, - final String defaultPattern, - final boolean alwaysWriteExceptions, - final boolean noConsoleNoAnsi, - final Configuration configuration) { - final Builder builder = newBuilder(); - builder.setScript(script); - builder.setProperties(properties); - builder.setDefaultPattern(defaultPattern); - builder.setAlwaysWriteExceptions(alwaysWriteExceptions); - builder.setNoConsoleNoAnsi(noConsoleNoAnsi); - builder.setConfiguration(configuration); - return builder.build(); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - boolean first = true; - for (final Map.Entry entry : patternMap.entrySet()) { - if (!first) { - sb.append(", "); - } - sb.append("key=\"").append(entry.getKey()).append("\", pattern=\"").append(entry.getValue()).append("\""); - first = false; - } - if (!first) { - sb.append(", "); - } - sb.append("default=\"").append(defaultPattern).append("\""); - return sb.toString(); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java deleted file mode 100644 index a77e8192ebe..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.io.OutputStream; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - -/** - * Formats a {@link LogEvent} in its Java serialized form. - * - * @deprecated Java Serialization has inherent security weaknesses, see https://www.owasp.org/index.php/Deserialization_of_untrusted_data . - * Using this layout is no longer recommended. An alternative layout containing the same information is - * {@link JsonLayout} when configured with properties="true". Deprecated since 2.9. - */ -@Deprecated -@Plugin(name = "SerializedLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) -public final class SerializedLayout extends AbstractLayout { - - private static byte[] serializedHeader; - - static { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - new ObjectOutputStream(baos).close(); - serializedHeader = baos.toByteArray(); - } catch (final Exception ex) { - LOGGER.error("Unable to generate Object stream header", ex); - } - } - - private SerializedLayout() { - super(null, null, null); - LOGGER.warn("SerializedLayout is deprecated due to the inherent security weakness in Java Serialization, see https://www.owasp.org/index.php/Deserialization_of_untrusted_data Consider using another layout, e.g. JsonLayout"); - } - - /** - * Formats a {@link org.apache.logging.log4j.core.LogEvent} as a serialized byte array of the LogEvent object. - * - * @param event The LogEvent. - * @return the formatted LogEvent. - */ - @Override - public byte[] toByteArray(final LogEvent event) { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (final ObjectOutputStream oos = new PrivateObjectOutputStream(baos)) { - oos.writeObject(event); - oos.reset(); - } catch (final IOException ioe) { - LOGGER.error("Serialization of LogEvent failed.", ioe); - } - return baos.toByteArray(); - } - - /** - * Returns the LogEvent. - * - * @param event The Logging Event. - * @return The LogEvent. - */ - @Override - public LogEvent toSerializable(final LogEvent event) { - return event; - } - - /** - * Creates a SerializedLayout. - * @return A SerializedLayout. - */ - @Deprecated - @PluginFactory - public static SerializedLayout createLayout() { - return new SerializedLayout(); - } - - @Override - public byte[] getHeader() { - return serializedHeader; - } - - /** - * SerializedLayout returns a binary stream. - * @return The content type. - */ - @Override - public String getContentType() { - return "application/octet-stream"; - } - - /** - * The stream header will be written in the Manager so skip it here. - */ - private class PrivateObjectOutputStream extends ObjectOutputStream { - - public PrivateObjectOutputStream(final OutputStream os) throws IOException { - super(os); - } - - @Override - protected void writeStreamHeader() { - // do nothing - } - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java index fb023938d10..9af331634f9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java @@ -31,7 +31,6 @@ */ public class StringBuilderEncoder implements Encoder { - private static final int DEFAULT_BYTE_BUFFER_SIZE = 8 * 1024; /** * This ThreadLocal uses raw and inconvenient Object[] to store three heterogeneous objects (CharEncoder, CharBuffer * and ByteBuffer) instead of a custom class, because it needs to contain JDK classes, no custom (Log4j) classes. @@ -49,7 +48,7 @@ public class StringBuilderEncoder implements Encoder { private final int byteBufferSize; public StringBuilderEncoder(final Charset charset) { - this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE, DEFAULT_BYTE_BUFFER_SIZE); + this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE, Constants.ENCODER_BYTE_BUFFER_SIZE); } public StringBuilderEncoder(final Charset charset, final int charBufferSize, final int byteBufferSize) { @@ -67,7 +66,7 @@ public void encode(final StringBuilder source, final ByteBufferDestination desti final ByteBuffer byteBuffer = (ByteBuffer) threadLocalState[2]; TextEncoderHelper.encodeText(charsetEncoder, charBuffer, byteBuffer, source, destination); } catch (final Exception ex) { - logEncodeTextException(ex, source, destination); + logEncodeTextException(ex, source); TextEncoderHelper.encodeTextFallBack(charset, source, destination); } } @@ -90,8 +89,8 @@ private Object[] getThreadLocalState() { return threadLocalState; } - private void logEncodeTextException(final Exception ex, final StringBuilder text, - final ByteBufferDestination destination) { + private static void logEncodeTextException(final Exception ex, final StringBuilder text) { StatusLogger.getLogger().error("Recovering from StringBuilderEncoder.encode('{}') error: {}", text, ex, ex); } + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java index 97fcc3b0cef..fb481233b62 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java @@ -28,35 +28,36 @@ import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; import org.apache.logging.log4j.core.net.Facility; import org.apache.logging.log4j.core.net.Priority; import org.apache.logging.log4j.core.util.NetUtils; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.util.Chars; /** * Formats a log event as a BSD Log record. */ -@Plugin(name = "SyslogLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin public final class SyslogLayout extends AbstractStringLayout { /** * Builds a SyslogLayout. *

    The main arguments are

    - *
      + *
        *
      • facility: The Facility is used to try to classify the message.
      • *
      • includeNewLine: If true a newline will be appended to the result.
      • *
      • escapeNL: Pattern to use for replacing newlines.
      • *
      • charset: The character set.
      • - *
      + *
    * @param the builder type */ public static class Builder> extends AbstractStringLayout.Builder - implements org.apache.logging.log4j.core.util.Builder { - + implements org.apache.logging.log4j.plugins.util.Builder { + public Builder() { super(); setCharset(StandardCharsets.UTF_8); @@ -104,8 +105,8 @@ public B setEscapeNL(final String escapeNL) { } } - - @PluginBuilderFactory + + @PluginFactory public static > B newBuilder() { return new Builder().asBuilder(); } @@ -123,7 +124,7 @@ public static > B newBuilder() { * Date format used if header = true. */ private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm:ss", Locale.ENGLISH); - + /** * Host name used to identify messages from this appender. */ @@ -137,7 +138,7 @@ protected SyslogLayout(final Facility facility, final boolean includeNL, final S } /** - * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the BSD Log record format. + * Formats a {@link LogEvent} in conformance with the BSD Log record format. * * @param event The LogEvent * @return the event formatted as a String. @@ -184,7 +185,7 @@ private synchronized void addDate(final long timestamp, final StringBuilder buf) *
  • Key: "formatType" Value: "logfilepatternreceiver" (format uses the keywords supported by * LogFilePatternReceiver)
  • * - * + * * @return Map of content format keys supporting SyslogLayout */ @Override @@ -197,25 +198,9 @@ public Map getContentFormat() { return result; } - /** - * Creates a SyslogLayout. - * - * @param facility The Facility is used to try to classify the message. - * @param includeNewLine If true a newline will be appended to the result. - * @param escapeNL Pattern to use for replacing newlines. - * @param charset The character set. - * @return A SyslogLayout. - * @deprecated Use {@link #newBuilder()}. - */ - @Deprecated - public static SyslogLayout createLayout(final Facility facility, final boolean includeNewLine, - final String escapeNL, final Charset charset) { - return new SyslogLayout(facility, includeNewLine, escapeNL, charset); - } - /** * Gets the facility. - * + * * @return the facility */ public Facility getFacility() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java index 327386ff690..4191f571212 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/TextEncoderHelper.java @@ -48,11 +48,9 @@ static void encodeTextFallBack(final Charset charset, final StringBuilder text, * @param byteBuf thread-local buffer to temporarily hold converted bytes before copying them to the destination * @param text the text to convert and write to the destination * @param destination the destination to write the bytes to - * @throws CharacterCodingException if conversion failed */ - static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer byteBuf, - final StringBuilder text, final ByteBufferDestination destination) - throws CharacterCodingException { + public static void encodeText(final CharsetEncoder charsetEncoder, final CharBuffer charBuf, final ByteBuffer byteBuf, + final StringBuilder text, final ByteBufferDestination destination) { charsetEncoder.reset(); if (text.length() > charBuf.capacity()) { encodeChunkedText(charsetEncoder, charBuf, byteBuf, text, destination); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java deleted file mode 100644 index b09add7a59f..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.jackson.XmlConstants; -import org.apache.logging.log4j.core.util.KeyValuePair; - -/** - * Appends a series of {@code event} elements as defined in the log4j.dtd. - * - *

    Complete well-formed XML vs. fragment XML

    - *

    - * If you configure {@code complete="true"}, the appender outputs a well-formed XML document where the default namespace - * is the log4j namespace {@value XmlConstants#XML_NAMESPACE}. By default, with {@code complete="false"}, you should - * include the output as an external entity in a separate file to form a well-formed XML document. - *

    - *

    - * If {@code complete="false"}, the appender does not write the XML processing instruction and the root element. - *

    - *

    Encoding

    - *

    - * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise - * events containing non-ASCII characters could result in corrupted log files. - *

    - *

    Pretty vs. compact XML

    - *

    - * By default, the XML layout is not compact (compact = not "pretty") with {@code compact="false"}, which means the - * appender uses end-of-line characters and indents lines to format the XML. If {@code compact="true"}, then no - * end-of-line or indentation is used. Message content may contain, of course, end-of-lines. - *

    - *

    Additional Fields

    - *

    - * This property allows addition of custom fields into generated JSON. - * {@code } inserts {@code bar} directly - * into XML output. Supports Lookup expressions. - *

    - */ -@Plugin(name = "XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) -public final class XmlLayout extends AbstractJacksonLayout { - - private static final String ROOT_TAG = "Events"; - - public static class Builder> extends AbstractJacksonLayout.Builder - implements org.apache.logging.log4j.core.util.Builder { - - public Builder() { - super(); - setCharset(StandardCharsets.UTF_8); - } - - @Override - public XmlLayout build() { - return new XmlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), - isCompact(), getCharset(), isIncludeStacktrace(), isStacktraceAsString(), - isIncludeNullDelimiter(), getAdditionalFields()); - } - } - - /** - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - protected XmlLayout(final boolean locationInfo, final boolean properties, final boolean complete, - final boolean compact, final Charset charset, final boolean includeStacktrace) { - this(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, false, null); - } - - private XmlLayout(final Configuration config, final boolean locationInfo, final boolean properties, - final boolean complete, final boolean compact, final Charset charset, - final boolean includeStacktrace, final boolean stacktraceAsString, - final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields) { - super(config, new JacksonFactory.XML(includeStacktrace, stacktraceAsString).newWriter( - locationInfo, properties, compact), - charset, compact, complete, false, null, null, includeNullDelimiter, - additionalFields); - } - - /** - * Returns appropriate XML headers. - *
      - *
    1. XML processing instruction
    2. - *
    3. XML root element
    4. - *
    - * - * @return a byte array containing the header. - */ - @Override - public byte[] getHeader() { - if (!complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - buf.append(""); - buf.append(this.eol); - // Make the log4j namespace the default namespace, no need to use more space with a namespace prefix. - buf.append('<'); - buf.append(ROOT_TAG); - buf.append(" xmlns=\"" + XmlConstants.XML_NAMESPACE + "\">"); - buf.append(this.eol); - return buf.toString().getBytes(this.getCharset()); - } - - /** - * Returns appropriate XML footer. - * - * @return a byte array containing the footer, closing the XML root element. - */ - @Override - public byte[] getFooter() { - if (!complete) { - return null; - } - return getBytes("' + this.eol); - } - - /** - * Gets this XmlLayout's content format. Specified by: - *
      - *
    • Key: "dtd" Value: "log4j-events.dtd"
    • - *
    • Key: "version" Value: "2.0"
    • - *
    - * - * @return Map of content format keys supporting XmlLayout - */ - @Override - public Map getContentFormat() { - final Map result = new HashMap<>(); - // result.put("dtd", "log4j-events.dtd"); - result.put("xsd", "log4j-events.xsd"); - result.put("version", "2.0"); - return result; - } - - /** - * @return The content type. - */ - @Override - public String getContentType() { - return "text/xml; charset=" + this.getCharset(); - } - - /** - * Creates an XML Layout. - * - * @param locationInfo If "true", includes the location information in the generated XML. - * @param properties If "true", includes the thread context map in the generated XML. - * @param complete If "true", includes the XML header and footer, defaults to "false". - * @param compact If "true", does not use end-of-lines and indentation, defaults to "false". - * @param charset The character set to use, if {@code null}, uses "UTF-8". - * @param includeStacktrace - * If "true", includes the stacktrace of any Throwable in the generated XML, defaults to "true". - * @return An XML Layout. - * - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - public static XmlLayout createLayout( - final boolean locationInfo, - final boolean properties, - final boolean complete, - final boolean compact, - final Charset charset, - final boolean includeStacktrace) { - return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, - false, null); - } - - @PluginBuilderFactory - public static > B newBuilder() { - return new Builder().asBuilder(); - } - - /** - * Creates an XML Layout using the default settings. - * - * @return an XML Layout. - */ - public static XmlLayout createDefaultLayout() { - return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false, null); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java deleted file mode 100644 index 1d8576f2ea9..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.util.Strings; - -/** - * Appends a series of YAML events as strings serialized as bytes. - * - *

    Encoding

    - *

    - * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise - * events containing non ASCII characters could result in corrupted log files. - *

    - *

    Additional Fields

    - *

    - * This property allows addition of custom fields into generated JSON. - * {@code } inserts {@code foo: "bar"} directly - * into YAML output. Supports Lookup expressions. - *

    - */ -@Plugin(name = "YamlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) -public final class YamlLayout extends AbstractJacksonLayout { - - private static final String DEFAULT_FOOTER = Strings.EMPTY; - - private static final String DEFAULT_HEADER = Strings.EMPTY; - - static final String CONTENT_TYPE = "application/yaml"; - - public static class Builder> extends AbstractJacksonLayout.Builder - implements org.apache.logging.log4j.core.util.Builder { - - public Builder() { - super(); - setCharset(StandardCharsets.UTF_8); - } - - @Override - public YamlLayout build() { - final String headerPattern = toStringOrNull(getHeader()); - final String footerPattern = toStringOrNull(getFooter()); - return new YamlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), - isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), - isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), - getAdditionalFields()); - } - } - - /** - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties, - final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, - final String footerPattern, final Charset charset, final boolean includeStacktrace) { - super(config, new JacksonFactory.YAML(includeStacktrace, false).newWriter(locationInfo, properties, compact), - charset, compact, complete, eventEol, - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), - false, null); - } - - private YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties, - final boolean complete, final boolean compact, final boolean eventEol, - final String headerPattern, final String footerPattern, final Charset charset, - final boolean includeStacktrace, final boolean stacktraceAsString, - final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields) { - super(config, new JacksonFactory.YAML(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties, compact), - charset, compact, complete, eventEol, - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), - includeNullDelimiter, - additionalFields); - } - - /** - * Returns appropriate YAML header. - * - * @return a byte array containing the header, opening the YAML array. - */ - @Override - public byte[] getHeader() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - final String str = serializeToString(getHeaderSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - /** - * Returns appropriate YAML footer. - * - * @return a byte array containing the footer, closing the YAML array. - */ - @Override - public byte[] getFooter() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - buf.append(this.eol); - final String str = serializeToString(getFooterSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - @Override - public Map getContentFormat() { - final Map result = new HashMap<>(); - result.put("version", "2.0"); - return result; - } - - /** - * @return The content type. - */ - @Override - public String getContentType() { - return CONTENT_TYPE + "; charset=" + this.getCharset(); - } - - /** - * Creates a YAML Layout. - * - * @param config - * The plugin configuration. - * @param locationInfo - * If "true", includes the location information in the generated YAML. - * @param properties - * If "true", includes the thread context map in the generated YAML. - * @param headerPattern - * The header pattern, defaults to {@code ""} if null. - * @param footerPattern - * The header pattern, defaults to {@code ""} if null. - * @param charset - * The character set to use, if {@code null}, uses "UTF-8". - * @param includeStacktrace - * If "true", includes the stacktrace of any Throwable in the generated YAML, defaults to "true". - * @return A YAML Layout. - * - * @deprecated Use {@link #newBuilder()} instead - */ - @Deprecated - public static AbstractJacksonLayout createLayout( - final Configuration config, - final boolean locationInfo, - final boolean properties, - final String headerPattern, - final String footerPattern, - final Charset charset, - final boolean includeStacktrace) { - return new YamlLayout(config, locationInfo, properties, false, false, true, headerPattern, footerPattern, - charset, includeStacktrace, false, false, null); - } - - @PluginBuilderFactory - public static > B newBuilder() { - return new Builder().asBuilder(); - } - - /** - * Creates a YAML Layout using the default settings. Useful for testing. - * - * @return A YAML Layout. - */ - public static AbstractJacksonLayout createDefaultLayout() { - return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER, - DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null); - } -} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ExcludeChecker.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ExcludeChecker.java new file mode 100644 index 00000000000..2841dd23e7c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ExcludeChecker.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout.internal; + +import java.util.List; + +/** + * Excludes the listed keys. + */ +public class ExcludeChecker implements ListChecker { + private final List list; + + public ExcludeChecker(final List list) { + this.list = list; + } + + @Override + public boolean check(final String key) { + return !list.contains(key); + } + + @Override + public String toString() { + return "ThreadContextExcludes=" + list.toString(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/IncludeChecker.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/IncludeChecker.java new file mode 100644 index 00000000000..c87b807d837 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/IncludeChecker.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout.internal; + +import java.util.List; + +/** + * Includes only the listed keys. + */ +public class IncludeChecker implements ListChecker { + private final List list; + + public IncludeChecker(final List list) { + this.list = list; + } + @Override + public boolean check(final String key) { + return list.contains(key); + } + + @Override + public String toString() { + return "ThreadContextIncludes=" + list.toString(); + } +} + diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ListChecker.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ListChecker.java new file mode 100644 index 00000000000..f815f445cd1 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ListChecker.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.layout.internal; + +/** + * Class Description goes here. + */ + +public interface ListChecker { + + NoopChecker NOOP_CHECKER = new NoopChecker(); + + boolean check(final String key); + + /** + * Does nothing. + */ + class NoopChecker implements ListChecker { + @Override + public boolean check(final String key) { + return true; + } + + @Override + public String toString() { + return ""; + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java index 581c139f812..fac886cc487 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java @@ -16,9 +16,8 @@ */ /** * Log4j 2 Layout support. {@link org.apache.logging.log4j.core.Layout} plugins should use the - * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#category() plugin category} - * {@link org.apache.logging.log4j.core.config.Node#CATEGORY Core} and the - * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() element type} + * {@link org.apache.logging.log4j.plugins.Configurable} plugin namespace annotation with an + * {@linkplain org.apache.logging.log4j.plugins.Configurable#elementType() element type} of * {@link org.apache.logging.log4j.core.Layout#ELEMENT_TYPE layout}. */ package org.apache.logging.log4j.core.layout; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/AbstractLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/AbstractLookup.java index 1dba4997884..c76561c30ad 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/AbstractLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/AbstractLookup.java @@ -18,14 +18,14 @@ /** * A default lookup for others to extend. - * + * * @since 2.1 */ public abstract class AbstractLookup implements StrLookup { /** * Calls {@code lookup(null, key)} in the super class. - * + * * @see StrLookup#lookup(LogEvent, String) */ @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Base64StrLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Base64StrLookup.java new file mode 100644 index 00000000000..1c254a208ba --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Base64StrLookup.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.logging.log4j.core.lookup; + +import java.util.Base64; + +import org.apache.logging.log4j.core.LogEvent; + +/** + * Decodes Base64 strings. + * + * @since 3.0.0 + */ +public class Base64StrLookup extends AbstractLookup { + + @Override + public String lookup(final LogEvent event, final String key) { + return new String(Base64.getDecoder().decode(key)); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java new file mode 100644 index 00000000000..1287721b545 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ConfigurationStrSubstitutor.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import java.util.Map; +import java.util.Properties; + +/** + * {@link RuntimeStrSubstitutor} is a {@link StrSubstitutor} which only supports recursive evaluation of lookups. + * This can be dangerous when combined with user-provided inputs, and should only be used on data directly from + * a configuration. + */ +public final class ConfigurationStrSubstitutor extends StrSubstitutor { + + public ConfigurationStrSubstitutor() { + } + + public ConfigurationStrSubstitutor(final Map valueMap) { + super(valueMap); + } + + public ConfigurationStrSubstitutor(final Properties properties) { + super(properties); + } + + public ConfigurationStrSubstitutor(final StrLookup lookup) { + super(lookup); + } + + public ConfigurationStrSubstitutor(final StrSubstitutor other) { + super(other); + } + + @Override + boolean isRecursiveEvaluationAllowed() { + return true; + } + + @Override + void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) { + throw new UnsupportedOperationException( + "recursiveEvaluationAllowed cannot be modified within ConfigurationStrSubstitutor"); + } + + @Override + public String toString() { + return "ConfigurationStrSubstitutor{" + super.toString() + "}"; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java index 220d17afcae..78b40c1ea24 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java @@ -17,10 +17,10 @@ package org.apache.logging.log4j.core.lookup; import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.ContextDataInjector; +import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.ReadOnlyStringMap; /** @@ -28,7 +28,8 @@ * {@linkplain ContextDataInjectorFactory configure} a custom {@link ContextDataInjector} which obtains context data * from some other source. */ -@Plugin(name = "ctx", category = StrLookup.CATEGORY) +@Lookup +@Plugin("ctx") public class ContextMapLookup implements StrLookup { private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); @@ -55,6 +56,6 @@ private ReadOnlyStringMap currentContextData() { */ @Override public String lookup(final LogEvent event, final String key) { - return event.getContextData().getValue(key); + return event == null ? null : event.getContextData().getValue(key); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java index 3e630b0e2fc..dd94ac07fcc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java @@ -16,30 +16,32 @@ */ package org.apache.logging.log4j.core.lookup; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.status.StatusLogger; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + /** - * Formats the current date or the date in the LogEvent. The "key" is used as the format String. + * Formats the current date or the date in the LogEvent. The "key" is used as the format String, + * following the java.text.SimpleDateFormat date and time pattern strings. */ -@Plugin(name = "date", category = StrLookup.CATEGORY) +@Lookup +@Plugin("date") public class DateLookup implements StrLookup { private static final Logger LOGGER = StatusLogger.getLogger(); private static final Marker LOOKUP = MarkerManager.getMarker("LOOKUP"); /** - * Looks up the value of the environment variable. + * Looks up the current date. * @param key the format to use. If null, the default DateFormat will be used. - * @return The value of the environment variable. + * @return The formatted current date, never null. */ @Override public String lookup(final String key) { @@ -47,14 +49,14 @@ public String lookup(final String key) { } /** - * Looks up the value of the environment variable. - * @param event The current LogEvent (is ignored by this StrLookup). + * Looks up d the current date or the date in the LogEvent. + * @param event The LogEvent for which the date is returned. If null, current date is returned. * @param key the format to use. If null, the default DateFormat will be used. - * @return The value of the environment variable. + * @return The formatted date, never null. */ @Override public String lookup(final LogEvent event, final String key) { - return formatDate(event.getTimeMillis(), key); + return event == null ? lookup(key) : formatDate(event.getTimeMillis(), key); } private String formatDate(final long date, final String format) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java index 1d3b8115e3b..f436c0da3ef 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java @@ -17,22 +17,28 @@ package org.apache.logging.log4j.core.lookup; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Plugin; /** * Looks up keys from environment variables. */ -@Plugin(name = "env", category = StrLookup.CATEGORY) +@Lookup +@Plugin("env") public class EnvironmentLookup extends AbstractLookup { /** - * Looks up the value of the environment variable. - * @param event The current LogEvent (is ignored by this StrLookup). - * @param key the key to be looked up, may be null - * @return The value of the environment variable. + * Looks up the value of the given environment variable. + * + * @param event + * The current LogEvent (ignored by this StrLookup). + * @param key + * the key to look up, may be null + * @return the string value of the variable, or null if the variable is not defined in the system + * environment */ @Override public String lookup(final LogEvent event, final String key) { - return System.getenv(key); + // getenv throws NullPointerException if name is null + return key != null ? System.getenv(key) : null; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java new file mode 100644 index 00000000000..c5c738e45c4 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.Plugin; + +/** + * Looks up values from the log event. + */ +@Lookup +@Plugin("event") +public class EventLookup extends AbstractLookup { + + /** + * Looks up the value from the logging event. + * @param event The current LogEvent. + * @param key the key to be looked up. + * @return The value of the specified log event field. + */ + @Override + public String lookup(final LogEvent event, final String key) { + if (event == null) { + return null; + } + switch (key) { + case "Marker": { + return event.getMarker() != null ? event.getMarker().getName() : null; + } + case "ThreadName": { + return event.getThreadName(); + } + case "Level": { + return event.getLevel().toString(); + } + case "ThreadId": { + return Long.toString(event.getThreadId()); + } + case "Timestamp": { + return Long.toString(event.getTimeMillis()); + } + case "Exception": { + if (event.getThrown() != null) { + return event.getThrown().getClass().getSimpleName(); + } + if (event.getThrownProxy() != null) { + return event.getThrownProxy().getName(); + } + return null; + } + case "Logger": { + return event.getLoggerName(); + } + case "Message": { + return event.getMessage().getFormattedMessage(); + } + default: { + return null; + } + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java index 122515c26ad..13540b1aec0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java @@ -16,120 +16,106 @@ */ package org.apache.logging.log4j.core.lookup; -import java.util.HashMap; +import java.lang.ref.WeakReference; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.ConfigurationAware; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; -import org.apache.logging.log4j.core.util.Loader; -import org.apache.logging.log4j.core.util.ReflectionUtil; +import org.apache.logging.log4j.core.config.LoggerContextAware; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.Keys; import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Constants; /** - * Proxies all the other {@link StrLookup}s. + * Proxies other {@link StrLookup}s using a keys within ${} markers. */ -public class Interpolator extends AbstractConfigurationAwareLookup { +public class Interpolator extends AbstractConfigurationAwareLookup implements LoggerContextAware { + + /** Constant for the prefix separator. */ + public static final char PREFIX_SEPARATOR = ':'; private static final String LOOKUP_KEY_WEB = "web"; + private static final String LOOKUP_KEY_DOCKER = "docker"; + + private static final String LOOKUP_KEY_KUBERNETES = "kubernetes"; + + private static final String LOOKUP_KEY_SPRING = "spring"; + private static final String LOOKUP_KEY_JNDI = "jndi"; private static final String LOOKUP_KEY_JVMRUNARGS = "jvmrunargs"; private static final Logger LOGGER = StatusLogger.getLogger(); - /** Constant for the prefix separator. */ - private static final char PREFIX_SEPARATOR = ':'; - - private final Map strLookupMap = new HashMap<>(); + private final Map> strLookups = new ConcurrentHashMap<>(); private final StrLookup defaultLookup; - public Interpolator(final StrLookup defaultLookup) { - this(defaultLookup, null); - } + private WeakReference loggerContext = null; /** * Constructs an Interpolator using a given StrLookup and a list of packages to find Lookup plugins in. + * Only used in the Interpolator. * * @param defaultLookup the default StrLookup to use as a fallback - * @param pluginPackages a list of packages to scan for Lookup plugins * @since 2.1 */ - public Interpolator(final StrLookup defaultLookup, final List pluginPackages) { - this.defaultLookup = defaultLookup == null ? new MapLookup(new HashMap()) : defaultLookup; - final PluginManager manager = new PluginManager(CATEGORY); - manager.collectPlugins(pluginPackages); - final Map> plugins = manager.getPlugins(); - - for (final Map.Entry> entry : plugins.entrySet()) { - try { - final Class clazz = entry.getValue().getPluginClass().asSubclass(StrLookup.class); - strLookupMap.put(entry.getKey(), ReflectionUtil.instantiate(clazz)); - } catch (final Throwable t) { - handleError(entry.getKey(), t); - } - } + public Interpolator(final StrLookup defaultLookup) { + this.defaultLookup = defaultLookup == null ? new PropertiesLookup(Map.of()) : defaultLookup; + final Injector injector = DI.createInjector(); + injector.getInstance(PLUGIN_CATEGORY_KEY) + .forEach((key, value) -> { + try { + strLookups.put(key, injector.getFactory(value.getPluginClass().asSubclass(StrLookup.class))); + } catch (final Throwable t) { + handleError(key, t); + } + }); } /** - * Create the default Interpolator using only Lookups that work without an event. + * Used by interpolatrorFactory. + * + * @param defaultLookup The default Lookup. + * @param strLookupPlugins The Lookup Plugins. + */ + public Interpolator(final StrLookup defaultLookup, final Map> strLookupPlugins) { + this.defaultLookup = defaultLookup; + strLookups.putAll(strLookupPlugins); + } + + /** + * Create the default Interpolator. */ public Interpolator() { - this((Map) null); + this(Map.of()); } /** - * Creates the Interpolator using only Lookups that work without an event and initial properties. + * Creates the default Interpolator with the provided properties. */ public Interpolator(final Map properties) { - this.defaultLookup = new MapLookup(properties == null ? new HashMap() : properties); - // TODO: this ought to use the PluginManager - strLookupMap.put("log4j", new Log4jLookup()); - strLookupMap.put("sys", new SystemPropertiesLookup()); - strLookupMap.put("env", new EnvironmentLookup()); - strLookupMap.put("main", MainMapLookup.MAIN_SINGLETON); - strLookupMap.put("marker", new MarkerLookup()); - strLookupMap.put("java", new JavaLookup()); - // JNDI - try { - // [LOG4J2-703] We might be on Android - strLookupMap.put(LOOKUP_KEY_JNDI, - Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class)); - } catch (final LinkageError | Exception e) { - handleError(LOOKUP_KEY_JNDI, e); - } - // JMX input args - try { - // We might be on Android - strLookupMap.put(LOOKUP_KEY_JVMRUNARGS, - Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JmxRuntimeInputArgumentsLookup", - StrLookup.class)); - } catch (final LinkageError | Exception e) { - handleError(LOOKUP_KEY_JVMRUNARGS, e); - } - strLookupMap.put("date", new DateLookup()); - strLookupMap.put("ctx", new ContextMapLookup()); - if (Constants.IS_WEB_APP) { - try { - strLookupMap.put(LOOKUP_KEY_WEB, - Loader.newCheckedInstanceOf("org.apache.logging.log4j.web.WebLookup", StrLookup.class)); - } catch (final Exception ignored) { - handleError(LOOKUP_KEY_WEB, ignored); - } - } else { - LOGGER.debug("Not in a ServletContext environment, thus not loading WebLookup plugin."); - } + this(new PropertiesLookup(properties)); + } + + public StrLookup getDefaultLookup() { + return defaultLookup; } public Map getStrLookupMap() { - return strLookupMap; + return strLookups.entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get())); } private void handleError(final String lookupKey, final Throwable t) { @@ -151,6 +137,13 @@ private void handleError(final String lookupKey, final Throwable t) { "available. If you want better web container support, please add the log4j-web JAR to your " + "web archive or server lib directory."); break; + case LOOKUP_KEY_DOCKER: case LOOKUP_KEY_SPRING: + break; + case LOOKUP_KEY_KUBERNETES: + if (t instanceof NoClassDefFoundError) { + LOGGER.warn("Unable to create Kubernetes lookup due to missing dependency: {}", t.getMessage()); + } + break; default: LOGGER.error("Unable to create Lookup for {}", lookupKey, t); } @@ -179,12 +172,16 @@ public String lookup(final LogEvent event, String var) { if (prefixPos >= 0) { final String prefix = var.substring(0, prefixPos).toLowerCase(Locale.US); final String name = var.substring(prefixPos + 1); - final StrLookup lookup = strLookupMap.get(prefix); - if (lookup instanceof ConfigurationAware) { - ((ConfigurationAware) lookup).setConfiguration(configuration); - } + final Supplier lookupSupplier = strLookups.get(prefix); String value = null; - if (lookup != null) { + if (lookupSupplier != null) { + final StrLookup lookup = lookupSupplier.get(); + if (lookup instanceof ConfigurationAware) { + ((ConfigurationAware) lookup).setConfiguration(configuration); + } + if (lookup instanceof LoggerContextAware) { + ((LoggerContextAware) lookup).setLoggerContext(loggerContext.get()); + } value = event == null ? lookup.lookup(name) : lookup.lookup(event, name); } @@ -199,10 +196,17 @@ public String lookup(final LogEvent event, String var) { return null; } + public void setLoggerContext(final LoggerContext loggerContext) { + if (loggerContext == null) { + return; + } + this.loggerContext = new WeakReference<>(loggerContext); + } + @Override public String toString() { final StringBuilder sb = new StringBuilder(); - for (final String name : strLookupMap.keySet()) { + for (final String name : strLookups.keySet()) { if (sb.length() == 0) { sb.append('{'); } else { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/InterpolatorFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/InterpolatorFactory.java new file mode 100644 index 00000000000..3700fe193c0 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/InterpolatorFactory.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.lookup; + +public interface InterpolatorFactory { + Interpolator newInterpolator(final StrLookup defaultLookup); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java index 08e388530fa..c6cb32384ee 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java @@ -16,16 +16,17 @@ */ package org.apache.logging.log4j.core.lookup; -import java.util.Locale; - import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.Strings; +import java.util.Locale; + /** * Looks up keys related to Java: Java version, JRE version, VM version, and so on. */ -@Plugin(name = "java", category = StrLookup.CATEGORY) +@Lookup +@Plugin("java") public class JavaLookup extends AbstractLookup { private final SystemPropertiesLookup spLookup = new SystemPropertiesLookup(); @@ -89,7 +90,7 @@ public String getVirtualMachine() { } /** - * Looks up the value of the environment variable. + * Looks up the value of the Java platform key. * * @param event * The current LogEvent (is ignored by this StrLookup). diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java index 3dc2e5d72e3..848b646f644 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java @@ -20,7 +20,10 @@ import java.util.List; import java.util.Map; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.util.Lazy; /** * Maps JVM input arguments (but not main arguments) using JMX to acquire JVM arguments. @@ -28,15 +31,19 @@ * @see java.lang.management.RuntimeMXBean#getInputArguments() * @since 2.1 */ -@Plugin(name = "jvmrunargs", category = StrLookup.CATEGORY) +@Lookup +@Plugin("jvmrunargs") public class JmxRuntimeInputArgumentsLookup extends MapLookup { - static { + private static final Lazy INSTANCE = Lazy.lazy(() -> { final List argsList = ManagementFactory.getRuntimeMXBean().getInputArguments(); - JMX_SINGLETON = new JmxRuntimeInputArgumentsLookup(MapLookup.toMap(argsList)); - } + return new JmxRuntimeInputArgumentsLookup(MapLookup.toMap(argsList)); + }); - public static final JmxRuntimeInputArgumentsLookup JMX_SINGLETON; + @PluginFactory + public static JmxRuntimeInputArgumentsLookup getInstance() { + return INSTANCE.value(); + } /** * Constructor when used directly as a plugin. @@ -49,4 +56,17 @@ public JmxRuntimeInputArgumentsLookup(final Map map) { super(map); } + @Override + public String lookup(final LogEvent event, final String key) { + return lookup(key); + } + + @Override + public String lookup(final String key) { + if (key == null) { + return null; + } + Map map = getMap(); + return map == null ? null : map.get(key); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java index 2f1ef677969..bf4678c4d41 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java @@ -16,26 +16,28 @@ */ package org.apache.logging.log4j.core.lookup; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.status.StatusLogger; + import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.ConfigurationSource; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.status.StatusLogger; - /** * Lookup properties of Log4j */ -@Plugin(name = "log4j", category = StrLookup.CATEGORY) +@Lookup +@Plugin("log4j") public class Log4jLookup extends AbstractConfigurationAwareLookup { public final static String KEY_CONFIG_LOCATION = "configLocation"; public final static String KEY_CONFIG_PARENT_LOCATION = "configParentLocation"; - private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + private static final Logger LOGGER = StatusLogger.getLogger(); private static String asPath(final URI uri) { if (uri.getScheme() == null || uri.getScheme().equals("file")) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Lookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Lookup.java new file mode 100644 index 00000000000..0f41876e40a --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Lookup.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.lookup; + +import org.apache.logging.log4j.plugins.Namespace; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Plugin namespace for {@link StrLookup} plugins. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Namespace(StrLookup.CATEGORY) +public @interface Lookup { +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java new file mode 100644 index 00000000000..73e9d2807b5 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.Plugin; + +/** + * Converts values to lower case. The passed in "key" should be the value of another lookup. + */ +@Lookup +@Plugin("lower") +public class LowerLookup implements StrLookup { + + /** + * Converts the "key" to lower case. + * @param key the key to be looked up, may be null + * @return The value associated with the key. + */ + @Override + public String lookup(final String key) { + return key != null ? key.toLowerCase() : null; + } + + /** + * Converts the "key" to lower case. + * @param event The current LogEvent. + * @param key the key to be looked up, may be null + * @return The value associated with the key. + */ + @Override + public String lookup(final LogEvent event, final String key) { + return lookup(key); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java index a50de0da9e1..66dd460222e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java @@ -16,19 +16,20 @@ */ package org.apache.logging.log4j.core.lookup; -import java.util.Map; - import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Plugin; + +import java.util.Map; /** * A map-based lookup for main arguments. - * + * * See {@link #setMainArguments(String[])}. - * + * * @since 2.4 */ -@Plugin(name = "main", category = StrLookup.CATEGORY) +@Lookup +@Plugin("main") public class MainMapLookup extends MapLookup { /** @@ -64,9 +65,13 @@ public MainMapLookup(final Map map) { * Second using the argument at position n as the key to access the value at n+1. *

    *
      - *
    • {@code "main:--file"} = {@code "path/file.txt"}
    • - *
    • {@code "main:-x"} = {@code "2"}
    • + *
    • {@code "main:\--file"} = {@code "path/file.txt"}
    • + *
    • {@code "main:\-x"} = {@code "2"}
    • *
    + *

    Note: Many applications use leading dashes to identify command arguments. Specifying {@code "main:--file} + * would result in the lookup failing because it would look for a variable named "main" with a default + * value of "-file". To avoid this the ":" separating the Lookup name from the key must be followed by + * a backslash as an escape character.

    * * @param args * An application's {@code public static main(String[])} arguments. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java index 466decb9d4e..b4e7738199c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java @@ -16,18 +16,20 @@ */ package org.apache.logging.log4j.core.lookup; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.util.Strings; + import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.message.StringMapMessage; - /** * A map-based lookup. */ -@Plugin(name = "map", category = StrLookup.CATEGORY) +@Lookup +@Plugin("map") public class MapLookup implements StrLookup { /** @@ -43,7 +45,7 @@ public MapLookup() { } /** - * Creates a new instance backed by a Map. Used by the default lookup. + * Creates a new instance backed by a Map. * * @param map * the map of keys to values, may be null @@ -66,43 +68,12 @@ static HashMap newMap(final int initialCapacity) { return new HashMap<>(initialCapacity); } - /** - * An application's {@code public static main(String[])} method calls this method to make its main arguments - * available for lookup with the prefix {@code main}. - *

    - * The map provides two kinds of access: First by index, starting at {@code "0"}, {@code "1"} and so on. For - * example, the command line {@code --file path/file.txt -x 2} can be accessed from a configuration file with: - *

    - *
      - *
    • {@code "main:0"} = {@code "--file"}
    • - *
    • {@code "main:1"} = {@code "path/file.txt"}
    • - *
    • {@code "main:2"} = {@code "-x"}
    • - *
    • {@code "main:3"} = {@code "2"}
    • - *
    - *

    - * Second using the argument at position n as the key to access the value at n+1. - *

    - *
      - *
    • {@code "main:--file"} = {@code "path/file.txt"}
    • - *
    • {@code "main:-x"} = {@code "2"}
    • - *
    - * - * @param args - * An application's {@code public static main(String[])} arguments. - * @since 2.1 - * @deprecated As of 2.4, use {@link MainMapLookup#setMainArguments(String[])} - */ - @Deprecated - public static void setMainArguments(final String... args) { - MainMapLookup.setMainArguments(args); - } - static Map toMap(final List args) { if (args == null) { return null; } final int size = args.size(); - return initMap(args.toArray(new String[size]), newMap(size)); + return initMap(args.toArray(Strings.EMPTY_ARRAY), newMap(size)); } static Map toMap(final String[] args) { @@ -118,18 +89,15 @@ protected Map getMap() { @Override public String lookup(final LogEvent event, final String key) { - final boolean isMapMessage = event != null && event.getMessage() instanceof StringMapMessage; - if (map == null && !isMapMessage) { - return null; - } - if (map != null && map.containsKey(key)) { - final String obj = map.get(key); + final boolean isMapMessage = event != null && event.getMessage() instanceof MapMessage; + if (isMapMessage) { + final String obj = ((MapMessage) event.getMessage()).get(key); if (obj != null) { return obj; } } - if (isMapMessage) { - return ((StringMapMessage) event.getMessage()).get(key); + if (map != null) { + return map.get(key); } return null; } @@ -146,7 +114,7 @@ public String lookup(final LogEvent event, final String key) { */ @Override public String lookup(final String key) { - if (map == null) { + if (key == null || map == null) { return null; } return map.get(key); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java index 320db492d2c..69c17383673 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java @@ -20,14 +20,15 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Plugin; /** * Looks-up markers. - * + * * @since 2.4 */ -@Plugin(name = "marker", category = StrLookup.CATEGORY) +@Lookup +@Plugin("marker") public class MarkerLookup extends AbstractLookup { static final String MARKER = "marker"; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/PropertiesLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/PropertiesLookup.java new file mode 100644 index 00000000000..30667926fae --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/PropertiesLookup.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import org.apache.logging.log4j.core.LogEvent; + +import java.util.Collections; +import java.util.Map; + +/** + * A lookup designed for {@code Properties} defined in the configuration. This is similar + * to {@link MapLookup} without special handling for structured messages. + * + * Note that this lookup is not a plugin, but wired as a default lookup in the configuration. + */ +public final class PropertiesLookup implements StrLookup { + + /** + * Configuration from which to read properties. + */ + private final Map properties; + + /** + * Constructs a new instance for the given map. + * + * @param properties map these. + */ + public PropertiesLookup(final Map properties) { + this.properties = properties == null + ? Collections.emptyMap() + : properties; + } + + /** + * Gets the property map. + * + * @return the property map. + */ + public Map getProperties() { + return properties; + } + + @Override + public String lookup(@SuppressWarnings("ignored") final LogEvent event, final String key) { + return lookup(key); + } + + /** + * Looks a value from configuration properties. + *

    + * If the property is not defined, then null is returned. + *

    + * + * @param key the key to be looked up, may be null + * @return the matching value, null if no match + */ + @Override + public String lookup(final String key) { + return key == null ? null : properties.get(key); + } + + @Override + public String toString() { + return "PropertiesLookup{properties=" + properties + '}'; + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java index 83187c2ae20..d71abd78155 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java @@ -16,20 +16,21 @@ */ package org.apache.logging.log4j.core.lookup; -import java.util.MissingResourceException; -import java.util.ResourceBundle; - import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.status.StatusLogger; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + /** * Looks up keys from resource bundles. */ -@Plugin(name = "bundle", category = StrLookup.CATEGORY) +@Lookup +@Plugin("bundle") public class ResourceBundleLookup extends AbstractLookup { private static final Logger LOGGER = StatusLogger.getLogger(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java new file mode 100644 index 00000000000..f002c6e56ba --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/RuntimeStrSubstitutor.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.lookup; + +import java.util.Map; +import java.util.Properties; + +/** + * {@link RuntimeStrSubstitutor} is a {@link StrSubstitutor} which only supports evaluation of top-level lookups. + */ +public final class RuntimeStrSubstitutor extends StrSubstitutor { + + public RuntimeStrSubstitutor() { + } + + public RuntimeStrSubstitutor(final Map valueMap) { + super(valueMap); + } + + public RuntimeStrSubstitutor(final Properties properties) { + super(properties); + } + + public RuntimeStrSubstitutor(final StrLookup lookup) { + super(lookup); + } + + public RuntimeStrSubstitutor(final StrSubstitutor other) { + super(other); + } + + @Override + boolean isRecursiveEvaluationAllowed() { + return false; + } + + @Override + void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) { + throw new UnsupportedOperationException( + "recursiveEvaluationAllowed cannot be modified within RuntimeStrSubstitutor"); + } + + @Override + public String toString() { + return "RuntimeStrSubstitutor{" + super.toString() + "}"; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java index e29d2804a27..74353423e31 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrLookup.java @@ -17,6 +17,9 @@ package org.apache.logging.log4j.core.lookup; import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.model.PluginNamespace; /** * Lookup a String key to a String value. @@ -26,10 +29,6 @@ * demand based on the key. *

    *

    - * This class comes complete with various factory methods. - * If these do not suffice, you can subclass and implement your own matcher. - *

    - *

    * For example, it would be possible to implement a lookup that used the * key as a primary key, and looked up the value on demand from the database *

    @@ -43,6 +42,8 @@ public interface StrLookup { */ String CATEGORY = "Lookup"; + Key PLUGIN_CATEGORY_KEY = new @Namespace(CATEGORY) Key<>() {}; + /** * Looks up a String key to a String value. *

    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrMatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrMatcher.java index f6d787ad6da..bea426234bc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrMatcher.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrMatcher.java @@ -369,7 +369,7 @@ public int isMatch(final char[] buffer, int pos, final int bufferStart, final in } return len; } - + @Override public String toString() { return super.toString() + Chars.SPACE + Arrays.toString(chars); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java index 405ca28254e..6802123ba4b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java @@ -22,11 +22,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.ConfigurationAware; +import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; /** @@ -57,7 +59,7 @@ * The following example demonstrates this: *

    *
    - * Map valuesMap = HashMap();
    + * Map valuesMap = new HashMap<>();
      * valuesMap.put("animal", "quick brown fox");
      * valuesMap.put("target", "lazy dog");
      * String templateString = "The ${animal} jumped over the ${target}.";
    @@ -78,7 +80,7 @@
      * The following shows an example with variable default value settings:
      * 

    *
    - * Map valuesMap = HashMap();
    + * Map valuesMap = new HashMap<>();
      * valuesMap.put("animal", "quick brown fox");
      * valuesMap.put("target", "lazy dog");
      * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.";
    @@ -158,7 +160,11 @@ public class StrSubstitutor implements ConfigurationAware {
         /**
          * Constant for the default value delimiter of a variable.
          */
    -    public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-");
    +    public static final String DEFAULT_VALUE_DELIMITER_STRING = ":-";
    +    public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(DEFAULT_VALUE_DELIMITER_STRING);
    +
    +    public static final String ESCAPE_DELIMITER_STRING = ":\\-";
    +    public static final StrMatcher DEFAULT_VALUE_ESCAPE_DELIMITER = StrMatcher.stringMatcher(ESCAPE_DELIMITER_STRING);
     
         private static final int BUF_SIZE = 256;
     
    @@ -180,8 +186,14 @@ public class StrSubstitutor implements ConfigurationAware {
         /**
          * Stores the default variable value delimiter
          */
    +    private String valueDelimiterString;
         private StrMatcher valueDelimiterMatcher;
     
    +    /**
    +     * Escape string to avoid matching the value delimiter matcher;
    +     */
    +    private StrMatcher valueEscapeDelimiterMatcher;
    +
         /**
          * Variable resolution is delegated to an implementer of VariableResolver.
          */
    @@ -197,6 +209,8 @@ public class StrSubstitutor implements ConfigurationAware {
          */
         private Configuration configuration;
     
    +    private boolean recursiveEvaluationAllowed;
    +
         //-----------------------------------------------------------------------
         /**
          * Creates a new instance with defaults for variable prefix and suffix
    @@ -213,7 +227,7 @@ public StrSubstitutor() {
          * @param valueMap  the map with the variables' values, may be null
          */
         public StrSubstitutor(final Map valueMap) {
    -        this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
    +        this(new PropertiesLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
         }
     
         /**
    @@ -225,7 +239,7 @@ public StrSubstitutor(final Map valueMap) {
          * @throws IllegalArgumentException if the prefix or suffix is null
          */
         public StrSubstitutor(final Map valueMap, final String prefix, final String suffix) {
    -        this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
    +        this(new PropertiesLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
         }
     
         /**
    @@ -239,7 +253,7 @@ public StrSubstitutor(final Map valueMap, final String prefix, f
          */
         public StrSubstitutor(final Map valueMap, final String prefix, final String suffix,
                               final char escape) {
    -        this(new MapLookup(valueMap), prefix, suffix, escape);
    +        this(new PropertiesLookup(valueMap), prefix, suffix, escape);
         }
     
         /**
    @@ -254,7 +268,7 @@ public StrSubstitutor(final Map valueMap, final String prefix, f
          */
         public StrSubstitutor(final Map valueMap, final String prefix, final String suffix,
                                   final char escape, final String valueDelimiter) {
    -        this(new MapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
    +        this(new PropertiesLookup(valueMap), prefix, suffix, escape, valueDelimiter);
         }
     
         /**
    @@ -323,7 +337,9 @@ public StrSubstitutor(final StrLookup variableResolver, final String prefix, fin
         public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
                               final StrMatcher suffixMatcher,
                               final char escape) {
    -        this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
    +        this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER,
    +                DEFAULT_VALUE_ESCAPE_DELIMITER);
    +        this.valueDelimiterString = DEFAULT_VALUE_DELIMITER_STRING;
         }
     
         /**
    @@ -336,8 +352,8 @@ public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixM
          * @param valueDelimiterMatcher  the variable default value delimiter matcher, may be null
          * @throws IllegalArgumentException if the prefix or suffix is null
          */
    -    public StrSubstitutor(
    -            final StrLookup variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) {
    +    public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
    +            final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) {
             this.setVariableResolver(variableResolver);
             this.setVariablePrefixMatcher(prefixMatcher);
             this.setVariableSuffixMatcher(suffixMatcher);
    @@ -345,6 +361,42 @@ public StrSubstitutor(
             this.setValueDelimiterMatcher(valueDelimiterMatcher);
         }
     
    +    /**
    +     * Creates a new instance and initializes it.
    +     *
    +     * @param variableResolver  the variable resolver, may be null
    +     * @param prefixMatcher  the prefix for variables, not null
    +     * @param suffixMatcher  the suffix for variables, not null
    +     * @param escape  the escape character
    +     * @param valueDelimiterMatcher  the variable default value delimiter matcher, may be null
    +     * @param valueEscapeMatcher the matcher to escape defaulting, may be null.
    +     * @throws IllegalArgumentException if the prefix or suffix is null
    +     */
    +    public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
    +                          final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher,
    +                          final StrMatcher valueEscapeMatcher) {
    +        this.setVariableResolver(variableResolver);
    +        this.setVariablePrefixMatcher(prefixMatcher);
    +        this.setVariableSuffixMatcher(suffixMatcher);
    +        this.setEscapeChar(escape);
    +        this.setValueDelimiterMatcher(valueDelimiterMatcher);
    +        valueEscapeDelimiterMatcher = valueEscapeMatcher;
    +    }
    +
    +    StrSubstitutor(final StrSubstitutor other) {
    +        Objects.requireNonNull(other, "other");
    +        this.setVariableResolver(other.getVariableResolver());
    +        this.setVariablePrefixMatcher(other.getVariablePrefixMatcher());
    +        this.setVariableSuffixMatcher(other.getVariableSuffixMatcher());
    +        this.setEscapeChar(other.getEscapeChar());
    +        this.setValueDelimiterMatcher(other.valueDelimiterMatcher);
    +        this.valueEscapeDelimiterMatcher = other.valueEscapeDelimiterMatcher;
    +        this.configuration = other.configuration;
    +        this.recursiveEvaluationAllowed = other.isRecursiveEvaluationAllowed();
    +        this.enableSubstitutionInVariables = other.isEnableSubstitutionInVariables();
    +        this.valueDelimiterString = other.valueDelimiterString;
    +    }
    +
         //-----------------------------------------------------------------------
         /**
          * Replaces all the occurrences of variables in the given source object with
    @@ -405,6 +457,11 @@ private static Map toTypeSafeMap(final Properties properties) {
             return map;
         }
     
    +    private static String handleFailedReplacement(String input, Throwable throwable) {
    +        StatusLogger.getLogger().error("Replacement failed on {}", input, throwable);
    +        return input;
    +    }
    +
         //-----------------------------------------------------------------------
         /**
          * Replaces all the occurrences of variables with their matching values
    @@ -430,8 +487,12 @@ public String replace(final LogEvent event, final String source) {
                 return null;
             }
             final StringBuilder buf = new StringBuilder(source);
    -        if (!substitute(event, buf, 0, source.length())) {
    -            return source;
    +        try {
    +            if (!substitute(event, buf, 0, source.length())) {
    +                return source;
    +            }
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(source, t);
             }
             return buf.toString();
         }
    @@ -472,8 +533,12 @@ public String replace(final LogEvent event, final String source, final int offse
                 return null;
             }
             final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
    -        if (!substitute(event, buf, 0, length)) {
    -            return source.substring(offset, offset + length);
    +        try {
    +            if (!substitute(event, buf, 0, length)) {
    +                return source.substring(offset, offset + length);
    +            }
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(source, t);
             }
             return buf.toString();
         }
    @@ -506,7 +571,11 @@ public String replace(final LogEvent event, final char[] source) {
                 return null;
             }
             final StringBuilder buf = new StringBuilder(source.length).append(source);
    -        substitute(event, buf, 0, source.length);
    +        try {
    +            substitute(event, buf, 0, source.length);
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(new String(source), t);
    +        }
             return buf.toString();
         }
     
    @@ -548,7 +617,11 @@ public String replace(final LogEvent event, final char[] source, final int offse
                 return null;
             }
             final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
    -        substitute(event, buf, 0, length);
    +        try {
    +            substitute(event, buf, 0, length);
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(new String(source, offset, length), t);
    +        }
             return buf.toString();
         }
     
    @@ -580,7 +653,11 @@ public String replace(final LogEvent event, final StringBuffer source) {
                 return null;
             }
             final StringBuilder buf = new StringBuilder(source.length()).append(source);
    -        substitute(event, buf, 0, buf.length());
    +        try {
    +            substitute(event, buf, 0, buf.length());
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(source.toString(), t);
    +        }
             return buf.toString();
         }
     
    @@ -622,7 +699,11 @@ public String replace(final LogEvent event, final StringBuffer source, final int
                 return null;
             }
             final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
    -        substitute(event, buf, 0, length);
    +        try {
    +            substitute(event, buf, 0, length);
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(source.substring(offset, offset + length), t);
    +        }
             return buf.toString();
         }
     
    @@ -654,7 +735,11 @@ public String replace(final LogEvent event, final StringBuilder source) {
                 return null;
             }
             final StringBuilder buf = new StringBuilder(source.length()).append(source);
    -        substitute(event, buf, 0, buf.length());
    +        try {
    +            substitute(event, buf, 0, buf.length());
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(source.toString(), t);
    +        }
             return buf.toString();
         }
         /**
    @@ -695,7 +780,11 @@ public String replace(final LogEvent event, final StringBuilder source, final in
                 return null;
             }
             final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
    -        substitute(event, buf, 0, length);
    +        try {
    +            substitute(event, buf, 0, length);
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(source.substring(offset, offset + length), t);
    +        }
             return buf.toString();
         }
     
    @@ -725,8 +814,13 @@ public String replace(final LogEvent event, final Object source) {
             if (source == null) {
                 return null;
             }
    -        final StringBuilder buf = new StringBuilder().append(source);
    -        substitute(event, buf, 0, buf.length());
    +        String stringValue = String.valueOf(source);
    +        final StringBuilder buf = new StringBuilder(stringValue.length()).append(stringValue);
    +        try {
    +            substitute(event, buf, 0, buf.length());
    +        } catch (Throwable t) {
    +            return handleFailedReplacement(stringValue, t);
    +        }
             return buf.toString();
         }
     
    @@ -784,7 +878,12 @@ public boolean replaceIn(final LogEvent event, final StringBuffer source, final
                 return false;
             }
             final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
    -        if (!substitute(event, buf, 0, length)) {
    +        try {
    +            if (!substitute(event, buf, 0, length)) {
    +                return false;
    +            }
    +        } catch (Throwable t) {
    +            StatusLogger.getLogger().error("Replacement failed on {}", source, t);
                 return false;
             }
             source.replace(offset, offset + length, buf.toString());
    @@ -909,100 +1008,123 @@ private int substitute(final LogEvent event, final StringBuilder buf, final int
                 final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd);
                 if (startMatchLen == 0) {
                     pos++;
    +            } else // found variable start marker
    +            if (pos > offset && chars[pos - 1] == escape) {
    +                // escaped
    +                buf.deleteCharAt(pos - 1);
    +                chars = getChars(buf);
    +                lengthChange--;
    +                altered = true;
    +                bufEnd--;
                 } else {
    -                // found variable start marker
    -                if (pos > offset && chars[pos - 1] == escape) {
    -                    // escaped
    -                    buf.deleteCharAt(pos - 1);
    -                    chars = getChars(buf);
    -                    lengthChange--;
    -                    altered = true;
    -                    bufEnd--;
    -                } else {
    -                    // find suffix
    -                    final int startPos = pos;
    -                    pos += startMatchLen;
    -                    int endMatchLen = 0;
    -                    int nestedVarCount = 0;
    -                    while (pos < bufEnd) {
    -                        if (substitutionInVariablesEnabled
    -                                && (endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) {
    -                            // found a nested variable start
    -                            nestedVarCount++;
    -                            pos += endMatchLen;
    -                            continue;
    -                        }
    +                // find suffix
    +                final int startPos = pos;
    +                pos += startMatchLen;
    +                int endMatchLen = 0;
    +                int nestedVarCount = 0;
    +                while (pos < bufEnd) {
    +                    if (substitutionInVariablesEnabled
    +                            && (endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) {
    +                        // found a nested variable start
    +                        nestedVarCount++;
    +                        pos += endMatchLen;
    +                        continue;
    +                    }
     
    -                        endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd);
    -                        if (endMatchLen == 0) {
    -                            pos++;
    -                        } else {
    -                            // found variable end marker
    -                            if (nestedVarCount == 0) {
    -                                String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
    -                                if (substitutionInVariablesEnabled) {
    -                                    final StringBuilder bufName = new StringBuilder(varNameExpr);
    -                                    substitute(event, bufName, 0, bufName.length());
    -                                    varNameExpr = bufName.toString();
    +                    endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd);
    +                    if (endMatchLen == 0) {
    +                        pos++;
    +                    } else {
    +                        // found variable end marker
    +                        if (nestedVarCount == 0) {
    +                            String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
    +                            if (substitutionInVariablesEnabled) {
    +                                // initialize priorVariables if they're not already set
    +                                if (priorVariables == null) {
    +                                    priorVariables = new ArrayList<>();
                                     }
    -                                pos += endMatchLen;
    -                                final int endPos = pos;
    -
    -                                String varName = varNameExpr;
    -                                String varDefaultValue = null;
    -
    -                                if (valueDelimiterMatcher != null) {
    -                                    final char [] varNameExprChars = varNameExpr.toCharArray();
    -                                    int valueDelimiterMatchLen = 0;
    -                                    for (int i = 0; i < varNameExprChars.length; i++) {
    -                                        // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
    -                                        if (!substitutionInVariablesEnabled
    -                                                && prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
    +                                final StringBuilder bufName = new StringBuilder(varNameExpr);
    +                                substitute(event, bufName, 0, bufName.length(), priorVariables);
    +                                varNameExpr = bufName.toString();
    +                            }
    +                            pos += endMatchLen;
    +                            final int endPos = pos;
    +
    +                            String varName = varNameExpr;
    +                            String varDefaultValue = null;
    +
    +                            if (valueDelimiterMatcher != null) {
    +                                final char [] varNameExprChars = varNameExpr.toCharArray();
    +                                int valueDelimiterMatchLen = 0;
    +                                for (int i = 0; i < varNameExprChars.length; i++) {
    +                                    // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
    +                                    if (!substitutionInVariablesEnabled
    +                                            && prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
    +                                        break;
    +                                    }
    +                                    if (valueEscapeDelimiterMatcher != null) {
    +                                        int matchLen = valueEscapeDelimiterMatcher.isMatch(varNameExprChars, i);
    +                                        if (matchLen != 0) {
    +                                            String varNamePrefix = varNameExpr.substring(0, i) + Interpolator.PREFIX_SEPARATOR;
    +                                            varName = varNamePrefix + varNameExpr.substring(i + matchLen - 1);
    +                                            for (int j = i + matchLen; j < varNameExprChars.length; ++j){
    +                                                if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, j)) != 0) {
    +                                                    varName = varNamePrefix + varNameExpr.substring(i + matchLen, j);
    +                                                    varDefaultValue = varNameExpr.substring(j + valueDelimiterMatchLen);
    +                                                    break;
    +                                                }
    +                                            }
                                                 break;
    -                                        }
    -                                        if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
    +                                        } else if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
                                                 varName = varNameExpr.substring(0, i);
                                                 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
                                                 break;
                                             }
    +                                    } else if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
    +                                        varName = varNameExpr.substring(0, i);
    +                                        varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
    +                                        break;
                                         }
                                     }
    +                            }
     
    -                                // on the first call initialize priorVariables
    -                                if (priorVariables == null) {
    -                                    priorVariables = new ArrayList<>();
    -                                    priorVariables.add(new String(chars, offset, length + lengthChange));
    -                                }
    +                            // on the first call initialize priorVariables
    +                            if (priorVariables == null) {
    +                                priorVariables = new ArrayList<>();
    +                                priorVariables.add(new String(chars, offset, length + lengthChange));
    +                            }
     
    -                                // handle cyclic substitution
    -                                checkCyclicSubstitution(varName, priorVariables);
    -                                priorVariables.add(varName);
    +                            // handle cyclic substitution
    +                            boolean isCyclic = isCyclicSubstitution(varName, priorVariables);
     
    -                                // resolve the variable
    -                                String varValue = resolveVariable(event, varName, buf, startPos, endPos);
    -                                if (varValue == null) {
    -                                    varValue = varDefaultValue;
    -                                }
    -                                if (varValue != null) {
    -                                    // recursive replace
    -                                    final int varLen = varValue.length();
    -                                    buf.replace(startPos, endPos, varValue);
    -                                    altered = true;
    -                                    int change = substitute(event, buf, startPos, varLen, priorVariables);
    -                                    change = change + (varLen - (endPos - startPos));
    -                                    pos += change;
    -                                    bufEnd += change;
    -                                    lengthChange += change;
    -                                    chars = getChars(buf); // in case buffer was altered
    -                                }
    +                            // resolve the variable
    +                            String varValue = isCyclic ? null : resolveVariable(event, varName, buf, startPos, endPos);
    +                            if (varValue == null) {
    +                                varValue = varDefaultValue;
    +                            }
    +                            if (varValue != null) {
    +                                // recursive replace
    +                                final int varLen = varValue.length();
    +                                buf.replace(startPos, endPos, varValue);
    +                                altered = true;
    +                                int change = isRecursiveEvaluationAllowed()
    +                                        ? substitute(event, buf, startPos, varLen, priorVariables)
    +                                        : 0;
    +                                change = change + (varLen - (endPos - startPos));
    +                                pos += change;
    +                                bufEnd += change;
    +                                lengthChange += change;
    +                                chars = getChars(buf); // in case buffer was altered
    +                            }
     
    -                                // remove variable from the cyclic stack
    +                            // remove variable from the cyclic stack
    +                            if (!isCyclic) {
                                     priorVariables.remove(priorVariables.size() - 1);
    -                                break;
                                 }
    -                            nestedVarCount--;
    -                            pos += endMatchLen;
    +                            break;
                             }
    +                        nestedVarCount--;
    +                        pos += endMatchLen;
                         }
                     }
                 }
    @@ -1014,21 +1136,23 @@ private int substitute(final LogEvent event, final StringBuilder buf, final int
         }
     
         /**
    -     * Checks if the specified variable is already in the stack (list) of variables.
    +     * Checks if the specified variable is already in the stack (list) of variables, adding the value
    +     * if it's not already present.
          *
          * @param varName  the variable name to check
          * @param priorVariables  the list of prior variables
    +     * @return true if this is a cyclic substitution
          */
    -    private void checkCyclicSubstitution(final String varName, final List priorVariables) {
    +    private boolean isCyclicSubstitution(final String varName, final List priorVariables) {
             if (!priorVariables.contains(varName)) {
    -            return;
    +            priorVariables.add(varName);
    +            return false;
             }
             final StringBuilder buf = new StringBuilder(BUF_SIZE);
             buf.append("Infinite loop in property interpolation of ");
    -        buf.append(priorVariables.remove(0));
    -        buf.append(": ");
             appendWithSeparators(buf, priorVariables, "->");
    -        throw new IllegalStateException(buf.toString());
    +        StatusLogger.getLogger().warn(buf);
    +        return true;
         }
     
         /**
    @@ -1057,7 +1181,12 @@ protected String resolveVariable(final LogEvent event, final String variableName
             if (resolver == null) {
                 return null;
             }
    -        return resolver.lookup(event, variableName);
    +        try {
    +            return resolver.lookup(event, variableName);
    +        } catch (Throwable t) {
    +            StatusLogger.getLogger().error("Resolver failed to lookup {}", variableName, t);
    +            return null;
    +        }
         }
     
         // Escape
    @@ -1294,6 +1423,9 @@ public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
                 setValueDelimiterMatcher(null);
                 return this;
             }
    +        String escapeValue = valueDelimiter.substring(0, valueDelimiter.length() - 1) + "\\"
    +                + valueDelimiter.substring(valueDelimiter.length() - 1);
    +        valueEscapeDelimiterMatcher = StrMatcher.stringMatcher(escapeValue);
             return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
         }
     
    @@ -1343,6 +1475,14 @@ public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInV
             this.enableSubstitutionInVariables = enableSubstitutionInVariables;
         }
     
    +    boolean isRecursiveEvaluationAllowed() {
    +        return recursiveEvaluationAllowed;
    +    }
    +
    +    void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) {
    +        this.recursiveEvaluationAllowed = recursiveEvaluationAllowed;
    +    }
    +
         private char[] getChars(final StringBuilder sb) {
             final char[] chars = new char[sb.length()];
             sb.getChars(0, sb.length(), chars, 0);
    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java
    index 379d6a9b42f..422211b82b7 100644
    --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java
    +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java
    @@ -17,13 +17,14 @@
     package org.apache.logging.log4j.core.lookup;
     
     import org.apache.logging.log4j.core.LogEvent;
    -import org.apache.logging.log4j.core.config.plugins.Plugin;
     import org.apache.logging.log4j.message.StructuredDataMessage;
    +import org.apache.logging.log4j.plugins.Plugin;
     
     /**
    - * Looks up keys from {@link org.apache.logging.log4j.message.StructuredDataMessage} log messages.
    + * Looks up keys from {@link StructuredDataMessage} log messages.
      */
    -@Plugin(name = "sd", category = StrLookup.CATEGORY)
    +@Lookup
    +@Plugin("sd")
     public class StructuredDataLookup implements StrLookup {
     
         /**
    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java
    index d2fb530d8c4..01a1171679b 100644
    --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java
    +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java
    @@ -20,20 +20,21 @@
     import org.apache.logging.log4j.Marker;
     import org.apache.logging.log4j.MarkerManager;
     import org.apache.logging.log4j.core.LogEvent;
    -import org.apache.logging.log4j.core.config.plugins.Plugin;
    +import org.apache.logging.log4j.plugins.Plugin;
     import org.apache.logging.log4j.status.StatusLogger;
     
     /**
      * Looks up keys from system properties.
      */
    -@Plugin(name = "sys", category = StrLookup.CATEGORY)
    +@Lookup
    +@Plugin("sys")
     public class SystemPropertiesLookup extends AbstractLookup {
     
         private static final Logger LOGGER = StatusLogger.getLogger();
         private static final Marker LOOKUP = MarkerManager.getMarker("LOOKUP");
     
         /**
    -     * Looks up the value for the key using the data in the LogEvent.
    +     * Looks up the value for the key from system properties.
          * @param event The current LogEvent.
          * @param key  the key to be looked up, may be null
          * @return The value associated with the key.
    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java
    new file mode 100644
    index 00000000000..b905e52848d
    --- /dev/null
    +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java
    @@ -0,0 +1,49 @@
    +/*
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    + * contributor license agreements. See the NOTICE file distributed with
    + * this work for additional information regarding copyright ownership.
    + * The ASF licenses this file to You under the Apache license, Version 2.0
    + * (the "License"); you may not use this file except in compliance with
    + * the License. You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the license for the specific language governing permissions and
    + * limitations under the license.
    + */
    +package org.apache.logging.log4j.core.lookup;
    +
    +import org.apache.logging.log4j.core.LogEvent;
    +import org.apache.logging.log4j.plugins.Plugin;
    +
    +/**
    + * Converts values to upper case. The passed in "key" should be the value of another lookup.
    + */
    +@Lookup
    +@Plugin("upper")
    +public class UpperLookup implements StrLookup {
    +
    +    /**
    +     * Converts the "key" to upper case.
    +     * @param key  the key to be looked up, may be null
    +     * @return The value associated with the key.
    +     */
    +    @Override
    +    public String lookup(final String key) {
    +        return key != null ? key.toUpperCase() : null;
    +    }
    +
    +    /**
    +     * Converts the "key" to upper case.
    +     * @param event The current LogEvent.
    +     * @param key  the key to be looked up, may be null
    +     * @return The value associated with the key.
    +     */
    +    @Override
    +    public String lookup(final LogEvent event, final String key) {
    +        return lookup(key);
    +    }
    +}
    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java
    index 87010c402d4..89dbfa540b9 100644
    --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java
    +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java
    @@ -17,7 +17,6 @@
     /**
      * Log4j 2 Lookups. These are used in variable interpolation in various configuration attributes.
      * {@link org.apache.logging.log4j.core.lookup.StrLookup} plugins should use the
    - * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#category() plugin category}
    - * {@link org.apache.logging.log4j.core.lookup.StrLookup#CATEGORY Lookup}.
    + * {@link org.apache.logging.log4j.core.lookup.Lookup} plugin namespace annotation.
      */
     package org.apache.logging.log4j.core.lookup;
    diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/AbstractSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/AbstractSocketManager.java
    index 51573653b03..253143e5e6e 100644
    --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/AbstractSocketManager.java
    +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/AbstractSocketManager.java
    @@ -34,19 +34,20 @@ public abstract class AbstractSocketManager extends OutputStreamManager {
          * The Internet address of the host.
          */
         protected final InetAddress inetAddress;
    -    
    +
         /**
          * The name of the host.
          */
         protected final String host;
    -    
    +
         /**
          * The port on the host.
          */
         protected final int port;
     
         /**
    -     * The Constructor.
    +     * Constructs a new instance.
    +     *
          * @param name The unique name of this connection.
          * @param os The OutputStream to manage.
          * @param inetAddress The Internet address.
    @@ -55,7 +56,7 @@ public abstract class AbstractSocketManager extends OutputStreamManager {
          * @param bufferSize The buffer size.
          */
         public AbstractSocketManager(final String name, final OutputStream os, final InetAddress inetAddress,
    -            final String host, final int port, final Layout layout, final boolean writeHeader, 
    +            final String host, final int port, final Layout layout, final boolean writeHeader,
                 final int bufferSize) {
             super(os, name, layout, writeHeader, bufferSize);
             this.inetAddress = inetAddress;
    @@ -69,7 +70,7 @@ public AbstractSocketManager(final String name, final OutputStream os, final Ine
          * 
  • Key: "port" Value: provided "port" param
  • *
  • Key: "address" Value: provided "address" param
  • * - * + * * @return Map of content format keys supporting AbstractSocketManager */ @Override @@ -79,4 +80,22 @@ public Map getContentFormat() { result.put("address", inetAddress.getHostAddress()); return result; } + + /** + * Gets the host. + * + * @return the host. + */ + public String getHost() { + return host; + } + + /** + * Gets the port. + * + * @return the port. + */ + public int getPort() { + return port; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/DatagramSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/DatagramSocketManager.java index 844c614a728..d169ab78d87 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/DatagramSocketManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/DatagramSocketManager.java @@ -75,7 +75,7 @@ public static DatagramSocketManager getSocketManager(final String host, final in *
  • Key: "protocol" Value: "udp"
  • *
  • Key: "direction" Value: "out"
  • * - * + * * @return Map of content format keys supporting DatagramSocketManager */ @Override @@ -94,7 +94,7 @@ private static class FactoryData { private final int port; private final Layout layout; private final int bufferSize; - + public FactoryData(final String host, final int port, final Layout layout, final int bufferSize) { this.host = host; this.port = port; @@ -110,7 +110,7 @@ private static class DatagramSocketManagerFactory implements ManagerFactory */ public enum Facility { - + /** Kernel messages. */ KERN(0), - + /** User level messages. */ USER(1), - + /** Mail system. */ MAIL(2), - + /** System daemons. */ DAEMON(3), - + /** Security/Authorization messages. */ AUTH(4), - + /** Messages generated by syslogd. */ SYSLOG(5), - + /** Line printer subsystem. */ LPR(6), - + /** Network news subsystem. */ NEWS(7), - + /** UUCP subsystem. */ UUCP(8), - + /** Clock daemon. */ CRON(9), - + /** Security/Authorization messages. */ AUTHPRIV(10), - + /** FTP daemon. */ FTP(11), - + /** NTP subsystem. */ NTP(12), - + /** Log audit. */ LOG_AUDIT(13), - + /** Log alert. */ LOG_ALERT(14), - + /** Clock daemon. */ CLOCK(15), - + /** Local use 0. */ LOCAL0(16), - + /** Local use 1. */ LOCAL1(17), - + /** Local use 2. */ LOCAL2(18), - + /** Local use 3. */ LOCAL3(19), - + /** Local use 4. */ LOCAL4(20), - + /** Local use 5. */ LOCAL5(21), - + /** Local use 6. */ LOCAL6(22), - + /** Local use 7. */ LOCAL7(23); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java index 3d610bffd8f..440dd3c34d8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java @@ -23,9 +23,9 @@ import java.util.Map; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.util.Integers; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.LoaderUtil; @@ -36,7 +36,8 @@ * will be removed prior to advertisement. * */ -@Plugin(name = "multicastdns", category = Core.CATEGORY_NAME, elementType = "advertiser", printObject = false) +@Configurable(elementType = "advertiser") +@Plugin("MulticastDns") public class MulticastDnsAdvertiser implements Advertiser { /** * Status logger. @@ -46,7 +47,7 @@ public class MulticastDnsAdvertiser implements Advertiser { private static final int MAX_LENGTH = 255; private static final int DEFAULT_PORT = 4555; - private static Object jmDNS = initializeJmDns(); + private static final Object jmDNS = initializeJmDns(); private static Class jmDNSClass; private static Class serviceInfoClass; @@ -93,7 +94,7 @@ public Object advertise(final Map properties) { } catch (final NoSuchMethodException e) { // no-op } - Object serviceInfo; + final Object serviceInfo; if (isVersion3) { serviceInfo = buildServiceInfoVersion3(zone, port, name, truncatedProperties); } else { @@ -116,7 +117,7 @@ public Object advertise(final Map properties) { /** * Unadvertise the previously advertised entity. - * + * * @param serviceInfo */ @Override @@ -189,7 +190,7 @@ private static Object initializeJmDns() { try { jmDNSClass = LoaderUtil.loadClass("javax.jmdns.JmDNS"); serviceInfoClass = LoaderUtil.loadClass("javax.jmdns.ServiceInfo"); - // if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API + // if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API boolean isVersion3 = false; try { // create method is in version 3, not version 1 diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Priority.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Priority.java index c7c6acc42b7..decd996d393 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Priority.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Priority.java @@ -49,7 +49,7 @@ public static int getPriority(final Facility facility, final Level level) { private static int toPriority(final Facility aFacility, final Severity aSeverity) { return (aFacility.getCode() << 3) + aSeverity.getCode(); } - + /** * Returns the Facility. * @return the Facility. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Rfc1349TrafficClass.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Rfc1349TrafficClass.java index 8144e45e7d1..825dff12b41 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Rfc1349TrafficClass.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/Rfc1349TrafficClass.java @@ -19,7 +19,7 @@ /** * Enumerates the RFC 1349 TOS field. - * + * *
      *
    • IPTOS_LOWCOST (0x02)
    • *
    • IPTOS_RELIABILITY (0x04)
    • @@ -39,7 +39,7 @@ public enum Rfc1349TrafficClass { private final int trafficClass; - private Rfc1349TrafficClass(final int trafficClass) { + Rfc1349TrafficClass(final int trafficClass) { this.trafficClass = trafficClass; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java index ef3ff05b19e..b2414fc46ea 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java @@ -16,22 +16,23 @@ */ package org.apache.logging.log4j.core.net; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.validation.constraints.ValidHost; +import org.apache.logging.log4j.plugins.validation.constraints.ValidPort; + import java.net.InetAddress; import java.net.InetSocketAddress; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort; - /** * Plugin to hold a hostname and port (socket address). * * @since 2.8 */ -@Plugin(name = "SocketAddress", category = Node.CATEGORY, printObject = true) +@Configurable(printObject = true) +@Plugin public class SocketAddress { /** @@ -66,12 +67,12 @@ public String getHostName() { return socketAddress.getHostName(); } - @PluginBuilderFactory + @PluginFactory public static Builder newBuilder() { return new Builder(); } - public static class Builder implements org.apache.logging.log4j.core.util.Builder { + public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { @PluginBuilderAttribute @ValidHost diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java index 711d0441d37..7c6fab9b273 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java @@ -16,23 +16,24 @@ */ package org.apache.logging.log4j.core.net; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.util.Builder; + import java.net.Socket; import java.net.SocketException; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.util.Builder; - /** * Holds all socket options settable via {@link Socket} methods. */ -@Plugin(name = "SocketOptions", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public class SocketOptions implements Builder, Cloneable { - @PluginBuilderFactory + @PluginFactory public static SocketOptions newBuilder() { return new SocketOptions(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java index 8a7e1b0169f..fc26153573d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java @@ -16,13 +16,14 @@ */ package org.apache.logging.log4j.core.net; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.util.Builder; +import org.apache.logging.log4j.plugins.validation.constraints.Required; + import java.net.Socket; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; -import org.apache.logging.log4j.core.util.Builder; /** * Holds all socket options settable via {@link Socket#setPerformancePreferences(int, int, int)}. @@ -30,10 +31,11 @@ * The {@link Socket#setPerformancePreferences(int, int, int)} API may not be implemented by a JRE. *

      */ -@Plugin(name = "SocketPerformancePreferences", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin public class SocketPerformancePreferences implements Builder, Cloneable { - @PluginBuilderFactory + @PluginFactory public static SocketPerformancePreferences newBuilder() { return new SocketPerformancePreferences(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java index 2a4593defaa..721a423c4a1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java @@ -22,6 +22,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; +import java.util.List; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -39,31 +40,6 @@ public class SslSocketManager extends TcpSocketManager { private final SslConfiguration sslConfig; /** - * - * - * @param name The unique name of this connection. - * @param os The OutputStream. - * @param sock The Socket. - * @param inetAddress The Internet address of the host. - * @param host The name of the host. - * @param port The port number on the host. - * @param connectTimeoutMillis the connect timeout in milliseconds. - * @param reconnectionDelayMillis Reconnection interval. - * @param immediateFail - * @param layout The Layout. - * @param bufferSize The buffer size. - * @deprecated Use {@link #SslSocketManager(String, OutputStream, Socket, SslConfiguration, InetAddress, String, int, int, int, boolean, Layout, int, SocketOptions)}. - */ - @Deprecated - public SslSocketManager(final String name, final OutputStream os, final Socket sock, - final SslConfiguration sslConfig, final InetAddress inetAddress, final String host, final int port, - final int connectTimeoutMillis, final int reconnectionDelayMillis, final boolean immediateFail, - final Layout layout, final int bufferSize) { - super(name, os, sock, inetAddress, host, port, connectTimeoutMillis, reconnectionDelayMillis, immediateFail, layout, bufferSize, null); - this.sslConfig = sslConfig; - } - - /** * * * @param name The unique name of this connection. @@ -106,16 +82,6 @@ public String toString() { } } - /** - * @deprecated Use {@link SslSocketManager#getSocketManager(SslConfiguration, String, int, int, int, boolean, Layout, int, SocketOptions)}. - */ - @Deprecated - public static SslSocketManager getSocketManager(final SslConfiguration sslConfig, final String host, final int port, - final int connectTimeoutMillis, final int reconnectDelayMillis, final boolean immediateFail, - final Layout layout, final int bufferSize) { - return getSocketManager(sslConfig, host, port, connectTimeoutMillis, reconnectDelayMillis, immediateFail, layout, bufferSize, null); - } - public static SslSocketManager getSocketManager(final SslConfiguration sslConfig, final String host, int port, final int connectTimeoutMillis, int reconnectDelayMillis, final boolean immediateFail, final Layout layout, final int bufferSize, final SocketOptions socketOptions) { @@ -134,16 +100,15 @@ public static SslSocketManager getSocketManager(final SslConfiguration sslConfig } @Override - protected Socket createSocket(final String host, final int port) throws IOException { + protected Socket createSocket(final InetSocketAddress socketAddress) throws IOException { final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfig); - final InetSocketAddress address = new InetSocketAddress(host, port); final Socket newSocket = socketFactory.createSocket(); - newSocket.connect(address, getConnectTimeoutMillis()); + newSocket.connect(socketAddress, getConnectTimeoutMillis()); return newSocket; } private static SSLSocketFactory createSslSocketFactory(final SslConfiguration sslConf) { - SSLSocketFactory socketFactory; + final SSLSocketFactory socketFactory; if (sslConf != null) { socketFactory = sslConf.getSslSocketFactory(); @@ -158,29 +123,38 @@ private static SSLSocketFactory createSslSocketFactory(final SslConfiguration ss private static class SslSocketManagerFactory extends TcpSocketManagerFactory { @Override - SslSocketManager createManager(final String name, OutputStream os, Socket socket, InetAddress inetAddress, - final SslFactoryData data) { + SslSocketManager createManager(final String name, final OutputStream os, final Socket socket, final InetAddress inetAddress, + final SslFactoryData data) { return new SslSocketManager(name, os, socket, data.sslConfiguration, inetAddress, data.host, data.port, data.connectTimeoutMillis, data.reconnectDelayMillis, data.immediateFail, data.layout, data.bufferSize, data.socketOptions); } - + @Override Socket createSocket(final SslFactoryData data) throws IOException { - return SslSocketManager.createSocket(data.host, data.port, data.connectTimeoutMillis, data.sslConfiguration, - data.socketOptions); + final List socketAddresses = RESOLVER.resolveHost(data.host, data.port); + IOException ioe = null; + for (final InetSocketAddress socketAddress : socketAddresses) { + try { + return SslSocketManager.createSocket(socketAddress, data.connectTimeoutMillis, + data.sslConfiguration, data.socketOptions); + } catch (final IOException ex) { + ioe = ex; + } + } + throw new IOException(errorMessage(data, socketAddresses) , ioe); } } - static Socket createSocket(final String host, int port, int connectTimeoutMillis, - final SslConfiguration sslConfiguration, SocketOptions socketOptions) throws IOException { + static Socket createSocket(final InetSocketAddress socketAddress, final int connectTimeoutMillis, + final SslConfiguration sslConfiguration, final SocketOptions socketOptions) throws IOException { final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfiguration); final SSLSocket socket = (SSLSocket) socketFactory.createSocket(); if (socketOptions != null) { // Not sure which options must be applied before or after the connect() call. socketOptions.apply(socket); } - socket.connect(new InetSocketAddress(host, port), connectTimeoutMillis); + socket.connect(socketAddress, connectTimeoutMillis); if (socketOptions != null) { // Not sure which options must be applied before or after the connect() call. socketOptions.apply(socket); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java index f5b971df3d0..6698c1ed176 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java @@ -24,7 +24,9 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -68,44 +70,7 @@ public class TcpSocketManager extends AbstractSocketManager { /** * Constructs. - * - * @param name - * The unique name of this connection. - * @param os - * The OutputStream. - * @param socket - * The Socket. - * @param inetAddress - * The Internet address of the host. - * @param host - * The name of the host. - * @param port - * The port number on the host. - * @param connectTimeoutMillis - * the connect timeout in milliseconds. - * @param reconnectionDelayMillis - * Reconnection interval. - * @param immediateFail - * True if the write should fail if no socket is immediately available. - * @param layout - * The Layout. - * @param bufferSize - * The buffer size. - * @deprecated Use - * {@link TcpSocketManager#TcpSocketManager(String, OutputStream, Socket, InetAddress, String, int, int, int, boolean, Layout, int, SocketOptions)}. - */ - @Deprecated - public TcpSocketManager(final String name, final OutputStream os, final Socket socket, - final InetAddress inetAddress, final String host, final int port, final int connectTimeoutMillis, - final int reconnectionDelayMillis, final boolean immediateFail, final Layout layout, - final int bufferSize) { - this(name, os, socket, inetAddress, host, port, connectTimeoutMillis, reconnectionDelayMillis, immediateFail, - layout, bufferSize, null); - } - - /** - * Constructs. - * + * * @param name * The unique name of this connection. * @param os @@ -148,31 +113,7 @@ public TcpSocketManager(final String name, final OutputStream os, final Socket s /** * Obtains a TcpSocketManager. - * - * @param host - * The host to connect to. - * @param port - * The port on the host. - * @param connectTimeoutMillis - * the connect timeout in milliseconds - * @param reconnectDelayMillis - * The interval to pause between retries. - * @param bufferSize - * The buffer size. - * @return A TcpSocketManager. - * @deprecated Use {@link #getSocketManager(String, int, int, int, boolean, Layout, int, SocketOptions)}. - */ - @Deprecated - public static TcpSocketManager getSocketManager(final String host, final int port, final int connectTimeoutMillis, - final int reconnectDelayMillis, final boolean immediateFail, final Layout layout, - final int bufferSize) { - return getSocketManager(host, port, connectTimeoutMillis, reconnectDelayMillis, immediateFail, layout, - bufferSize, null); - } - - /** - * Obtains a TcpSocketManager. - * + * * @param host * The host to connect to. * @param port @@ -216,12 +157,12 @@ protected void write(final byte[] bytes, final int offset, final int length, fin try { writeAndFlush(bytes, offset, length, immediateFlush); } catch (final IOException causeEx) { + final String config = inetAddress + ":" + port; if (retry && reconnector == null) { - final String config = inetAddress + ":" + port; reconnector = createReconnector(); try { reconnector.reconnect(); - } catch (IOException reconnEx) { + } catch (final IOException reconnEx) { LOGGER.debug("Cannot reestablish socket connection to {}: {}; starting reconnector thread {}", config, reconnEx.getLocalizedMessage(), reconnector.getName(), reconnEx); reconnector.start(); @@ -230,13 +171,16 @@ protected void write(final byte[] bytes, final int offset, final int length, fin } try { writeAndFlush(bytes, offset, length, immediateFlush); - } catch (IOException e) { + } catch (final IOException e) { throw new AppenderLoggingException( String.format("Error writing to %s after reestablishing connection for %s", getName(), config), causeEx); } + return; } + final String message = String.format("Error writing to %s for connection %s", getName(), config); + throw new AppenderLoggingException(message, causeEx); } } } @@ -282,7 +226,7 @@ public int getConnectTimeoutMillis() { *
    • Key: "protocol" Value: "tcp"
    • *
    • Key: "direction" Value: "out"
    • *
    - * + * * @return Map of content format keys supporting TcpSocketManager */ @Override @@ -340,9 +284,30 @@ public void run() { } void reconnect() throws IOException { - final Socket sock = createSocket(inetAddress.getHostName(), port); + final List socketAddresses = TcpSocketManagerFactory.RESOLVER.resolveHost(host, port); + if (socketAddresses.size() == 1) { + LOGGER.debug("Reconnecting " + socketAddresses.get(0)); + connect(socketAddresses.get(0)); + } else { + IOException ioe = null; + for (final InetSocketAddress socketAddress : socketAddresses) { + try { + LOGGER.debug("Reconnecting " + socketAddress); + connect(socketAddress); + return; + } catch (final IOException ex) { + ioe = ex; + } + } + throw ioe; + } + } + + private void connect(final InetSocketAddress socketAddress) throws IOException { + final Socket sock = createSocket(socketAddress); @SuppressWarnings("resource") // newOS is managed by the enclosing Manager. final OutputStream newOS = sock.getOutputStream(); + final InetAddress prev = socket != null ? socket.getInetAddress() : null; synchronized (owner) { Closer.closeSilently(getOutputStream()); setOutputStream(newOS); @@ -350,12 +315,14 @@ void reconnect() throws IOException { reconnector = null; shutdown = true; } - LOGGER.debug("Connection to {}:{} reestablished: {}", host, port, socket); + final String type = prev != null && prev.getHostAddress().equals(socketAddress.getAddress().getHostAddress()) ? + "reestablished" : "established"; + LOGGER.debug("Connection to {}:{} {}: {}", host, port, type, socket); } @Override public String toString() { - return "Reconnector [latch=" + latch + ", shutdown=" + shutdown + ", owner=" + owner + "]"; + return "Reconnector [latch=" + latch + ", shutdown=" + shutdown + "]"; } } @@ -366,19 +333,19 @@ private Reconnector createReconnector() { return recon; } - protected Socket createSocket(final String host, final int port) throws IOException { - return createSocket(host, port, socketOptions, connectTimeoutMillis); + protected Socket createSocket(final InetSocketAddress socketAddress) throws IOException { + return createSocket(socketAddress, socketOptions, connectTimeoutMillis); } - protected static Socket createSocket(final String host, final int port, final SocketOptions socketOptions, + protected static Socket createSocket(final InetSocketAddress socketAddress, final SocketOptions socketOptions, final int connectTimeoutMillis) throws IOException { - LOGGER.debug("Creating socket {}:{}", host, port); + LOGGER.debug("Creating socket {}", socketAddress.toString()); final Socket newSocket = new Socket(); if (socketOptions != null) { // Not sure which options must be applied before or after the connect() call. socketOptions.apply(newSocket); } - newSocket.connect(new InetSocketAddress(host, port), connectTimeoutMillis); + newSocket.connect(socketAddress, connectTimeoutMillis); if (socketOptions != null) { // Not sure which options must be applied before or after the connect() call. socketOptions.apply(newSocket); @@ -386,6 +353,7 @@ protected static Socket createSocket(final String host, final int port, final So return newSocket; } + /** * Data for the factory. */ @@ -422,7 +390,7 @@ public String toString() { /** * Factory to create a TcpSocketManager. - * + * * @param * The manager type. * @param @@ -431,10 +399,12 @@ public String toString() { protected static class TcpSocketManagerFactory implements ManagerFactory { + static volatile HostResolver RESOLVER = HostResolver.INSTANCE; + @SuppressWarnings("resource") @Override public M createManager(final String name, final T data) { - InetAddress inetAddress; + final InetAddress inetAddress; OutputStream os; try { inetAddress = InetAddress.getByName(data.host); @@ -449,7 +419,7 @@ public M createManager(final String name, final T data) { os = socket.getOutputStream(); return createManager(name, os, socket, inetAddress, data); } catch (final IOException ex) { - LOGGER.error("TcpSocketManager ({}) caught exception and will continue:", name, ex, ex); + LOGGER.error("TcpSocketManager ({}) caught exception and will continue:", name, ex); os = NullOutputStream.getInstance(); } if (data.reconnectDelayMillis == 0) { @@ -460,16 +430,70 @@ public M createManager(final String name, final T data) { } @SuppressWarnings("unchecked") - M createManager(final String name, OutputStream os, Socket socket, InetAddress inetAddress, final T data) { + M createManager(final String name, final OutputStream os, final Socket socket, final InetAddress inetAddress, final T data) { return (M) new TcpSocketManager(name, os, socket, inetAddress, data.host, data.port, data.connectTimeoutMillis, data.reconnectDelayMillis, data.immediateFail, data.layout, data.bufferSize, data.socketOptions); } Socket createSocket(final T data) throws IOException { - return TcpSocketManager.createSocket(data.host, data.port, data.socketOptions, data.connectTimeoutMillis); + final List socketAddresses = RESOLVER.resolveHost(data.host, data.port); + IOException ioe = null; + for (final InetSocketAddress socketAddress : socketAddresses) { + try { + return TcpSocketManager.createSocket(socketAddress, data.socketOptions, data.connectTimeoutMillis); + } catch (final IOException ex) { + ioe = ex; + } + } + throw new IOException(errorMessage(data, socketAddresses) , ioe); } + protected String errorMessage(final T data, final List socketAddresses) { + final StringBuilder sb = new StringBuilder("Unable to create socket for "); + sb.append(data.host).append(" at port ").append(data.port); + if (socketAddresses.size() == 1) { + if (!socketAddresses.get(0).getAddress().getHostAddress().equals(data.host)) { + sb.append(" using ip address ").append(socketAddresses.get(0).getAddress().getHostAddress()); + sb.append(" and port ").append(socketAddresses.get(0).getPort()); + } + } else { + sb.append(" using ip addresses and ports "); + for (int i = 0; i < socketAddresses.size(); ++i) { + if (i > 0) { + sb.append(", "); + sb.append(socketAddresses.get(i).getAddress().getHostAddress()); + sb.append(":").append(socketAddresses.get(i).getPort()); + } + } + } + return sb.toString(); + } + } + + /** + * This method is only for unit testing. It is not Thread-safe. + * @param resolver the HostResolver. + */ + public static void setHostResolver(HostResolver resolver) { + TcpSocketManagerFactory.RESOLVER = resolver; + } + + public static class HostResolver { + + /** + * Singleton instance. + */ + public static final HostResolver INSTANCE = new HostResolver(); + + public List resolveHost(final String host, final int port) throws UnknownHostException { + final InetAddress[] addresses = InetAddress.getAllByName(host); + final List socketAddresses = new ArrayList<>(addresses.length); + for (final InetAddress address: addresses) { + socketAddresses.add(new InetSocketAddress(address, port)); + } + return socketAddresses; + } } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java new file mode 100644 index 00000000000..445cfe356ca --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.JarURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import javax.net.ssl.HttpsURLConnection; + +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier; +import org.apache.logging.log4j.core.net.ssl.SslConfiguration; +import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory; +import org.apache.logging.log4j.core.util.AuthorizationProvider; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; +import org.apache.logging.log4j.util.Strings; + +/** + * Constructs an HTTPURLConnection. This class should be considered to be internal + */ +public class UrlConnectionFactory { + + private static final int DEFAULT_TIMEOUT = 60000; + private static final int connectTimeoutMillis = DEFAULT_TIMEOUT; + private static final int readTimeoutMillis = DEFAULT_TIMEOUT; + private static final String JSON = "application/json"; + private static final String XML = "application/xml"; + private static final String PROPERTIES = "text/x-java-properties"; + private static final String TEXT = "text/plain"; + private static final String HTTP = "http"; + private static final String HTTPS = "https"; + private static final String JAR = "jar"; + private static final String DEFAULT_ALLOWED_PROTOCOLS = "https, file, jar"; + private static final String NO_PROTOCOLS = "_none"; + public static final String ALLOWED_PROTOCOLS = "log4j2.Configuration.allowedProtocols"; + + @SuppressWarnings("unchecked") + public static T createConnection(final URL url, final long lastModifiedMillis, + final SslConfiguration sslConfiguration, final AuthorizationProvider authorizationProvider) + throws IOException { + final PropertyEnvironment props = PropertiesUtil.getProperties(); + final List allowed = Arrays.asList(Strings.splitList(props + .getStringProperty(ALLOWED_PROTOCOLS, DEFAULT_ALLOWED_PROTOCOLS).toLowerCase(Locale.ROOT))); + if (allowed.size() == 1 && NO_PROTOCOLS.equals(allowed.get(0))) { + throw new ProtocolException("No external protocols have been enabled"); + } + final String protocol = url.getProtocol(); + if (protocol == null) { + throw new ProtocolException("No protocol was specified on " + url.toString()); + } + if (!allowed.contains(protocol)) { + throw new ProtocolException("Protocol " + protocol + " has not been enabled as an allowed protocol"); + } + URLConnection urlConnection; + if (url.getProtocol().equals(HTTP) || url.getProtocol().equals(HTTPS)) { + final HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); + if (authorizationProvider != null) { + authorizationProvider.addAuthorization(httpURLConnection); + } + httpURLConnection.setAllowUserInteraction(false); + httpURLConnection.setDoOutput(true); + httpURLConnection.setDoInput(true); + httpURLConnection.setRequestMethod("GET"); + if (connectTimeoutMillis > 0) { + httpURLConnection.setConnectTimeout(connectTimeoutMillis); + } + if (readTimeoutMillis > 0) { + httpURLConnection.setReadTimeout(readTimeoutMillis); + } + final String[] fileParts = url.getFile().split("\\."); + final String type = fileParts[fileParts.length - 1].trim(); + final String contentType = isXml(type) ? XML : isJson(type) ? JSON : isProperties(type) ? PROPERTIES : TEXT; + httpURLConnection.setRequestProperty("Content-Type", contentType); + if (lastModifiedMillis > 0) { + httpURLConnection.setIfModifiedSince(lastModifiedMillis); + } + if (url.getProtocol().equals(HTTPS) && sslConfiguration != null) { + ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory()); + if (!sslConfiguration.isVerifyHostName()) { + ((HttpsURLConnection) httpURLConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE); + } + } + urlConnection = httpURLConnection; + } else if (url.getProtocol().equals(JAR)) { + urlConnection = url.openConnection(); + urlConnection.setUseCaches(false); + } else { + urlConnection = url.openConnection(); + } + return (T) urlConnection; + } + + public static URLConnection createConnection(final URL url) throws IOException { + URLConnection urlConnection = null; + if (url.getProtocol().equals(HTTPS) || url.getProtocol().equals(HTTP)) { + final AuthorizationProvider provider = ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties()); + urlConnection = createConnection(url, 0, SslConfigurationFactory.getSslConfiguration(), provider); + } else { + urlConnection = url.openConnection(); + if (urlConnection instanceof JarURLConnection) { + // A "jar:" URL file remains open after the stream is closed, so do not cache it. + urlConnection.setUseCaches(false); + } + } + return urlConnection; + } + + private static boolean isXml(final String type) { + return type.equalsIgnoreCase("xml"); + } + + private static boolean isJson(final String type) { + return type.equalsIgnoreCase("json") || type.equalsIgnoreCase("jsn"); + } + + private static boolean isProperties(final String type) { + return type.equalsIgnoreCase("properties"); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java index 5855026250e..4df86e7f8c4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java @@ -32,6 +32,8 @@ * Configuration of the KeyStore */ public class AbstractKeyStoreConfiguration extends StoreConfiguration { + static final char[] DEFAULT_PASSWORD = "changeit".toCharArray(); + private final KeyStore keyStore; private final String keyStoreType; @@ -42,24 +44,6 @@ public AbstractKeyStoreConfiguration(final String location, final PasswordProvid this.keyStore = this.load(); } - /** - * @deprecated Use {@link #AbstractKeyStoreConfiguration(String, PasswordProvider, String)} instead - */ - @Deprecated - public AbstractKeyStoreConfiguration(final String location, final char[] password, final String keyStoreType) - throws StoreConfigurationException { - this(location, new MemoryPasswordProvider(password), keyStoreType); - } - - /** - * @deprecated Use {@link #AbstractKeyStoreConfiguration(String, PasswordProvider, String)} instead - */ - @Deprecated - public AbstractKeyStoreConfiguration(final String location, final String password, final String keyStoreType) - throws StoreConfigurationException { - this(location, new MemoryPasswordProvider(password == null ? null : password.toCharArray()), keyStoreType); - } - @Override protected KeyStore load() throws StoreConfigurationException { final String loadLocation = this.getLocation(); @@ -70,9 +54,9 @@ protected KeyStore load() throws StoreConfigurationException { } try (final InputStream fin = openInputStream(loadLocation)) { final KeyStore ks = KeyStore.getInstance(this.keyStoreType); - char[] password = this.getPasswordAsCharArray(); + final char[] password = this.getPassword(); try { - ks.load(fin, password); + ks.load(fin, password != null ? password : DEFAULT_PASSWORD); } finally { if (password != null) { Arrays.fill(password, '\0'); @@ -94,7 +78,7 @@ protected KeyStore load() throws StoreConfigurationException { LOGGER.error("The keystore file {} is not found", loadLocation, e); throw new StoreConfigurationException(loadLocation, e); } catch (final IOException e) { - LOGGER.error("Something is wrong with the format of the keystore or the given password for location", loadLocation, e); + LOGGER.error("Something is wrong with the format of the keystore or the given password for location {}", loadLocation, e); throw new StoreConfigurationException(loadLocation, e); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProvider.java index d60f82cf2c5..da16622882e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProvider.java @@ -49,7 +49,7 @@ public EnvironmentPasswordProvider(final String passwordEnvironmentVariable) { @Override public char[] getPassword() { - String password = System.getenv(passwordEnvironmentVariable); + final String password = System.getenv(passwordEnvironmentVariable); return password == null ? null : password.toCharArray(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProvider.java index ff59b008e09..355a7816a26 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProvider.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/FilePasswordProvider.java @@ -65,14 +65,14 @@ public char[] getPassword() { byte[] bytes = null; try { bytes = Files.readAllBytes(passwordPath); - ByteBuffer bb = ByteBuffer.wrap(bytes); - CharBuffer decoded = Charset.defaultCharset().decode(bb); - char[] result = new char[decoded.limit()]; + final ByteBuffer bb = ByteBuffer.wrap(bytes); + final CharBuffer decoded = Charset.defaultCharset().decode(bb); + final char[] result = new char[decoded.limit()]; decoded.get(result, 0, result.length); decoded.rewind(); decoded.put(new char[result.length]); // erase decoded CharBuffer return result; - } catch (IOException e) { + } catch (final IOException e) { throw new IllegalStateException("Could not read password from " + passwordPath + ": " + e, e); } finally { if (bytes != null) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java index 0c9e3ce852e..3a3296a9a95 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java @@ -16,22 +16,22 @@ */ package org.apache.logging.log4j.core.net.ssl; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import javax.net.ssl.KeyManagerFactory; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.util.Arrays; -import javax.net.ssl.KeyManagerFactory; - -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Configuration of the KeyStore */ -@Plugin(name = "KeyStore", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("KeyStore") public class KeyStoreConfiguration extends AbstractKeyStoreConfiguration { private final String keyManagerFactoryAlgorithm; @@ -94,12 +94,12 @@ public KeyStoreConfiguration(final String location, final String password, final @PluginFactory public static KeyStoreConfiguration createKeyStoreConfiguration( // @formatter:off - @PluginAttribute("location") final String location, - @PluginAttribute(value = "password", sensitive = true) final char[] password, - @PluginAttribute("passwordEnvironmentVariable") final String passwordEnvironmentVariable, - @PluginAttribute("passwordFile") final String passwordFile, + @PluginAttribute final String location, + @PluginAttribute(sensitive = true) final char[] password, + @PluginAttribute final String passwordEnvironmentVariable, + @PluginAttribute final String passwordFile, @PluginAttribute("type") final String keyStoreType, - @PluginAttribute("keyManagerFactoryAlgorithm") final String keyManagerFactoryAlgorithm) throws StoreConfigurationException { + @PluginAttribute final String keyManagerFactoryAlgorithm) throws StoreConfigurationException { // @formatter:on if (password != null && passwordEnvironmentVariable != null && passwordFile != null) { @@ -107,7 +107,7 @@ public static KeyStoreConfiguration createKeyStoreConfiguration( } try { // @formatter:off - PasswordProvider provider = passwordFile != null + final PasswordProvider provider = passwordFile != null ? new FilePasswordProvider(passwordFile) : passwordEnvironmentVariable != null ? new EnvironmentPasswordProvider(passwordEnvironmentVariable) @@ -118,57 +118,17 @@ public static KeyStoreConfiguration createKeyStoreConfiguration( Arrays.fill(password, '\0'); } return new KeyStoreConfiguration(location, provider, keyStoreType, keyManagerFactoryAlgorithm); - } catch (Exception ex) { + } catch (final Exception ex) { throw new StoreConfigurationException("Could not configure KeyStore", ex); } } - /** - * @deprecated use {@link #createKeyStoreConfiguration(String, char[], String, String, String, String)} - */ - @Deprecated - public static KeyStoreConfiguration createKeyStoreConfiguration( - // @formatter:off - final String location, - final char[] password, - final String keyStoreType, - final String keyManagerFactoryAlgorithm) throws StoreConfigurationException { - // @formatter:on - return createKeyStoreConfiguration(location, password, null, null, keyStoreType, keyManagerFactoryAlgorithm); - } - - /** - * Creates a KeyStoreConfiguration. - * - * @param location The location of the KeyStore, a file path, URL or resource. - * @param password The password to access the KeyStore. - * @param keyStoreType The KeyStore type, null defaults to {@code "JKS"}. - * @param keyManagerFactoryAlgorithm The standard name of the requested algorithm. See the Java Secure Socket - * Extension Reference Guide for information about these names. - * @return a new KeyStoreConfiguration - * @throws StoreConfigurationException Thrown if this call cannot load the KeyStore. - * @deprecated Use createKeyStoreConfiguration(String, char[], String, String) - */ - @Deprecated - public static KeyStoreConfiguration createKeyStoreConfiguration( - // @formatter:off - final String location, - final String password, - final String keyStoreType, - final String keyManagerFactoryAlgorithm) throws StoreConfigurationException { - // @formatter:on - return createKeyStoreConfiguration(location, - (password == null ? null : password.toCharArray()), - keyStoreType, - keyManagerFactoryAlgorithm); - } - public KeyManagerFactory initKeyManagerFactory() throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { final KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(this.keyManagerFactoryAlgorithm); - char[] password = this.getPasswordAsCharArray(); + final char[] password = this.getPassword(); try { - kmFactory.init(this.getKeyStore(), password); + kmFactory.init(this.getKeyStore(), password != null ? password : DEFAULT_PASSWORD); } finally { if (password != null) { Arrays.fill(password, '\0'); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java index b12918493b2..46757f51bb9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java @@ -16,10 +16,12 @@ */ package org.apache.logging.log4j.core.net.ssl; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginElement; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -28,31 +30,31 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; - -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.status.StatusLogger; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; /** * SSL Configuration */ -@Plugin(name = "Ssl", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("Ssl") public class SslConfiguration { private static final StatusLogger LOGGER = StatusLogger.getLogger(); private final KeyStoreConfiguration keyStoreConfig; private final TrustStoreConfiguration trustStoreConfig; private final SSLContext sslContext; private final String protocol; + private final boolean verifyHostName; private SslConfiguration(final String protocol, final KeyStoreConfiguration keyStoreConfig, - final TrustStoreConfiguration trustStoreConfig) { + final TrustStoreConfiguration trustStoreConfig, final boolean verifyHostName) { this.keyStoreConfig = keyStoreConfig; this.trustStoreConfig = trustStoreConfig; this.protocol = protocol == null ? SslConfigurationDefaults.PROTOCOL : protocol; this.sslContext = this.createSslContext(); + this.verifyHostName = verifyHostName; } /** @@ -66,7 +68,7 @@ public void clearSecrets() { this.trustStoreConfig.clearSecrets(); } } - + public SSLSocketFactory getSslSocketFactory() { return sslContext.getSocketFactory(); } @@ -127,7 +129,7 @@ private SSLContext createSslContextWithDefaultKeyManagerFactory() throws TrustSt try { return createSslContext(true, false); } catch (final KeyStoreConfigurationException dummy) { - LOGGER.debug("Exception occured while using default keystore. This should be a BUG"); + LOGGER.debug("Exception occurred while using default keystore. This should be a BUG"); return null; } } @@ -137,7 +139,7 @@ private SSLContext createSslContextWithDefaultTrustManagerFactory() throws KeySt return createSslContext(false, true); } catch (final TrustStoreConfigurationException dummy) { - LOGGER.debug("Exception occured while using default truststore. This should be a BUG"); + LOGGER.debug("Exception occurred while using default truststore. This should be a BUG"); return null; } } @@ -219,7 +221,7 @@ private KeyManagerFactory loadKeyManagerFactory() throws KeyStoreConfigurationEx /** * Creates an SslConfiguration from a KeyStoreConfiguration and a TrustStoreConfiguration. - * + * * @param protocol The protocol, see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext * @param keyStoreConfig The KeyStoreConfiguration. * @param trustStoreConfig The TrustStoreConfiguration. @@ -228,11 +230,31 @@ private KeyManagerFactory loadKeyManagerFactory() throws KeyStoreConfigurationEx @PluginFactory public static SslConfiguration createSSLConfiguration( // @formatter:off - @PluginAttribute("protocol") final String protocol, - @PluginElement("KeyStore") final KeyStoreConfiguration keyStoreConfig, - @PluginElement("TrustStore") final TrustStoreConfiguration trustStoreConfig) { + @PluginAttribute final String protocol, + @PluginElement final KeyStoreConfiguration keyStoreConfig, + @PluginElement final TrustStoreConfiguration trustStoreConfig) { // @formatter:on - return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig); + return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, false); + } + + /** + * Creates an SslConfiguration from a KeyStoreConfiguration and a TrustStoreConfiguration. + * + * @param protocol The protocol, see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext + * @param keyStoreConfig The KeyStoreConfiguration. + * @param trustStoreConfig The TrustStoreConfiguration. + * @param verifyHostName whether or not to perform host name verification + * @return a new SslConfiguration + * @since 2.12 + */ + public static SslConfiguration createSSLConfiguration( + // @formatter:off + @PluginAttribute final String protocol, + @PluginElement final KeyStoreConfiguration keyStoreConfig, + @PluginElement final TrustStoreConfiguration trustStoreConfig, + @PluginAttribute final boolean verifyHostName) { + // @formatter:on + return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, verifyHostName); } @Override @@ -304,4 +326,8 @@ public SSLContext getSslContext() { public String getProtocol() { return protocol; } + + public boolean isVerifyHostName() { + return verifyHostName; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationDefaults.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationDefaults.java index 3c95eeb1e46..daa052e05a5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationDefaults.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationDefaults.java @@ -16,12 +16,15 @@ */ package org.apache.logging.log4j.core.net.ssl; +import java.security.KeyStore; + /** * */ public class SslConfigurationDefaults { - public static final String KEYSTORE_TYPE = "JKS"; - public static final String PROTOCOL = "SSL"; + public static final String KEYSTORE_TYPE = KeyStore.getDefaultType(); + // "TLS" uses all protocols available except those excluded in "jdk.tls.disabledAlgorithms" + public static final String PROTOCOL = "TLS"; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java new file mode 100644 index 00000000000..ff928168679 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.net.ssl; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Lazy; +import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.PropertyEnvironment; + +/** + * Creates an SSL configuration from Log4j properties. + */ +public class SslConfigurationFactory { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private static final Lazy SSL_CONFIGURATION = Lazy.lazy(() -> { + final PropertyEnvironment props = PropertiesUtil.getProperties(); + return createSslConfiguration(props); + }); + + static SslConfiguration createSslConfiguration(final PropertyEnvironment props) { + KeyStoreConfiguration keyStoreConfiguration = null; + TrustStoreConfiguration trustStoreConfiguration = null; + String location = props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_TRUST_STORE_LOCATION); + if (location != null) { + final String password = props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_TRUST_STORE_PASSWORD); + char[] passwordChars = null; + if (password != null) { + passwordChars = password.toCharArray(); + } + try { + trustStoreConfiguration = TrustStoreConfiguration.createKeyStoreConfiguration(location, passwordChars, + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_TRUST_STORE_PASSWORD_ENV_VAR), + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_TRUST_STORE_PASSWORD_FILE), + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_TRUST_STORE_KEY_STORE_TYPE), + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_TRUST_STORE_KEY_MANAGER_FACTORY_ALGORITHM)); + } catch (final Exception ex) { + LOGGER.warn("Unable to create trust store configuration due to: {} {}", ex.getClass().getName(), + ex.getMessage()); + } + } + location = props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_KEY_STORE_LOCATION); + if (location != null) { + final String password = props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_KEY_STORE_PASSWORD); + char[] passwordChars = null; + if (password != null) { + passwordChars = password.toCharArray(); + } + try { + keyStoreConfiguration = KeyStoreConfiguration.createKeyStoreConfiguration(location, passwordChars, + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_KEY_STORE_PASSWORD_ENV_VAR), + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_KEY_STORE_PASSWORD_FILE), + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_KEY_STORE_TYPE), + props.getStringProperty(Log4jProperties.TRANSPORT_SECURITY_KEY_STORE_KEY_MANAGER_FACTORY_ALGORITHM)); + } catch (final Exception ex) { + LOGGER.warn("Unable to create key store configuration due to: {} {}", ex.getClass().getName(), + ex.getMessage()); + } + } + if (trustStoreConfiguration != null || keyStoreConfiguration != null) { + final boolean isVerifyHostName = props.getBooleanProperty(Log4jProperties.TRANSPORT_SECURITY_VERIFY_HOST_NAME, false); + return SslConfiguration.createSSLConfiguration(null, keyStoreConfiguration, + trustStoreConfiguration, isVerifyHostName); + } + return null; + } + + public static SslConfiguration getSslConfiguration() { + return SSL_CONFIGURATION.value(); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java index 9bdeaf5fa9f..d1462f146a9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java @@ -35,22 +35,6 @@ public StoreConfiguration(final String location, final PasswordProvider password this.passwordProvider = Objects.requireNonNull(passwordProvider, "passwordProvider"); } - /** - * @deprecated Use {@link #StoreConfiguration(String, PasswordProvider)} - */ - @Deprecated - public StoreConfiguration(final String location, final char[] password) { - this(location, new MemoryPasswordProvider(password)); - } - - /** - * @deprecated Use {@link #StoreConfiguration(String, PasswordProvider)} - */ - @Deprecated - public StoreConfiguration(final String location, final String password) { - this(location, new MemoryPasswordProvider(password == null ? null : password.toCharArray())); - } - /** * Clears the secret fields in this object. */ @@ -67,16 +51,7 @@ public void setLocation(final String location) { this.location = location; } - /** - * - * @deprecated Use getPasswordAsCharArray() - */ - @Deprecated - public String getPassword() { - return String.valueOf(this.passwordProvider.getPassword()); - } - - public char[] getPasswordAsCharArray() { + public char[] getPassword() { return this.passwordProvider.getPassword(); } @@ -84,15 +59,6 @@ public void setPassword(final char[] password) { this.passwordProvider = new MemoryPasswordProvider(password); } - /** - * - * @deprecated Use getPasswordAsCharArray() - */ - @Deprecated - public void setPassword(final String password) { - this.passwordProvider = new MemoryPasswordProvider(password == null ? null : password.toCharArray()); - } - /** * @throws StoreConfigurationException May be thrown by subclasses */ diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationException.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationException.java index 793e6dfd846..f6351451160 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationException.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfigurationException.java @@ -30,7 +30,7 @@ public StoreConfigurationException(final String message) { super(message); } - public StoreConfigurationException(String message, Exception e) { + public StoreConfigurationException(final String message, final Exception e) { super(message, e); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java index b5c282b0b00..81db690aecf 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java @@ -16,21 +16,21 @@ */ package org.apache.logging.log4j.core.net.ssl; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; + +import javax.net.ssl.TrustManagerFactory; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import javax.net.ssl.TrustManagerFactory; - -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; - /** * Configuration of the TrustStore */ -@Plugin(name = "TrustStore", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("TrustStore") public class TrustStoreConfiguration extends AbstractKeyStoreConfiguration { private final String trustManagerFactoryAlgorithm; @@ -56,16 +56,6 @@ public TrustStoreConfiguration(final String location, final char[] password, fin } } - /** - * @deprecated Use {@link #TrustStoreConfiguration(String, PasswordProvider, String, String)} instead - */ - @Deprecated - public TrustStoreConfiguration(final String location, final String password, final String keyStoreType, - final String trustManagerFactoryAlgorithm) throws StoreConfigurationException { - this(location, new MemoryPasswordProvider(password == null ? null : password.toCharArray()), keyStoreType, - trustManagerFactoryAlgorithm); - } - /** * Creates a KeyStoreConfiguration. * @@ -83,12 +73,12 @@ public TrustStoreConfiguration(final String location, final String password, fin @PluginFactory public static TrustStoreConfiguration createKeyStoreConfiguration( // @formatter:off - @PluginAttribute("location") final String location, - @PluginAttribute(value = "password", sensitive = true) final char[] password, - @PluginAttribute("passwordEnvironmentVariable") final String passwordEnvironmentVariable, - @PluginAttribute("passwordFile") final String passwordFile, + @PluginAttribute final String location, + @PluginAttribute(sensitive = true) final char[] password, + @PluginAttribute final String passwordEnvironmentVariable, + @PluginAttribute final String passwordFile, @PluginAttribute("type") final String keyStoreType, - @PluginAttribute("trustManagerFactoryAlgorithm") final String trustManagerFactoryAlgorithm) throws StoreConfigurationException { + @PluginAttribute final String trustManagerFactoryAlgorithm) throws StoreConfigurationException { // @formatter:on if (password != null && passwordEnvironmentVariable != null && passwordFile != null) { @@ -96,7 +86,7 @@ public static TrustStoreConfiguration createKeyStoreConfiguration( } try { // @formatter:off - PasswordProvider provider = passwordFile != null + final PasswordProvider provider = passwordFile != null ? new FilePasswordProvider(passwordFile) : passwordEnvironmentVariable != null ? new EnvironmentPasswordProvider(passwordEnvironmentVariable) @@ -107,49 +97,11 @@ public static TrustStoreConfiguration createKeyStoreConfiguration( Arrays.fill(password, '\0'); } return new TrustStoreConfiguration(location, provider, keyStoreType, trustManagerFactoryAlgorithm); - } catch (Exception ex) { + } catch (final Exception ex) { throw new StoreConfigurationException("Could not configure TrustStore", ex); } } - /** - * @deprecated Use {@link #createKeyStoreConfiguration(String, char[], String, String, String, String)} - */ - @Deprecated - public static TrustStoreConfiguration createKeyStoreConfiguration( - // @formatter:off - final String location, - final char[] password, - final String keyStoreType, - final String trustManagerFactoryAlgorithm) throws StoreConfigurationException { - // @formatter:on - return createKeyStoreConfiguration(location, password, null, null, keyStoreType, trustManagerFactoryAlgorithm); - } - - /** - * Creates a KeyStoreConfiguration. - * - * @param location The location of the KeyStore, a file path, URL or resource. - * @param password The password to access the KeyStore. - * @param keyStoreType The KeyStore type, null defaults to {@code "JKS"}. - * @param trustManagerFactoryAlgorithm The standard name of the requested trust management algorithm. See the Java - * Secure Socket Extension Reference Guide for information these names. - * @return a new TrustStoreConfiguration - * @throws StoreConfigurationException Thrown if this instance cannot load the KeyStore. - * @deprecated Use createKeyStoreConfiguration(String, char[], String, String) - */ - @Deprecated - public static TrustStoreConfiguration createKeyStoreConfiguration( - // @formatter:off - final String location, - final String password, - final String keyStoreType, - final String trustManagerFactoryAlgorithm) throws StoreConfigurationException { - // @formatter:on - return createKeyStoreConfiguration(location, (password == null ? null : password.toCharArray()), - null, null, keyStoreType, trustManagerFactoryAlgorithm); - } - public TrustManagerFactory initTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException { final TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(this.trustManagerFactoryAlgorithm); tmFactory.init(this.getKeyStore()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java index 842447100e8..93e6359b9a1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java @@ -17,4 +17,4 @@ /** * Log4j 2 SSL support */ -package org.apache.logging.log4j.core.net.ssl; \ No newline at end of file +package org.apache.logging.log4j.core.net.ssl; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java index 816ce378ff1..ef0ebaa17d4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java @@ -14,101 +14,81 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.osgi; import java.util.Hashtable; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry; -import org.apache.logging.log4j.core.impl.Log4jProvider; -import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.spi.Provider; -import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.util.ContextDataProvider; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.plugins.di.InjectorCallback; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.model.PluginRegistry; +import org.apache.logging.log4j.plugins.model.PluginService; import org.apache.logging.log4j.util.PropertiesUtil; +import org.apache.logging.log4j.util.ServiceRegistry; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; import org.osgi.framework.ServiceRegistration; -import org.osgi.framework.SynchronousBundleListener; import org.osgi.framework.wiring.BundleWiring; /** * OSGi BundleActivator. */ -public final class Activator implements BundleActivator, SynchronousBundleListener { - - private static final Logger LOGGER = StatusLogger.getLogger(); - +public final class Activator implements BundleActivator { private final AtomicReference contextRef = new AtomicReference<>(); - - ServiceRegistration provideRegistration = null; + private ServiceRegistration pluginRegistryServiceRegistration; + private PluginRegistry pluginRegistry; + private ServiceRegistration injectorCallbackServiceRegistration; + private InjectorCallback injectorCallback; @Override public void start(final BundleContext context) throws Exception { - final Provider provider = new Log4jProvider(); - final Hashtable props = new Hashtable<>(); - props.put("APIVersion", "2.60"); - provideRegistration = context.registerService(Provider.class.getName(), provider, props); - // allow the user to override the default ContextSelector (e.g., by using BasicContextSelector for a global cfg) - if (PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR) == null) { - System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, BundleContextSelector.class.getName()); - } - if (this.contextRef.compareAndSet(null, context)) { - context.addBundleListener(this); - // done after the BundleListener as to not miss any new bundle installs in the interim - scanInstalledBundlesForPlugins(context); - } - } - - private static void scanInstalledBundlesForPlugins(final BundleContext context) { - final Bundle[] bundles = context.getBundles(); - for (final Bundle bundle : bundles) { - // TODO: bundle state can change during this - scanBundleForPlugins(bundle); - } - } + pluginRegistryServiceRegistration = context.registerService(PluginRegistry.class, new PluginRegistry(), new Hashtable<>()); + pluginRegistry = context.getService(pluginRegistryServiceRegistration.getReference()); + injectorCallbackServiceRegistration = context.registerService(InjectorCallback.class, new InjectorCallback() { + @Override + public void configure(final Injector injector) { + injector.registerBinding(Key.forClass(PluginRegistry.class), + () -> context.getService(pluginRegistryServiceRegistration.getReference())); + } - private static void scanBundleForPlugins(final Bundle bundle) { + @Override + public int getOrder() { + return -50; + } + }, new Hashtable<>()); + injectorCallback = context.getService(injectorCallbackServiceRegistration.getReference()); + final ServiceRegistry registry = ServiceRegistry.getInstance(); + final Bundle bundle = context.getBundle(); final long bundleId = bundle.getBundleId(); - // LOG4J2-920: don't scan system bundle for plugins - if (bundle.getState() == Bundle.ACTIVE && bundleId != 0) { - LOGGER.trace("Scanning bundle [{}, id=%d] for plugins.", bundle.getSymbolicName(), bundleId); - PluginRegistry.getInstance().loadFromBundle(bundleId, - bundle.adapt(BundleWiring.class).getClassLoader()); + final ClassLoader classLoader = bundle.adapt(BundleWiring.class).getClassLoader(); + registry.registerBundleServices(InjectorCallback.class, bundleId, List.of(injectorCallback)); + registry.loadServicesFromBundle(PluginService.class, bundleId, classLoader); + registry.loadServicesFromBundle(ContextDataProvider.class, bundleId, classLoader); + registry.loadServicesFromBundle(InjectorCallback.class, bundleId, classLoader); + // allow the user to override the default ContextSelector (e.g., by using BasicContextSelector for a global cfg) + if (PropertiesUtil.getProperties().getStringProperty(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME) == null) { + System.setProperty(Log4jProperties.CONTEXT_SELECTOR_CLASS_NAME, BundleContextSelector.class.getName()); } - } - - private static void stopBundlePlugins(final Bundle bundle) { - LOGGER.trace("Stopping bundle [{}] plugins.", bundle.getSymbolicName()); - // TODO: plugin lifecycle code - PluginRegistry.getInstance().clearBundlePlugins(bundle.getBundleId()); + contextRef.compareAndSet(null, context); } @Override public void stop(final BundleContext context) throws Exception { - provideRegistration.unregister(); - this.contextRef.compareAndSet(context, null); - LogManager.shutdown(); - } - - @Override - public void bundleChanged(final BundleEvent event) { - switch (event.getType()) { - // FIXME: STARTING instead of STARTED? - case BundleEvent.STARTED: - scanBundleForPlugins(event.getBundle()); - break; - - case BundleEvent.STOPPING: - stopBundlePlugins(event.getBundle()); - break; - - default: - break; + if (injectorCallback != null) { + injectorCallback = null; + injectorCallbackServiceRegistration.unregister(); } + if (pluginRegistry != null) { + pluginRegistry = null; + pluginRegistryServiceRegistration.unregister(); + } + this.contextRef.compareAndSet(context, null); + LogManager.shutdown(false, true); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java index 5e229229b11..718c0bf6fc0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java @@ -19,11 +19,15 @@ import java.lang.ref.WeakReference; import java.net.URI; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.impl.ContextAnchor; import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.di.Injector; import org.apache.logging.log4j.util.StackLocatorUtil; import org.osgi.framework.Bundle; import org.osgi.framework.BundleReference; @@ -36,7 +40,80 @@ * * @since 2.1 */ +@Singleton public class BundleContextSelector extends ClassLoaderContextSelector { + @Inject + public BundleContextSelector(final Injector injector) { + super(injector); + } + + @Override + public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext, + final boolean allContexts) { + LoggerContext ctx = null; + Bundle bundle = null; + if (currentContext) { + ctx = ContextAnchor.THREAD_CONTEXT.get(); + ContextAnchor.THREAD_CONTEXT.remove(); + } + if (ctx == null && loader instanceof BundleReference) { + bundle = ((BundleReference) loader).getBundle(); + ctx = getLoggerContext(bundle); + removeLoggerContext(ctx); + } + if (ctx == null) { + final Class callerClass = StackLocatorUtil.getCallerClass(fqcn); + if (callerClass != null) { + bundle = FrameworkUtil.getBundle(callerClass); + ctx = getLoggerContext(FrameworkUtil.getBundle(callerClass)); + removeLoggerContext(ctx); + } + } + if (ctx == null) { + ctx = ContextAnchor.THREAD_CONTEXT.get(); + ContextAnchor.THREAD_CONTEXT.remove(); + } + if (ctx != null) { + ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS); + } + if (bundle != null && allContexts) { + final Bundle[] bundles = bundle.getBundleContext().getBundles(); + for (final Bundle bdl : bundles) { + ctx = getLoggerContext(bdl); + if (ctx != null) { + ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS); + } + } + } + } + + private LoggerContext getLoggerContext(final Bundle bundle) { + final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName(); + final AtomicReference> ref = contextMap.get(name); + if (ref != null && ref.get() != null) { + return ref.get().get(); + } + return null; + } + + private void removeLoggerContext(final LoggerContext context) { + contextMap.remove(context.getName()); + } + + @Override + public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + if (currentContext && ContextAnchor.THREAD_CONTEXT.get() != null) { + return ContextAnchor.THREAD_CONTEXT.get().isStarted(); + } + if (loader instanceof BundleReference) { + return hasContext(((BundleReference) loader).getBundle()); + } + final Class callerClass = StackLocatorUtil.getCallerClass(fqcn); + if (callerClass != null) { + return hasContext(FrameworkUtil.getBundle(callerClass)); + } + return ContextAnchor.THREAD_CONTEXT.get() != null && ContextAnchor.THREAD_CONTEXT.get().isStarted(); + } @Override public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, @@ -60,14 +137,20 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin return lc == null ? getDefault() : lc; } - private static LoggerContext locateContext(final Bundle bundle, final URI configLocation) { + private boolean hasContext(final Bundle bundle) { + final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName(); + final AtomicReference> ref = contextMap.get(name); + return ref != null && ref.get() != null && ref.get().get() != null && ref.get().get().isStarted(); + } + + private LoggerContext locateContext(final Bundle bundle, final URI configLocation) { final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName(); - final AtomicReference> ref = CONTEXT_MAP.get(name); + final AtomicReference> ref = contextMap.get(name); if (ref == null) { final LoggerContext context = new LoggerContext(name, bundle, configLocation); - CONTEXT_MAP.putIfAbsent(name, + contextMap.putIfAbsent(name, new AtomicReference<>(new WeakReference<>(context))); - return CONTEXT_MAP.get(name).get().get(); + return contextMap.get(name).get().get(); } final WeakReference r = ref.get(); final LoggerContext ctx = r.get(); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java index d7963e5902b..7c7e8ce05fa 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java @@ -18,4 +18,4 @@ /** * Collection of OSGi-specific classes for bundles. */ -package org.apache.logging.log4j.core.osgi; \ No newline at end of file +package org.apache.logging.log4j.core.osgi; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/AbstractJacksonLogEventParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/AbstractJacksonLogEventParser.java index c7643680610..8c36c1100e7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/AbstractJacksonLogEventParser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/AbstractJacksonLogEventParser.java @@ -24,10 +24,10 @@ import java.io.IOException; -class AbstractJacksonLogEventParser implements TextLogEventParser { +public class AbstractJacksonLogEventParser implements TextLogEventParser { private final ObjectReader objectReader; - AbstractJacksonLogEventParser(final ObjectMapper objectMapper) { + protected AbstractJacksonLogEventParser(final ObjectMapper objectMapper) { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); this.objectReader = objectMapper.readerFor(Log4jLogEvent.class); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/TextLogEventParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/TextLogEventParser.java index ca20335f706..2ef2bc3dd79 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/TextLogEventParser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/parser/TextLogEventParser.java @@ -22,7 +22,7 @@ * Parses the output from a text based layout into instances of {@link LogEvent}. */ public interface TextLogEventParser extends LogEventParser { - + /** * Parses a String, which is expected to contain exactly one log event. * diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java index 2e1db29bda6..350daf8b086 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java @@ -23,8 +23,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -52,7 +53,8 @@ protected AbstractStyleNameConverter(final String name, final List T newInstance(final Clas try { final Constructor constructor = asnConverterClass.getConstructor(List.class, String.class); return constructor.newInstance(formatters, AnsiEscape.createSequence(name)); - } catch (final SecurityException e) { - LOGGER.error(e.toString(), e); - } catch (final NoSuchMethodException e) { - LOGGER.error(e.toString(), e); - } catch (final IllegalArgumentException e) { - LOGGER.error(e.toString(), e); - } catch (final InstantiationException e) { - LOGGER.error(e.toString(), e); - } catch (final IllegalAccessException e) { - LOGGER.error(e.toString(), e); - } catch (final InvocationTargetException e) { + } catch (final SecurityException | NoSuchMethodException | IllegalArgumentException | InstantiationException + | IllegalAccessException | InvocationTargetException e) { LOGGER.error(e.toString(), e); } return null; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java index 30f580f5d4f..64e11693112 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java @@ -17,13 +17,15 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; /** * Formats the class name of the site of the logging request. */ -@Plugin(name = "ClassNamePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("ClassNamePatternConverter") @ConverterKeys({ "C", "class" }) public final class ClassNamePatternConverter extends NamePatternConverter { @@ -64,4 +66,9 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) { abbreviate(element.getClassName(), toAppendTo); } } + + @Override + public boolean requiresLocation() { + return true; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java index ca65d4d7dc2..6612452133d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java @@ -18,24 +18,28 @@ import java.util.Arrays; import java.util.Date; +import java.util.Locale; import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.time.Instant; import org.apache.logging.log4j.core.time.MutableInstant; -import org.apache.logging.log4j.core.util.datetime.FastDateFormat; -import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; -import org.apache.logging.log4j.core.util.datetime.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.core.time.internal.format.FastDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; +import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled; + /** * Converts and formats the event's date in a StringBuilder. */ -@Plugin(name = "DatePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("DatePatternConverter") @ConverterKeys({"d", "date"}) @PerformanceSensitive("allocation") public final class DatePatternConverter extends LogEventPatternConverter implements ArrayPatternConverter { @@ -88,7 +92,7 @@ private static final class FixedFormatter extends Formatter { private final FixedDateFormat fixedDateFormat; // below fields are only used in ThreadLocal caching mode - private final char[] cachedBuffer = new char[64]; // max length of formatted date-time in any format < 64 + private final char[] cachedBuffer = new char[70]; // max length of formatted date-time in any format < 70 private int length = 0; FixedFormatter(final FixedDateFormat fixedDateFormat) { @@ -104,7 +108,7 @@ String format(final Instant instant) { void formatToBuffer(final Instant instant, final StringBuilder destination) { final long epochSecond = instant.getEpochSecond(); final int nanoOfSecond = instant.getNanoOfSecond(); - if (previousTime != epochSecond || nanos != nanoOfSecond) { + if (!fixedDateFormat.isEquivalent(previousTime, nanos, epochSecond, nanoOfSecond)) { length = fixedDateFormat.formatInstant(instant, cachedBuffer, 0); previousTime = epochSecond; nanos = nanoOfSecond; @@ -145,9 +149,9 @@ void formatToBuffer(final Instant instant, final StringBuilder destination) { } private final class CachedTime { - public long epochSecond; - public int nanoOfSecond; - public String formatted; + public final long epochSecond; + public final int nanoOfSecond; + public final String formatted; public CachedTime(final Instant instant) { this.epochSecond = instant.getEpochSecond(); @@ -184,7 +188,7 @@ private DatePatternConverter(final String[] options) { cachedTime = new AtomicReference<>(fromEpochMillis(System.currentTimeMillis())); } - private CachedTime fromEpochMillis(long epochMillis) { + private CachedTime fromEpochMillis(final long epochMillis) { final MutableInstant temp = new MutableInstant(); temp.initFromEpochMilli(epochMillis, 0); return new CachedTime(temp); @@ -216,7 +220,7 @@ private static Formatter createNonFixedFormatter(final String[] options) { // if we get here, options is a non-null array with at least one element (first of which non-null) Objects.requireNonNull(options); if (options.length == 0) { - throw new IllegalArgumentException("options array must have at least one element"); + throw new IllegalArgumentException("Options array must have at least one element"); } Objects.requireNonNull(options[0]); final String patternOption = options[0]; @@ -236,8 +240,13 @@ private static Formatter createNonFixedFormatter(final String[] options) { tz = TimeZone.getTimeZone(options[1]); } + Locale locale = null; + if (options.length > 2 && options[2] != null) { + locale = Locale.forLanguageTag(options[2]); + } + try { - final FastDateFormat tempFormat = FastDateFormat.getInstance(pattern, tz); + final FastDateFormat tempFormat = FastDateFormat.getInstance(pattern, tz, locale); return new PatternFormatter(tempFormat); } catch (final IllegalArgumentException e) { LOGGER.warn("Could not instantiate FastDateFormat with pattern " + pattern, e); @@ -266,26 +275,25 @@ public void format(final LogEvent event, final StringBuilder output) { } public void format(final long epochMilli, final StringBuilder output) { - MutableInstant instant = getMutableInstant(); + final MutableInstant instant = getMutableInstant(); instant.initFromEpochMilli(epochMilli, 0); format(instant, output); } private MutableInstant getMutableInstant() { - if (Constants.ENABLE_THREADLOCALS) { + if (isThreadLocalsEnabled()) { MutableInstant result = threadLocalMutableInstant.get(); if (result == null) { result = new MutableInstant(); threadLocalMutableInstant.set(result); } return result; - } else { - return new MutableInstant(); } + return new MutableInstant(); } public void format(final Instant instant, final StringBuilder output) { - if (Constants.ENABLE_THREADLOCALS) { + if (isThreadLocalsEnabled()) { formatWithoutAllocation(instant, output); } else { formatWithoutThreadLocals(instant, output); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviator.java new file mode 100644 index 00000000000..305731e93ae --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DynamicWordAbbreviator.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + *

    Specialized abbreviator that shortens all words to the first char except the indicated number of rightmost words. + * To select this abbreviator, use pattern 1.n* where n (> 0) is the number of rightmost words to leave unchanged.

    + * + * By example for input org.apache.logging.log4j.core.pattern.NameAbbreviator:
    + *
    + * 1.1*     =>   o.a.l.l.c.p.NameAbbreviator
    + * 1.2*     =>   o.a.l.l.c.pattern.NameAbbreviator
    + * 1.3*     =>   o.a.l.l.core.pattern.NameAbbreviator
    + * ..
    + * 1.999*   =>   org.apache.logging.log4j.core.pattern.NameAbbreviator
    + * 
    + * @since 2.19.1 + */ +class DynamicWordAbbreviator extends NameAbbreviator { + + /** Right-most number of words (at least one) that will not be abbreviated. */ + private final int rightWordCount; + + static DynamicWordAbbreviator create(String pattern) { + if (pattern != null) { + Matcher matcher = Pattern.compile("1\\.([1-9][0-9]*)\\*").matcher(pattern); + if (matcher.matches()) { + return new DynamicWordAbbreviator(Integer.parseInt(matcher.group(1))); + } + } + return null; + } + + private DynamicWordAbbreviator(int rightWordCount) { + this.rightWordCount = rightWordCount; + } + + @Override + public void abbreviate(final String original, final StringBuilder destination) { + if (original == null || destination == null) { + return; + } + + // for efficiency refrain from using String#split or StringTokenizer + final String[] words = split(original, '.'); + final int wordCount = words.length; + + if (rightWordCount >= wordCount) { + // nothing to abbreviate + destination.append(original); + return; + } + + final int lastAbbrevIdx = wordCount - rightWordCount; // last index to abbreviate + for (int i = 0; i < wordCount; i++) { + if (i >= lastAbbrevIdx) { + destination.append(words[i]); + if (i < wordCount - 1) { + destination.append("."); + } + } else if (words[i].length() > 0) { + destination.append(words[i].charAt(0)) + .append("."); + } + } + } + + static String[] split(final String input, final char delim) { + if (input == null) { + return null; + } else if (input.isEmpty()) { + return new String[0]; + } + + int countDelim = input.chars().filter(c -> c == delim).map(c -> 1).sum(); + final String[] tokens = new String[countDelim + 1]; + + int countToken = 0; + int idxBegin = 0; + int idxDelim = 0; + + while ((idxDelim = input.indexOf(delim, idxBegin)) > -1) { + if (idxBegin < idxDelim) { + tokens[countToken++] = input.substring(idxBegin, idxDelim); + } + idxBegin = idxDelim + 1; + } + + if (idxBegin < input.length()) { // remains + tokens[countToken++] = input.substring(idxBegin); + } + + if (countToken < tokens.length) { + return Arrays.copyOf(tokens, countToken); + } + + return tokens; + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java index 941ecca9788..0723e22d3e0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java @@ -20,8 +20,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.EnglishEnums; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilders; @@ -30,7 +31,8 @@ * Converter that encodes the output from a pattern using a specified format. Supported formats include HTML * (default) and JSON. */ -@Plugin(name = "encode", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("encode") @ConverterKeys({"enc", "encode"}) @PerformanceSensitive("allocation") public final class EncodingPatternConverter extends LogEventPatternConverter { @@ -51,6 +53,13 @@ private EncodingPatternConverter(final List formatters, this.escapeFormat = escapeFormat; } + @Override + public boolean handlesThrowable() { + return formatters != null && formatters.stream() + .map(PatternFormatter::getConverter) + .anyMatch(LogEventPatternConverter::handlesThrowable); + } + /** * Creates an EncodingPatternConverter using a pattern string and an optional escape format. * @@ -91,44 +100,57 @@ private enum EscapeFormat { HTML { @Override void escape(final StringBuilder toAppendTo, final int start) { - for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change + + // do this in two passes to keep O(n) time complexity + + final int origLength = toAppendTo.length(); + int firstSpecialChar = origLength; + + for (int i = origLength - 1; i >= start; i--) { final char c = toAppendTo.charAt(i); - switch (c) { - case '\r': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'r'); - break; - case '\n': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'n'); - break; - case '&': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "amp;"); - break; - case '<': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "lt;"); - break; - case '>': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "gt;"); - break; - case '"': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "quot;"); - break; - case '\'': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "apos;"); - break; - case '/': - toAppendTo.setCharAt(i, '&'); - toAppendTo.insert(i + 1, "#x2F;"); - break; + final String escaped = escapeChar(c); + if (escaped != null) { + firstSpecialChar = i; + for (int j = 0; j < escaped.length() - 1; j++) { + toAppendTo.append(' '); // make room for the escape sequence + } + } + } + + for (int i = origLength - 1, j = toAppendTo.length(); i >= firstSpecialChar; i--) { + final char c = toAppendTo.charAt(i); + final String escaped = escapeChar(c); + if (escaped == null) { + toAppendTo.setCharAt(--j, c); + } else { + toAppendTo.replace(j - escaped.length(), j, escaped); + j -= escaped.length(); } } } + + private String escapeChar(char c) { + switch (c) { + case '\r': + return "\\r"; + case '\n': + return "\\n"; + case '&': + return "&"; + case '<': + return "<"; + case '>': + return ">"; + case '"': + return """; + case '\'': + return "'"; + case '/': + return "/"; + default: + return null; + } + } }, /** @@ -146,17 +168,33 @@ void escape(final StringBuilder toAppendTo, final int start) { CRLF { @Override void escape(final StringBuilder toAppendTo, final int start) { - for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change + + // do this in two passes to keep O(n) time complexity + + final int origLength = toAppendTo.length(); + int firstSpecialChar = origLength; + + for (int i = origLength - 1; i >= start; i--) { + final char c = toAppendTo.charAt(i); + if (c == '\r' || c == '\n') { + firstSpecialChar = i; + toAppendTo.append(' '); // make room for the escape sequence + } + } + + for (int i = origLength - 1, j = toAppendTo.length(); i >= firstSpecialChar; i--) { final char c = toAppendTo.charAt(i); switch (c) { case '\r': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'r'); + toAppendTo.setCharAt(--j, 'r'); + toAppendTo.setCharAt(--j, '\\'); break; case '\n': - toAppendTo.setCharAt(i, '\\'); - toAppendTo.insert(i + 1, 'n'); + toAppendTo.setCharAt(--j, 'n'); + toAppendTo.setCharAt(--j, '\\'); break; + default: + toAppendTo.setCharAt(--j, c); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java index a2f95fde1a0..a77c1830bde 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java @@ -17,15 +17,17 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Formats the EndOfBatch. * - * @since 2.10.1 + * @since 2.11.0 */ -@Plugin(name = "EndOfBatchPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("EndOfBatchPatternConverter") @ConverterKeys({ "endOfBatch" }) @PerformanceSensitive("allocation") public final class EndOfBatchPatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java index 8e30ac59b70..5e36512a844 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java @@ -19,15 +19,17 @@ import java.util.List; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilders; /** * Equals ignore case pattern converter. */ -@Plugin(name = "equalsIgnoreCase", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("equalsIgnoreCase") @ConverterKeys({ "equalsIgnoreCase" }) @PerformanceSensitive("allocation") public final class EqualsIgnoreCaseReplacementConverter extends EqualsBaseReplacementConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java index da2938ab802..2cb82254235 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java @@ -19,15 +19,17 @@ import java.util.List; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilders; /** * Equals pattern converter. */ -@Plugin(name = "equals", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("equals") @ConverterKeys({ "equals" }) @PerformanceSensitive("allocation") public final class EqualsReplacementConverter extends EqualsBaseReplacementConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java index 31967583707..afdf3280cae 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java @@ -18,8 +18,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.Strings; /** @@ -30,13 +31,14 @@ * The extended stack trace will also include the location of where the class was loaded from and the * version of the jar if available. */ -@Plugin(name = "ExtendedThrowablePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("ExtendedThrowablePatternConverter") @ConverterKeys({ "xEx", "xThrowable", "xException" }) public final class ExtendedThrowablePatternConverter extends ThrowablePatternConverter { /** * Private constructor. - * + * * @param config * @param options options, may be null. */ @@ -68,13 +70,28 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) { super.format(event, toAppendTo); return; } - final String extStackTrace = proxy.getExtendedStackTraceAsString(options.getIgnorePackages(), - options.getTextRenderer(), getSuffix(event), options.getSeparator()); final int len = toAppendTo.length(); if (len > 0 && !Character.isWhitespace(toAppendTo.charAt(len - 1))) { toAppendTo.append(' '); } - toAppendTo.append(extStackTrace); + proxy.formatExtendedStackTraceTo(toAppendTo, options.getIgnorePackages(), + options.getTextRenderer(), getSuffix(event), options.getSeparator()); + final String trace = proxy.getExtendedStackTraceAsString(options.getIgnorePackages(), + options.getTextRenderer(), getSuffix(event), options.getSeparator()); + if (!options.allLines() || !Strings.LINE_SEPARATOR.equals(options.getSeparator())) { + final StringBuilder sb = new StringBuilder(); + final String[] array = trace.split(Strings.LINE_SEPARATOR); + final int limit = options.minLines(array.length) - 1; + for (int i = 0; i <= limit; ++i) { + sb.append(array[i]); + if (i < limit) { + sb.append(options.getSeparator()); + } + } + toAppendTo.append(sb.toString()); + } else { + toAppendTo.append(trace); + } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java index c9eb0396761..0b9cbde9de8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java @@ -16,7 +16,8 @@ */ package org.apache.logging.log4j.core.pattern; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -24,14 +25,19 @@ * date pattern for a %d specifier in a file name is different than * the %d pattern in pattern layout. */ -@Plugin(name = "FileDatePatternConverter", category = "FileConverter") +@Namespace("FileConverter") +@Plugin("FileDatePatternConverter") @ConverterKeys({ "d", "date" }) @PerformanceSensitive("allocation") -public final class FileDatePatternConverter { +public final class FileDatePatternConverter implements ArrayPatternConverter { + + private final DatePatternConverter delegate; + /** * Private constructor. */ - private FileDatePatternConverter() { + private FileDatePatternConverter(final String... options) { + delegate = DatePatternConverter.newInstance(options); } /** @@ -40,14 +46,35 @@ private FileDatePatternConverter() { * @param options options, may be null. * @return instance of pattern converter. */ - public static PatternConverter newInstance(final String[] options) { + public static FileDatePatternConverter newInstance(final String[] options) { if (options == null || options.length == 0) { - return DatePatternConverter.newInstance( - new String[]{ - "yyyy-MM-dd" - }); + return new FileDatePatternConverter("yyyy-MM-dd"); } - return DatePatternConverter.newInstance(options); + return new FileDatePatternConverter(options); + } + + @Override + public void format(final Object obj, final StringBuilder toAppendTo) { + delegate.format(obj, toAppendTo); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public String getStyleClass(final Object e) { + return delegate.getStyleClass(e); + } + + @Override + public void format(final StringBuilder toAppendTo, final Object... objects) { + delegate.format(toAppendTo, objects); + } + + public String getPattern() { + return delegate.getPattern(); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java index 7fcd33d53bf..4ae6cb9bafe 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java @@ -17,13 +17,15 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; /** * Returns the event's line location information in a StringBuilder. */ -@Plugin(name = "FileLocationPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("FileLocationPatternConverter") @ConverterKeys({ "F", "file" }) public final class FileLocationPatternConverter extends LogEventPatternConverter { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java index 0b9f2b90d38..73d9c08f81a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.util.PerformanceSensitive; @@ -29,6 +28,11 @@ public final class FormattingInfo { */ private static final char[] SPACES = new char[] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }; + /** + * Array of zeros. + */ + private static final char[] ZEROS = new char[] { '0', '0', '0', '0', '0', '0', '0', '0' }; + /** * Default instance. */ @@ -54,6 +58,16 @@ public final class FormattingInfo { */ private final boolean leftTruncate; + /** + * Use zero-padding instead whitespace padding + */ + private final boolean zeroPad; + + /** + * Empty array. + */ + public static final FormattingInfo[] EMPTY_ARRAY = {}; + /** * Creates new instance. * @@ -67,10 +81,29 @@ public final class FormattingInfo { * truncates to the left if true */ public FormattingInfo(final boolean leftAlign, final int minLength, final int maxLength, final boolean leftTruncate) { + this(leftAlign, minLength, maxLength, leftTruncate, false); + } + + /** + * Creates new instance. + * + * @param leftAlign + * left align if true. + * @param minLength + * minimum length. + * @param maxLength + * maximum length. + * @param leftTruncate + * truncates to the left if true + * @param zeroPad + * use zero-padding instead of whitespace-padding + */ + public FormattingInfo(final boolean leftAlign, final int minLength, final int maxLength, final boolean leftTruncate, final boolean zeroPad) { this.leftAlign = leftAlign; this.minLength = minLength; this.maxLength = maxLength; this.leftTruncate = leftTruncate; + this.zeroPad = zeroPad; } /** @@ -97,8 +130,17 @@ public boolean isLeftAligned() { * @return true if left truncated. */ public boolean isLeftTruncate() { - return leftTruncate; - } + return leftTruncate; + } + + /** + * Determine if zero-padded. + * + * @return true if zero-padded. + */ + public boolean isZeroPad() { + return zeroPad; + } /** * Get minimum length. @@ -130,11 +172,11 @@ public void format(final int fieldStart, final StringBuilder buffer) { final int rawLength = buffer.length() - fieldStart; if (rawLength > maxLength) { - if (leftTruncate) { - buffer.delete(fieldStart, buffer.length() - maxLength); - } else { - buffer.delete(fieldStart + maxLength, fieldStart + buffer.length()); - } + if (leftTruncate) { + buffer.delete(fieldStart, buffer.length() - maxLength); + } else { + buffer.delete(fieldStart + maxLength, fieldStart + buffer.length()); + } } else if (rawLength < minLength) { if (leftAlign) { final int fieldEnd = buffer.length(); @@ -146,11 +188,13 @@ public void format(final int fieldStart, final StringBuilder buffer) { } else { int padLength = minLength - rawLength; - for (; padLength > SPACES.length; padLength -= SPACES.length) { - buffer.insert(fieldStart, SPACES); + final char[] paddingArray= zeroPad ? ZEROS : SPACES; + + for (; padLength > paddingArray.length; padLength -= paddingArray.length) { + buffer.insert(fieldStart, paddingArray); } - buffer.insert(fieldStart, SPACES, 0, padLength); + buffer.insert(fieldStart, paddingArray, 0, padLength); } } } @@ -172,6 +216,8 @@ public String toString() { sb.append(minLength); sb.append(", leftTruncate="); sb.append(leftTruncate); + sb.append(", zeroPad="); + sb.append(zeroPad); sb.append(']'); return sb.toString(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java index 120a6718637..ff38fa700d2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java @@ -17,13 +17,15 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; /** * Format the event's line location information. */ -@Plugin(name = "FullLocationPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("FullLocationPatternConverter") @ConverterKeys({ "l", "location" }) public final class FullLocationPatternConverter extends LogEventPatternConverter { /** @@ -60,4 +62,9 @@ public void format(final LogEvent event, final StringBuilder output) { output.append(element.toString()); } } + + @Override + public boolean requiresLocation() { + return true; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java index c4b9703cc2c..a8ba3983cee 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java @@ -25,8 +25,9 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.Strings; @@ -75,14 +76,29 @@ * %highlight{%d{ ISO8601 } [%t] %-5level: %msg%n%throwable}{STYLE=DEFAULT, noConsoleNoAnsi=true} *
    */ -@Plugin(name = "highlight", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("highlight") @ConverterKeys({ "highlight" }) @PerformanceSensitive("allocation") public final class HighlightConverter extends LogEventPatternConverter implements AnsiConverter { - private static final Map DEFAULT_STYLES = new HashMap<>(); + private static final Map DEFAULT_STYLES = Map.of( + Level.FATAL.name(), AnsiEscape.createSequence("BRIGHT", "RED"), + Level.ERROR.name(), AnsiEscape.createSequence("BRIGHT", "RED"), + Level.WARN.name(), AnsiEscape.createSequence("YELLOW"), + Level.INFO.name(), AnsiEscape.createSequence("GREEN"), + Level.DEBUG.name(), AnsiEscape.createSequence("CYAN"), + Level.TRACE.name(), AnsiEscape.createSequence("BLACK") + ); - private static final Map LOGBACK_STYLES = new HashMap<>(); + private static final Map LOGBACK_STYLES = Map.of( + Level.FATAL.name(), AnsiEscape.createSequence("BLINK", "BRIGHT", "RED"), + Level.ERROR.name(), AnsiEscape.createSequence("BRIGHT", "RED"), + Level.WARN.name(), AnsiEscape.createSequence("RED"), + Level.INFO.name(), AnsiEscape.createSequence("BLUE"), + Level.DEBUG.name(), AnsiEscape.createSequence((String[]) null), + Level.TRACE.name(), AnsiEscape.createSequence((String[]) null) + ); private static final String STYLE_KEY = "STYLE"; @@ -90,27 +106,10 @@ public final class HighlightConverter extends LogEventPatternConverter implement private static final String STYLE_KEY_LOGBACK = "LOGBACK"; - private static final Map> STYLES = new HashMap<>(); - - static { - // Default styles: - DEFAULT_STYLES.put(Level.FATAL, AnsiEscape.createSequence("BRIGHT", "RED")); - DEFAULT_STYLES.put(Level.ERROR, AnsiEscape.createSequence("BRIGHT", "RED")); - DEFAULT_STYLES.put(Level.WARN, AnsiEscape.createSequence("YELLOW")); - DEFAULT_STYLES.put(Level.INFO, AnsiEscape.createSequence("GREEN")); - DEFAULT_STYLES.put(Level.DEBUG, AnsiEscape.createSequence("CYAN")); - DEFAULT_STYLES.put(Level.TRACE, AnsiEscape.createSequence("BLACK")); - // Logback styles: - LOGBACK_STYLES.put(Level.FATAL, AnsiEscape.createSequence("BLINK", "BRIGHT", "RED")); - LOGBACK_STYLES.put(Level.ERROR, AnsiEscape.createSequence("BRIGHT", "RED")); - LOGBACK_STYLES.put(Level.WARN, AnsiEscape.createSequence("RED")); - LOGBACK_STYLES.put(Level.INFO, AnsiEscape.createSequence("BLUE")); - LOGBACK_STYLES.put(Level.DEBUG, AnsiEscape.createSequence((String[]) null)); - LOGBACK_STYLES.put(Level.TRACE, AnsiEscape.createSequence((String[]) null)); - // Style map: - STYLES.put(STYLE_KEY_DEFAULT, DEFAULT_STYLES); - STYLES.put(STYLE_KEY_LOGBACK, LOGBACK_STYLES); - } + private static final Map> STYLES = Map.of( + STYLE_KEY_DEFAULT, DEFAULT_STYLES, + STYLE_KEY_LOGBACK, LOGBACK_STYLES + ); /** * Creates a level style map where values are ANSI escape sequences given configuration options in {@code option[1]} @@ -140,7 +139,7 @@ public final class HighlightConverter extends LogEventPatternConverter implement * The second slot can optionally contain the style map. * @return a new map */ - private static Map createLevelStyleMap(final String[] options) { + private static Map createLevelStyleMap(final String[] options) { if (options.length < 2) { return DEFAULT_STYLES; } @@ -150,12 +149,12 @@ private static Map createLevelStyleMap(final String[] options) { .replaceAll(PatternParser.NO_CONSOLE_NO_ANSI + "=(true|false)", Strings.EMPTY); // final Map styles = AnsiEscape.createMap(string, new String[] {STYLE_KEY}); - final Map levelStyles = new HashMap<>(DEFAULT_STYLES); + final Map levelStyles = new HashMap<>(DEFAULT_STYLES); for (final Map.Entry entry : styles.entrySet()) { final String key = entry.getKey().toUpperCase(Locale.ENGLISH); final String value = entry.getValue(); if (STYLE_KEY.equalsIgnoreCase(key)) { - final Map enumMap = STYLES.get(value.toUpperCase(Locale.ENGLISH)); + final Map enumMap = STYLES.get(value.toUpperCase(Locale.ENGLISH)); if (enumMap == null) { LOGGER.error("Unknown level style: " + value + ". Use one of " + Arrays.toString(STYLES.keySet().toArray())); @@ -165,9 +164,10 @@ private static Map createLevelStyleMap(final String[] options) { } else { final Level level = Level.toLevel(key, null); if (level == null) { - LOGGER.error("Unknown level name: {}; use one of {}", key, Arrays.toString(Level.values())); + LOGGER.warn("Setting style for yet unknown level name {}", key); + levelStyles.put(key, value); } else { - levelStyles.put(level, value); + levelStyles.put(level.name(), value); } } } @@ -199,7 +199,7 @@ public static HighlightConverter newInstance(final Configuration config, final S return new HighlightConverter(formatters, createLevelStyleMap(options), hideAnsi); } - private final Map levelStyles; + private final Map levelStyles; private final List patternFormatters; @@ -215,7 +215,7 @@ public static HighlightConverter newInstance(final Configuration config, final S * @param noAnsi * If true, do not output ANSI escape codes. */ - private HighlightConverter(final List patternFormatters, final Map levelStyles, final boolean noAnsi) { + private HighlightConverter(final List patternFormatters, final Map levelStyles, final boolean noAnsi) { super("style", "style"); this.patternFormatters = patternFormatters; this.levelStyles = levelStyles; @@ -230,9 +230,12 @@ private HighlightConverter(final List patternFormatters, final public void format(final LogEvent event, final StringBuilder toAppendTo) { int start = 0; int end = 0; + final String levelStyle = levelStyles.get(event.getLevel().name()); if (!noAnsi) { // use ANSI: set prefix start = toAppendTo.length(); - toAppendTo.append(levelStyles.get(event.getLevel())); + if (levelStyle != null) { + toAppendTo.append(levelStyle); + } end = toAppendTo.length(); } @@ -246,14 +249,14 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) { if (!noAnsi) { if (empty) { toAppendTo.setLength(start); // erase prefix - } else { + } else if (levelStyle != null) { toAppendTo.append(defaultStyle); // add postfix } } } - String getLevelStyle(Level level) { - return levelStyles.get(level); + String getLevelStyle(final Level level) { + return levelStyles.get(level.name()); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java index 4a41da4fc02..1684706da99 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java @@ -18,13 +18,15 @@ import java.util.Date; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Formats an integer. */ -@Plugin(name = "IntegerPatternConverter", category = "FileConverter") +@Namespace("FileConverter") +@Plugin("IntegerPatternConverter") @ConverterKeys({ "i", "index" }) @PerformanceSensitive("allocation") public final class IntegerPatternConverter extends AbstractPatternConverter implements ArrayPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java index 83770360fd8..84584032845 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/JAnsiTextRenderer.java @@ -34,9 +34,9 @@ /** * Renders an input as ANSI escaped output. - * + * * Uses the JAnsi rendering syntax as the default to render a message into an ANSI escaped string. - * + * * The default syntax for embedded ANSI codes is: * *
    @@ -54,28 +54,28 @@
      * 
      *   @|bold,red Warning!|@
      * 
    - * + * * You can also define custom style names in the configuration with the syntax: - * + * *
      * %message{ansi}{StyleName=value(,value)*( StyleName=value(,value)*)*}%n
      * 
    - * + * * For example: - * + * *
      * %message{ansi}{WarningStyle=red,bold KeyStyle=white ValueStyle=blue}%n
      * 
    - * + * * The call site can look like this: - * + * *
      * logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue());
      * 
    - * + * * Note: This class originally copied and then heavily modified code from JAnsi's AnsiRenderer (which is licensed as * Apache 2.0.) - * + * * @see AnsiRenderer */ public final class JAnsiTextRenderer implements TextRenderer { @@ -172,7 +172,7 @@ private static void put(final Map map, final String name, final public JAnsiTextRenderer(final String[] formats, final Map defaultStyleMap) { String tempBeginToken = AnsiRenderer.BEGIN_TOKEN; String tempEndToken = AnsiRenderer.END_TOKEN; - Map map; + final Map map; if (formats.length > 1) { final String allStylesStr = formats[1]; // Style def split @@ -254,7 +254,7 @@ private void render(final Ansi ansi, final Code... codes) { /** * Renders the given text with the given names which can be ANSI code names or Log4j style names. - * + * * @param text * The text to render * @param names diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java index debee5a68bc..2a1046c6ac1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java @@ -22,33 +22,32 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Returns the event's level in a StringBuilder. */ -@Plugin(name = "LevelPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("LevelPatternConverter") @ConverterKeys({ "p", "level" }) @PerformanceSensitive("allocation") -public final class LevelPatternConverter extends LogEventPatternConverter { +public class LevelPatternConverter extends LogEventPatternConverter { private static final String OPTION_LENGTH = "length"; private static final String OPTION_LOWER = "lowerCase"; /** * Singleton. */ - private static final LevelPatternConverter INSTANCE = new LevelPatternConverter(null); - - private final Map levelMap; + private static final LevelPatternConverter INSTANCE = new SimpleLevelPatternConverter(); /** * Private constructor. */ - private LevelPatternConverter(final Map map) { + private LevelPatternConverter() { super("Level", "level"); - this.levelMap = map; } /** @@ -97,7 +96,7 @@ public static LevelPatternConverter newInstance(final String[] options) { levelMap.put(level, lowerCase ? left.toLowerCase(Locale.US) : left); } } - return new LevelPatternConverter(levelMap); + return new LevelMapLevelPatternConverter(levelMap); } /** @@ -123,7 +122,7 @@ private static String left(final Level level, final int length) { */ @Override public void format(final LogEvent event, final StringBuilder output) { - output.append(levelMap == null ? event.getLevel().toString() : levelMap.get(event.getLevel())); + throw new UnsupportedOperationException("Overridden by subclasses"); } /** @@ -137,4 +136,32 @@ public String getStyleClass(final Object e) { return "level"; } + + private static final class SimpleLevelPatternConverter extends LevelPatternConverter { + + /** + * {@inheritDoc} + */ + @Override + public void format(final LogEvent event, final StringBuilder output) { + output.append(event.getLevel()); + } + } + + private static final class LevelMapLevelPatternConverter extends LevelPatternConverter { + + private final Map levelMap; + + private LevelMapLevelPatternConverter(final Map levelMap) { + this.levelMap = levelMap; + } + + /** + * {@inheritDoc} + */ + @Override + public void format(final LogEvent event, final StringBuilder output) { + output.append(levelMap.get(event.getLevel())); + } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java index 2b912d7f548..5ff63e30698 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java @@ -17,12 +17,14 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; /** * Returns the event's line location information in a StringBuilder. */ -@Plugin(name = "LineLocationPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("LineLocationPatternConverter") @ConverterKeys({ "L", "line" }) public final class LineLocationPatternConverter extends LogEventPatternConverter { /** @@ -60,4 +62,9 @@ public void format(final LogEvent event, final StringBuilder output) { output.append(element.getLineNumber()); } } + + @Override + public boolean requiresLocation() { + return true; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java index 1d7b022bc43..6ab333d898b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java @@ -17,14 +17,16 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.Strings; /** * Formats a line separator. */ -@Plugin(name = "LineSeparatorPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("LineSeparatorPatternConverter") @ConverterKeys({ "n" }) @PerformanceSensitive("allocation") public final class LineSeparatorPatternConverter extends LogEventPatternConverter { @@ -34,17 +36,11 @@ public final class LineSeparatorPatternConverter extends LogEventPatternConverte */ private static final LineSeparatorPatternConverter INSTANCE = new LineSeparatorPatternConverter(); - /** - * Line separator. - */ - private final String lineSep; - /** * Private constructor. */ private LineSeparatorPatternConverter() { super("Line Sep", "lineSep"); - lineSep = Strings.LINE_SEPARATOR; } /** @@ -62,7 +58,20 @@ public static LineSeparatorPatternConverter newInstance(final String[] options) * {@inheritDoc} */ @Override - public void format(final LogEvent event, final StringBuilder toAppendTo) { - toAppendTo.append(lineSep); + public void format(final LogEvent ignored, final StringBuilder toAppendTo) { + toAppendTo.append(Strings.LINE_SEPARATOR); + } + + /** + * {@inheritDoc} + */ + @Override + public void format(final Object ignored, final StringBuilder output) { + output.append(Strings.LINE_SEPARATOR); + } + + @Override + public boolean isVariable() { + return false; } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverter.java index 2e2470663ba..ba0d9ec12d3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LiteralPatternConverter.java @@ -49,7 +49,11 @@ public LiteralPatternConverter(final Configuration config, final String literal, super("Literal", "literal"); this.literal = convertBackslashes ? OptionConverter.convertSpecialChars(literal) : literal; // LOG4J2-829 this.config = config; - substitute = config != null && literal.contains("${"); + substitute = config != null && containsSubstitutionSequence(literal); + } + + static boolean containsSubstitutionSequence(final String literal) { + return literal != null && literal.contains("${"); } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java index 84cee329e10..2ff42974feb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java @@ -65,6 +65,15 @@ public boolean handlesThrowable() { return false; } + /** + * Some pattern converters require location information. By returning true the location can be + * calculated efficiently. + * @return true if this PatternConverter uses location information. + */ + public boolean requiresLocation() { + return false; + } + public boolean isVariable() { return true; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java index 9eb429606b7..769b367e833 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java @@ -17,15 +17,17 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Formats the Logger FQCN. * - * @since 2.10.1 + * @since 2.11.0 */ -@Plugin(name = "LoggerFqcnPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("LoggerFqcnPatternConverter") @ConverterKeys({ "fqcn" }) @PerformanceSensitive("allocation") public final class LoggerFqcnPatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java index 609af7a2167..9cfb5ebb46a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java @@ -17,14 +17,16 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Formats a logger name. */ -@Plugin(name = "LoggerPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("LoggerPatternConverter") @ConverterKeys({ "c", "logger" }) @PerformanceSensitive("allocation") public final class LoggerPatternConverter extends NamePatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java index 6f018bd2ca4..4c8f899bc1f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java @@ -17,9 +17,12 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.message.StringMapMessage; -import org.apache.logging.log4j.util.IndexedReadOnlyStringMap; +import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.message.MapMessage.MapFormat; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; + +import java.util.Objects; /** * Able to handle the contents of the LogEvent's MapMessage and either @@ -27,32 +30,57 @@ * java.util.Hashtable.toString(), or to output the value of a specific key * within the Map. */ -@Plugin(name = "MapPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("MapPatternConverter") @ConverterKeys({ "K", "map", "MAP" }) public final class MapPatternConverter extends LogEventPatternConverter { + + private static final String JAVA_UNQUOTED = MapFormat.JAVA_UNQUOTED.name(); + /** * Name of property to output. */ private final String key; + /** + * Format to use when no key is provided. + * + * @see MapFormat + * @since 2.11.2 + */ + private final String[] format; + /** * Private constructor. * * @param options options, may be null. */ - private MapPatternConverter(final String[] options) { + private MapPatternConverter(final String[] options, final String... format) { super(options != null && options.length > 0 ? "MAP{" + options[0] + '}' : "MAP", "map"); key = options != null && options.length > 0 ? options[0] : null; + this.format = format; } /** - * Obtains an instance of PropertiesPatternConverter. + * Obtains an instance of {@link MapPatternConverter}. * * @param options options, may be null or first element contains name of property to format. - * @return instance of PropertiesPatternConverter. + * @return instance of {@link MapPatternConverter}. */ public static MapPatternConverter newInstance(final String[] options) { - return new MapPatternConverter(options); + return new MapPatternConverter(options, JAVA_UNQUOTED); + } + + /** + * Obtain an instance of {@link MapPatternConverter}. + * + * @param options options, may be null or first element contains name of property to format. + * @param format the format to use if no options are given (i.e., options is null). Ignored if options is non-null. + * @return instance of {@link MapPatternConverter}. + * @since 2.11.2 + */ + public static MapPatternConverter newInstance(final String[] options, final MapFormat format) { + return new MapPatternConverter(options, Objects.toString(format, JAVA_UNQUOTED)); } /** @@ -60,31 +88,19 @@ public static MapPatternConverter newInstance(final String[] options) { */ @Override public void format(final LogEvent event, final StringBuilder toAppendTo) { - StringMapMessage msg; - if (event.getMessage() instanceof StringMapMessage) { - msg = (StringMapMessage) event.getMessage(); + final MapMessage msg; + if (event.getMessage() instanceof MapMessage) { + msg = (MapMessage) event.getMessage(); } else { return; } - final IndexedReadOnlyStringMap sortedMap = msg.getIndexedReadOnlyStringMap(); // if there is no additional options, we output every single // Key/Value pair for the Map in a similar format to Hashtable.toString() if (key == null) { - if (sortedMap.isEmpty()) { - toAppendTo.append("{}"); - return; - } - toAppendTo.append("{"); - for (int i = 0; i < sortedMap.size(); i++) { - if (i > 0) { - toAppendTo.append(", "); - } - toAppendTo.append(sortedMap.getKeyAt(i)).append('=').append((String)sortedMap.getValueAt(i)); - } - toAppendTo.append('}'); + msg.formatTo(format, toAppendTo); } else { // otherwise they just want a single key output - final String val = sortedMap.getValue(key); + final String val = msg.get(key); if (val != null) { toAppendTo.append(val); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java index dde55025b1d..efba002a882 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java @@ -18,14 +18,16 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilders; /** * Returns events' full marker string in a StringBuilder. */ -@Plugin(name = "MarkerPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("MarkerPatternConverter") @ConverterKeys({ "marker" }) @PerformanceSensitive("allocation") public final class MarkerPatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java index b274eb1ef69..c11b8da004f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java @@ -18,13 +18,15 @@ import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Appends an event's maker name to a StringBuilder. */ -@Plugin(name = "MarkerNamePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("MarkerNamePatternConverter") @ConverterKeys({ "markerSimpleName" }) @PerformanceSensitive("allocation") public final class MarkerSimpleNamePatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java index 1a59a4bc65b..3d65c70078b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java @@ -14,7 +14,6 @@ * See the license for the specific language governing permissions and * limitations under the license. */ - package org.apache.logging.log4j.core.pattern; import java.util.List; @@ -22,8 +21,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -35,7 +35,8 @@ * * @author Thies Wellpott */ -@Plugin(name = "maxLength", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("maxLength") @ConverterKeys({"maxLength", "maxLen"}) @PerformanceSensitive("allocation") public final class MaxLengthConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java index b39c15e83f2..9649886607c 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java @@ -16,12 +16,13 @@ */ package org.apache.logging.log4j.core.pattern; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.util.TriConsumer; import org.apache.logging.log4j.util.StringBuilders; +import org.apache.logging.log4j.util.TriConsumer; /** * Able to handle the contents of the LogEvent's MDC and either @@ -30,7 +31,8 @@ * within the property bundle * when this pattern converter has the option set. */ -@Plugin(name = "MdcPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("MdcPatternConverter") @ConverterKeys({ "X", "mdc", "MDC" }) @PerformanceSensitive("allocation") public final class MdcPatternConverter extends LogEventPatternConverter { @@ -78,13 +80,10 @@ public static MdcPatternConverter newInstance(final String[] options) { return new MdcPatternConverter(options); } - private static final TriConsumer WRITE_KEY_VALUES_INTO = new TriConsumer() { - @Override - public void accept(final String key, final Object value, final StringBuilder sb) { - sb.append(key).append('='); - StringBuilders.appendValue(sb, value); - sb.append(", "); - } + private static final TriConsumer WRITE_KEY_VALUES_INTO = (key, value, sb) -> { + sb.append(key).append('='); + StringBuilders.appendValue(sb, value); + sb.append(", "); }; /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java index 92d75a2de46..0e595aa065d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java @@ -16,64 +16,40 @@ */ package org.apache.logging.log4j.core.pattern; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.util.ArrayUtils; -import org.apache.logging.log4j.core.util.Constants; import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.message.MultiformatMessage; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable; import org.apache.logging.log4j.util.PerformanceSensitive; import org.apache.logging.log4j.util.StringBuilderFormattable; +import org.apache.logging.log4j.util.Strings; /** * Returns the event's rendered message in a StringBuilder. */ -@Plugin(name = "MessagePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("MessagePatternConverter") @ConverterKeys({ "m", "msg", "message" }) @PerformanceSensitive("allocation") -public final class MessagePatternConverter extends LogEventPatternConverter { +public class MessagePatternConverter extends LogEventPatternConverter { + private static final String LOOKUPS = "lookups"; private static final String NOLOOKUPS = "nolookups"; - private final String[] formats; - private final Configuration config; - private final TextRenderer textRenderer; - private final boolean noLookups; - - /** - * Private constructor. - * - * @param options - * options, may be null. - */ - private MessagePatternConverter(final Configuration config, final String[] options) { + private MessagePatternConverter() { super("Message", "message"); - this.formats = options; - this.config = config; - final int noLookupsIdx = loadNoLookups(options); - this.noLookups = Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS || noLookupsIdx >= 0; - this.textRenderer = loadMessageRenderer(noLookupsIdx >= 0 ? ArrayUtils.remove(options, noLookupsIdx) : options); } - private int loadNoLookups(final String[] options) { - if (options != null) { - for (int i = 0; i < options.length; i++) { - final String option = options[i]; - if (NOLOOKUPS.equalsIgnoreCase(option)) { - return i; - } - } - } - return -1; - } - - private TextRenderer loadMessageRenderer(final String[] options) { + private static TextRenderer loadMessageRenderer(final String[] options) { if (options != null) { for (final String option : options) { switch (option.toUpperCase(Locale.ROOT)) { @@ -102,55 +78,101 @@ private TextRenderer loadMessageRenderer(final String[] options) { * @return instance of pattern converter. */ public static MessagePatternConverter newInstance(final Configuration config, final String[] options) { - return new MessagePatternConverter(config, options); + String[] formats = withoutLookupOptions(options); + TextRenderer textRenderer = loadMessageRenderer(formats); + MessagePatternConverter result = formats == null || formats.length == 0 + ? SimpleMessagePatternConverter.INSTANCE + : new FormattedMessagePatternConverter(formats); + if (textRenderer != null) { + result = new RenderingPatternConverter(result, textRenderer); + } + return result; + } + + private static String[] withoutLookupOptions(final String[] options) { + if (options == null || options.length == 0) { + return options; + } + List results = new ArrayList<>(options.length); + for (String option : options) { + if (LOOKUPS.equalsIgnoreCase(option) || NOLOOKUPS.equalsIgnoreCase(option)) { + LOGGER.info("The {} option will be ignored. Message Lookups are no longer supported.", option); + } else { + results.add(option); + } + } + return results.toArray(Strings.EMPTY_ARRAY); } - /** - * {@inheritDoc} - */ @Override public void format(final LogEvent event, final StringBuilder toAppendTo) { - final Message msg = event.getMessage(); - if (msg instanceof StringBuilderFormattable) { + throw new UnsupportedOperationException(); + } - final boolean doRender = textRenderer != null; - final StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo; + private static final class SimpleMessagePatternConverter extends MessagePatternConverter { + private static final MessagePatternConverter INSTANCE = new SimpleMessagePatternConverter(); - final int offset = workingBuilder.length(); - if (msg instanceof MultiFormatStringBuilderFormattable) { - ((MultiFormatStringBuilderFormattable) msg).formatTo(formats, workingBuilder); - } else { - ((StringBuilderFormattable) msg).formatTo(workingBuilder); + /** + * {@inheritDoc} + */ + @Override + public void format(final LogEvent event, final StringBuilder toAppendTo) { + Message msg = event.getMessage(); + if (msg instanceof StringBuilderFormattable) { + ((StringBuilderFormattable) msg).formatTo(toAppendTo); + } else if (msg != null) { + toAppendTo.append(msg.getFormattedMessage()); } + } + } - // TODO can we optimize this? - if (config != null && !noLookups) { - for (int i = offset; i < workingBuilder.length() - 1; i++) { - if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') { - final String value = workingBuilder.substring(offset, workingBuilder.length()); - workingBuilder.setLength(offset); - workingBuilder.append(config.getStrSubstitutor().replace(event, value)); - } + private static final class FormattedMessagePatternConverter extends MessagePatternConverter { + + private final String[] formats; + + FormattedMessagePatternConverter(final String[] formats) { + this.formats = formats; + } + + /** + * {@inheritDoc} + */ + @Override + public void format(final LogEvent event, final StringBuilder toAppendTo) { + Message msg = event.getMessage(); + if (msg instanceof StringBuilderFormattable) { + if (msg instanceof MultiFormatStringBuilderFormattable) { + ((MultiFormatStringBuilderFormattable) msg).formatTo(formats, toAppendTo); + } else { + ((StringBuilderFormattable) msg).formatTo(toAppendTo); } + } else if (msg != null) { + toAppendTo.append(msg instanceof MultiformatMessage + ? ((MultiformatMessage) msg).getFormattedMessage(formats) + : msg.getFormattedMessage()); } - if (doRender) { - textRenderer.render(workingBuilder, toAppendTo); - } - return; } - if (msg != null) { - String result; - if (msg instanceof MultiformatMessage) { - result = ((MultiformatMessage) msg).getFormattedMessage(formats); - } else { - result = msg.getFormattedMessage(); - } - if (result != null) { - toAppendTo.append(config != null && result.contains("${") - ? config.getStrSubstitutor().replace(event, result) : result); - } else { - toAppendTo.append("null"); - } + } + + private static final class RenderingPatternConverter extends MessagePatternConverter { + + private final MessagePatternConverter delegate; + private final TextRenderer textRenderer; + + RenderingPatternConverter(final MessagePatternConverter delegate, final TextRenderer textRenderer) { + this.delegate = delegate; + this.textRenderer = textRenderer; } + + /** + * {@inheritDoc} + */ + @Override + public void format(final LogEvent event, final StringBuilder toAppendTo) { + StringBuilder workingBuilder = new StringBuilder(80); + delegate.format(event, workingBuilder); + textRenderer.render(workingBuilder, toAppendTo); + } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java index 457b7a68b66..106c82d83ec 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java @@ -17,13 +17,14 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; - +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; /** * Returns the event's line location information in a StringBuilder. */ -@Plugin(name = "MethodLocationPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("MethodLocationPatternConverter") @ConverterKeys({ "M", "method" }) public final class MethodLocationPatternConverter extends LogEventPatternConverter { /** @@ -60,4 +61,9 @@ public void format(final LogEvent event, final StringBuilder toAppendTo) { toAppendTo.append(element.getMethodName()); } } + + @Override + public boolean requiresLocation() { + return true; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java index 2fb6ea9dd1f..331ad3931d8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NameAbbreviator.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.core.pattern; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.logging.log4j.util.PerformanceSensitive; @@ -55,7 +56,12 @@ public static NameAbbreviator getAbbreviator(final String pattern) { return DEFAULT; } - boolean isNegativeNumber; + NameAbbreviator dwa = DynamicWordAbbreviator.create(trimmed); + if (dwa != null) { + return dwa; + } + + final boolean isNegativeNumber; final String number; // check if number is a negative number @@ -259,7 +265,10 @@ public void abbreviate(final String original, final StringBuilder destination) { /** * Fragment of an pattern abbreviator. */ - private static class PatternAbbreviatorFragment { + private static final class PatternAbbreviatorFragment { + + static final PatternAbbreviatorFragment[] EMPTY_ARRAY = {}; + /** * Count of initial characters of element to output. */ @@ -278,7 +287,7 @@ private static class PatternAbbreviatorFragment { * @param ellipsis character to represent elimination of characters, * '\0' if no ellipsis is desired. */ - public PatternAbbreviatorFragment( + PatternAbbreviatorFragment( final int charCount, final char ellipsis) { this.charCount = charCount; this.ellipsis = ellipsis; @@ -287,40 +296,47 @@ public PatternAbbreviatorFragment( /** * Abbreviate element of name. * - * @param buf buffer to receive element. - * @param startPos starting index of name element. - * @return starting index of next element. + * @param input input string which is being written to the output {@code buf}. + * @param inputIndex starting index of name element in the {@code input} string. + * @param buf buffer to receive element. + * @return starting index of next element. */ - public int abbreviate(final StringBuilder buf, final int startPos) { - final int start = (startPos < 0) ? 0 : startPos; - final int max = buf.length(); - int nextDot = -1; - for (int i = start; i < max; i++) { - if (buf.charAt(i) == '.') { - nextDot = i; - break; - } + int abbreviate(final String input, final int inputIndex, final StringBuilder buf) { + // Note that indexOf(char) performs worse than indexOf(String) on pre-16 JREs + // due to missing intrinsics for the character implementation. The difference + // is a few nanoseconds in most cases, so we opt to give the jre as much + // information as possible for best performance on new runtimes, with the + // possibility that such optimizations may be back-ported. + // See https://bugs.openjdk.java.net/browse/JDK-8173585 + int nextDot = input.indexOf('.', inputIndex); + if (nextDot < 0) { + buf.append(input, inputIndex, input.length()); + return nextDot; } - if (nextDot != -1) { - if (nextDot - startPos > charCount) { - buf.delete(startPos + charCount, nextDot); - nextDot = startPos + charCount; - - if (ellipsis != '\0') { - buf.insert(nextDot, ellipsis); - nextDot++; - } + if (nextDot - inputIndex > charCount) { + buf.append(input, inputIndex, inputIndex + charCount); + if (ellipsis != '\0') { + buf.append(ellipsis); } - nextDot++; + buf.append('.'); + } else { + // Include the period to reduce interactions with the buffer + buf.append(input, inputIndex, nextDot + 1); } - return nextDot; + return nextDot + 1; + } + + @Override + public String toString() { + return String.format("%s[charCount=%s, ellipsis=%s]", + getClass().getSimpleName(), charCount, Integer.toHexString(ellipsis)); } } /** * Pattern abbreviator. */ - private static class PatternAbbreviator extends NameAbbreviator { + private static final class PatternAbbreviator extends NameAbbreviator { /** * Element abbreviation patterns. */ @@ -331,14 +347,13 @@ private static class PatternAbbreviator extends NameAbbreviator { * * @param fragments element abbreviation patterns. */ - public PatternAbbreviator(final List fragments) { + PatternAbbreviator(final List fragments) { if (fragments.isEmpty()) { throw new IllegalArgumentException( "fragments must have at least one element"); } - this.fragments = new PatternAbbreviatorFragment[fragments.size()]; - fragments.toArray(this.fragments); + this.fragments = fragments.toArray(PatternAbbreviatorFragment.EMPTY_ARRAY); } /** @@ -349,25 +364,22 @@ public PatternAbbreviator(final List fragments) { */ @Override public void abbreviate(final String original, final StringBuilder destination) { - // - // all non-terminal patterns are executed once - // - int pos = destination.length(); - final int max = pos + original.length(); - final StringBuilder sb = destination.append(original);//new StringBuilder(original); - - for (int i = 0; i < fragments.length - 1 && pos < original.length(); i++) { - pos = fragments[i].abbreviate(sb, pos); + // non-terminal patterns are executed once + int originalIndex = 0; + int iteration = 0; + int originalLength = original.length(); + while (originalIndex >= 0 && originalIndex < originalLength) { + originalIndex = fragment(iteration++).abbreviate(original, originalIndex, destination); } + } - // - // last pattern in executed repeatedly - // - final PatternAbbreviatorFragment terminalFragment = fragments[fragments.length - 1]; + PatternAbbreviatorFragment fragment(int index) { + return fragments[Math.min(index, fragments.length - 1)]; + } - while (pos < max && pos >= 0) { - pos = terminalFragment.abbreviate(sb, pos); - } + @Override + public String toString() { + return String.format("%s[fragments=%s]", getClass().getSimpleName(), Arrays.toString(fragments)); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java index ba6186b0de5..50597dd003a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java @@ -17,13 +17,15 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Converts and formats the event's nanoTime in a StringBuilder. */ -@Plugin(name = "NanoTimePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("NanoTimePatternConverter") @ConverterKeys({ "N", "nano" }) @PerformanceSensitive("allocation") public final class NanoTimePatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java index 754ea9a7ad9..31deeb1252e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java @@ -17,14 +17,16 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Returns the event's NDC in a StringBuilder. */ -@Plugin(name = "NdcPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("NdcPatternConverter") @ConverterKeys({"x", "NDC"}) public final class NdcPatternConverter extends LogEventPatternConverter { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java index 4bde1e23ec9..de6526b076d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java @@ -67,6 +67,15 @@ public boolean handlesThrowable() { return converter.handlesThrowable(); } + /** + * Most pattern formatters do not use location information. When they do they should return true here + * so that the logging system can efficiently capture it. + * @return true if location information is required. + */ + public boolean requiresLocation() { + return converter.requiresLocation(); + } + /** * Returns a String suitable for debugging. * diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java index 91c11ecb498..1ee7c43d477 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java @@ -27,9 +27,11 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.util.PluginManager; -import org.apache.logging.log4j.core.config.plugins.util.PluginType; -import org.apache.logging.log4j.core.util.SystemNanoClock; +import org.apache.logging.log4j.core.time.SystemNanoClock; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Key; +import org.apache.logging.log4j.plugins.model.PluginNamespace; +import org.apache.logging.log4j.plugins.model.PluginType; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; @@ -76,7 +78,7 @@ private enum ParserState { /** * Max state. */ - MAX_STATE; + MAX_STATE } private static final Logger LOGGER = StatusLogger.getLogger(); @@ -85,9 +87,11 @@ private enum ParserState { private static final int DECIMAL = 10; + private static final Key PLUGIN_CATEGORY_KEY = Key.forClass(PluginNamespace.class); + private final Configuration config; - private final Map> converterRules; + private final Map> converterRules; /** * Constructor. @@ -128,15 +132,19 @@ public PatternParser(final Configuration config, final String converterKey, fina public PatternParser(final Configuration config, final String converterKey, final Class expectedClass, final Class filterClass) { this.config = config; - final PluginManager manager = new PluginManager(converterKey); - manager.collectPlugins(config == null ? null : config.getPluginPackages()); - final Map> plugins = manager.getPlugins(); - final Map> converters = new LinkedHashMap<>(); + final PluginNamespace plugins; + final Key pluginCategoryKey = PLUGIN_CATEGORY_KEY.withNamespace(converterKey); + if (config == null) { + plugins = DI.createInjector().getInstance(pluginCategoryKey); + } else { + plugins = config.getComponent(pluginCategoryKey); + } - for (final PluginType type : plugins.values()) { + final Map> converters = new LinkedHashMap<>(); + + for (final PluginType type : plugins) { try { - @SuppressWarnings("unchecked") - final Class clazz = (Class) type.getPluginClass(); + final Class clazz = type.getPluginClass().asSubclass(PatternConverter.class); if (filterClass != null && !filterClass.isAssignableFrom(clazz)) { continue; } @@ -153,7 +161,7 @@ public PatternParser(final Configuration config, final String converterKey, fina } } } catch (final Exception ex) { - LOGGER.error("Error processing plugin " + type.getElementName(), ex); + LOGGER.error("Error processing plugin " + type.getElementType(), ex); } } converterRules = converters; @@ -187,15 +195,15 @@ public List parse(final String pattern, final boolean alwaysWr config.setNanoClock(new SystemNanoClock()); } } - LogEventPatternConverter pc; + final LogEventPatternConverter pc; if (converter instanceof LogEventPatternConverter) { pc = (LogEventPatternConverter) converter; handlesThrowable |= pc.handlesThrowable(); } else { - pc = new LiteralPatternConverter(config, Strings.EMPTY, true); + pc = SimpleLiteralPatternConverter.of(Strings.EMPTY); } - FormattingInfo field; + final FormattingInfo field; if (fieldIter.hasNext()) { field = fieldIter.next(); } else { @@ -276,7 +284,7 @@ private static int extractOptions(final String pattern, final int start, final L final int begin = i; // position of first real char int depth = 1; // already inside one level while (depth > 0 && i < pattern.length()) { - char c = pattern.charAt(i); + final char c = pattern.charAt(i); if (c == '{') { depth++; } else if (c == '}') { @@ -376,8 +384,7 @@ public void parse(final String pattern, final List patternConv default: if (currentLiteral.length() != 0) { - patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), - convertBackslashes)); + patternConverters.add(literalPattern(currentLiteral.toString(), convertBackslashes)); formattingInfos.add(FormattingInfo.getDefault()); } @@ -396,9 +403,15 @@ public void parse(final String pattern, final List patternConv currentLiteral.append(c); switch (c) { + case '0': + // a '0' directly after the % sign indicates zero-padding + formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(), + formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), true); + break; + case '-': formattingInfo = new FormattingInfo(true, formattingInfo.getMinLength(), - formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate()); + formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad()); break; case '.': @@ -409,7 +422,7 @@ public void parse(final String pattern, final List patternConv if (c >= '0' && c <= '9') { formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0', - formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate()); + formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad()); state = ParserState.MIN_STATE; } else { i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules, @@ -430,7 +443,7 @@ public void parse(final String pattern, final List patternConv if (c >= '0' && c <= '9') { // Multiply the existing value and add the value of the number just encountered. formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength() - * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate()); + * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad()); } else if (c == '.') { state = ParserState.DOT_STATE; } else { @@ -448,21 +461,21 @@ public void parse(final String pattern, final List patternConv switch (c) { case '-': formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(), - formattingInfo.getMaxLength(),false); + formattingInfo.getMaxLength(),false, formattingInfo.isZeroPad()); break; default: - if (c >= '0' && c <= '9') { - formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(), - c - '0', formattingInfo.isLeftTruncate()); - state = ParserState.MAX_STATE; - } else { - LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c - + "\"."); + if (c >= '0' && c <= '9') { + formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(), + c - '0', formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad()); + state = ParserState.MAX_STATE; + } else { + LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c + + "\"."); - state = ParserState.LITERAL_STATE; - } + state = ParserState.LITERAL_STATE; + } } break; @@ -473,7 +486,7 @@ public void parse(final String pattern, final List patternConv if (c >= '0' && c <= '9') { // Multiply the existing value and add the value of the number just encountered. formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(), - formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate()); + formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad()); } else { i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules, patternConverters, formattingInfos, disableAnsi, noConsoleNoAnsi, convertBackslashes); @@ -488,7 +501,7 @@ public void parse(final String pattern, final List patternConv // while if (currentLiteral.length() != 0) { - patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes)); + patternConverters.add(literalPattern(currentLiteral.toString(), convertBackslashes)); formattingInfos.add(FormattingInfo.getDefault()); } } @@ -512,10 +525,10 @@ public void parse(final String pattern, final List patternConv * @return converter or null. */ private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral, - final Map> rules, final List options, final boolean disableAnsi, + final Map> rules, final List options, final boolean disableAnsi, final boolean noConsoleNoAnsi) { String converterName = converterId; - Class converterClass = null; + Class converterClass = null; if (rules == null) { LOGGER.error("Null rules for [" + converterId + ']'); @@ -540,8 +553,10 @@ private PatternConverter createConverter(final String converterId, final StringB final Method[] methods = converterClass.getDeclaredMethods(); Method newInstanceMethod = null; for (final Method method : methods) { - if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass) - && method.getName().equals("newInstance")) { + if (Modifier.isStatic(method.getModifiers()) + && method.getDeclaringClass().equals(converterClass) + && method.getName().equals("newInstance") + && areValidNewInstanceParameters(method.getParameterTypes())) { if (newInstanceMethod == null) { newInstanceMethod = method; } else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) { @@ -563,7 +578,7 @@ private PatternConverter createConverter(final String converterId, final StringB boolean errors = false; for (final Class clazz : parmTypes) { if (clazz.isArray() && clazz.getName().equals("[Ljava.lang.String;")) { - final String[] optionsArray = options.toArray(new String[options.size()]); + final String[] optionsArray = options.toArray(Strings.EMPTY_ARRAY); parms[i] = optionsArray; } else if (clazz.isAssignableFrom(Configuration.class)) { parms[i] = config; @@ -595,6 +610,17 @@ private PatternConverter createConverter(final String converterId, final StringB return null; } + /** LOG4J2-2564: Returns true if all method parameters are valid for injection. */ + private static boolean areValidNewInstanceParameters(final Class[] parameterTypes) { + for (final Class clazz : parameterTypes) { + if (!clazz.isAssignableFrom(Configuration.class) + && !(clazz.isArray() && "[Ljava.lang.String;".equals(clazz.getName()))) { + return false; + } + } + return true; + } + /** * Processes a format specifier sequence. * @@ -624,7 +650,7 @@ private PatternConverter createConverter(final String converterId, final StringB */ private int finalizeConverter(final char c, final String pattern, final int start, final StringBuilder currentLiteral, final FormattingInfo formattingInfo, - final Map> rules, final List patternConverters, + final Map> rules, final List patternConverters, final List formattingInfos, final boolean disableAnsi, final boolean noConsoleNoAnsi, final boolean convertBackslashes) { int i = start; @@ -640,7 +666,7 @@ private int finalizeConverter(final char c, final String pattern, final int star noConsoleNoAnsi); if (pc == null) { - StringBuilder msg; + final StringBuilder msg; if (Strings.isEmpty(converterId)) { msg = new StringBuilder("Empty conversion specifier starting at position "); @@ -650,12 +676,12 @@ private int finalizeConverter(final char c, final String pattern, final int star msg.append("] starting at position "); } - msg.append(Integer.toString(i)); + msg.append(i); msg.append(" in conversion pattern."); LOGGER.error(msg.toString()); - patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes)); + patternConverters.add(literalPattern(currentLiteral.toString(), convertBackslashes)); formattingInfos.add(FormattingInfo.getDefault()); } else { patternConverters.add(pc); @@ -663,7 +689,7 @@ private int finalizeConverter(final char c, final String pattern, final int star if (currentLiteral.length() > 0) { patternConverters - .add(new LiteralPatternConverter(config, currentLiteral.toString(), convertBackslashes)); + .add(literalPattern(currentLiteral.toString(), convertBackslashes)); formattingInfos.add(FormattingInfo.getDefault()); } } @@ -672,4 +698,12 @@ private int finalizeConverter(final char c, final String pattern, final int star return i; } + + // Create a literal pattern converter with support for substitutions if necessary + private LogEventPatternConverter literalPattern(String literal, boolean convertBackslashes) { + if (config != null && LiteralPatternConverter.containsSubstitutionSequence(literal)) { + return new LiteralPatternConverter(config, literal, convertBackslashes); + } + return SimpleLiteralPatternConverter.of(literal, convertBackslashes); + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java index 52334322b24..a29634d596a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PlainTextRenderer.java @@ -20,7 +20,7 @@ Renders input unchanged. */ public final class PlainTextRenderer implements TextRenderer { - + private static final PlainTextRenderer INSTANCE = new PlainTextRenderer(); public static PlainTextRenderer getInstance() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java index d182dc0a69a..1938f1c9407 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java @@ -17,10 +17,12 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.util.ProcessIdUtil; +import org.apache.logging.log4j.core.util.ProcessIdUtil; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; -@Plugin(name = "ProcessIdPatternConverter", category = "Converter") +@Namespace("Converter") +@Plugin("ProcessIdPatternConverter") @ConverterKeys({ "pid", "processId" }) public final class ProcessIdPatternConverter extends LogEventPatternConverter { private static final String DEFAULT_DEFAULT_VALUE = "???"; @@ -32,7 +34,7 @@ public final class ProcessIdPatternConverter extends LogEventPatternConverter { private ProcessIdPatternConverter(final String... options) { super("Process ID", "pid"); final String defaultValue = options.length > 0 ? options[0] : DEFAULT_DEFAULT_VALUE; - String discoveredPid = ProcessIdUtil.getProcessId(); + final String discoveredPid = ProcessIdUtil.getProcessId(); pid = discoveredPid.equals(ProcessIdUtil.DEFAULT_PROCESSID) ? defaultValue : discoveredPid; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java index 47ce355f150..00a25db5b31 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java @@ -16,19 +16,20 @@ */ package org.apache.logging.log4j.core.pattern; -import java.util.regex.Pattern; - import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Core; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.plugins.Configurable; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginAttribute; +import org.apache.logging.log4j.plugins.PluginFactory; import org.apache.logging.log4j.status.StatusLogger; +import java.util.regex.Pattern; + /** * Replace tokens in the LogEvent message. */ -@Plugin(name = "replace", category = Core.CATEGORY_NAME, printObject = true) +@Configurable(printObject = true) +@Plugin("replace") public final class RegexReplacement { private static final Logger LOGGER = StatusLogger.getLogger(); @@ -70,8 +71,8 @@ public String toString() { */ @PluginFactory public static RegexReplacement createRegexReplacement( - @PluginAttribute("regex") final Pattern regex, - @PluginAttribute("replacement") final String replacement) { + @PluginAttribute final Pattern regex, + @PluginAttribute final String replacement) { if (regex == null) { LOGGER.error("A regular expression is required for replacement"); return null; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java index f7c46977946..a5379242ea6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java @@ -21,13 +21,15 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; /** * Replacement pattern converter. */ -@Plugin(name = "replace", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("replace") @ConverterKeys({ "replace" }) public final class RegexReplacementConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java index ffc1ed43bbf..5c8cd68a3ed 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java @@ -19,13 +19,15 @@ import java.lang.management.ManagementFactory; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Returns the relative time in milliseconds since JVM Startup. */ -@Plugin(name = "RelativeTimePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("RelativeTimePatternConverter") @ConverterKeys({ "r", "relative" }) @PerformanceSensitive("allocation") public class RelativeTimePatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverter.java new file mode 100644 index 00000000000..59d6a8d8f20 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RepeatPatternConverter.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.util.PerformanceSensitive; +import org.apache.logging.log4j.util.Strings; + +/** + * Equals pattern converter. + */ +@Namespace(PatternConverter.CATEGORY) +@Plugin("repeat") +@ConverterKeys({"R","repeat" }) +@PerformanceSensitive("allocation") +public final class RepeatPatternConverter extends LogEventPatternConverter { + + private final String result; + + /** + * Gets an instance of the class. + * + * @param config The current Configuration. + * @param options pattern options, an array of two elements: repeatString and count. + * @return instance of class. + */ + public static RepeatPatternConverter newInstance(final Configuration config, final String[] options) { + if (options.length != 2) { + LOGGER.error("Incorrect number of options on repeat. Expected 2 received " + options.length); + return null; + } + if (options[0] == null) { + LOGGER.error("No string supplied on repeat"); + return null; + } + if (options[1] == null) { + LOGGER.error("No repeat count supplied on repeat"); + return null; + } + int count = 0; + String result = options[0]; + try { + count = Integer.parseInt(options[1].trim()); + result = Strings.repeat(options[0], count); + } catch (final Exception ex) { + LOGGER.error("The repeat count is not an integer: {}", options[1].trim()); + } + + return new RepeatPatternConverter(result); + } + + /** + * Construct the converter. + * + * @param result The repeated String + + */ + private RepeatPatternConverter(final String result) { + super("repeat", "repeat"); + this.result = result; + } + + /** + * Adds the repeated String to the buffer. + * + * @param obj event to format, may not be null. + * @param toAppendTo string buffer to which the formatted event will be appended. May not be null. + */ + public void format(final Object obj, final StringBuilder toAppendTo) { + format(toAppendTo); + } + + /** + * Adds the repeated String to the buffer. + * + * @param event event to format, may not be null. + * @param toAppendTo string buffer to which the formatted event will be appended. May not be null. + */ + public void format(final LogEvent event, final StringBuilder toAppendTo) { + format(toAppendTo); + } + + private void format(final StringBuilder toAppendTo) { + if (result != null) { + toAppendTo.append(result); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java index 33bc0957d22..76748612e29 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java @@ -18,8 +18,9 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.Strings; /** @@ -30,7 +31,8 @@ * The extended stack trace will also include the location of where the class was loaded from and the * version of the jar if available. */ -@Plugin(name = "RootThrowablePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("RootThrowablePatternConverter") @ConverterKeys({ "rEx", "rThrowable", "rException" }) public final class RootThrowablePatternConverter extends ThrowablePatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java index b607cbfeafd..9b50bc2f52d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java @@ -19,13 +19,15 @@ import java.util.concurrent.atomic.AtomicLong; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Formats the event sequence number. */ -@Plugin(name = "SequenceNumberPatternConverter", category = "Converter") +@Namespace("Converter") +@Plugin("SequenceNumberPatternConverter") @ConverterKeys({ "sn", "sequenceNumber" }) @PerformanceSensitive("allocation") public final class SequenceNumberPatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverter.java new file mode 100644 index 00000000000..d3abe55e0e7 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SimpleLiteralPatternConverter.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.pattern; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.util.OptionConverter; +import org.apache.logging.log4j.util.PerformanceSensitive; + + +/** + * Formats a string literal without substitution. + * + * This is an effectively-sealed internal type. + */ +@PerformanceSensitive("allocation") +abstract class SimpleLiteralPatternConverter extends LogEventPatternConverter implements ArrayPatternConverter { + + private SimpleLiteralPatternConverter() { + super("SimpleLiteral", "literal"); + } + + static LogEventPatternConverter of(final String literal, final boolean convertBackslashes) { + String value = convertBackslashes ? OptionConverter.convertSpecialChars(literal) : literal; + return of(value); + } + + static LogEventPatternConverter of(final String literal) { + if (literal == null || literal.isEmpty()) { + return Noop.INSTANCE; + } + if (" ".equals(literal)) { + return Space.INSTANCE; + } + return new StringValue(literal); + } + + /** + * {@inheritDoc} + */ + @Override + public final void format(final LogEvent ignored, final StringBuilder output) { + format(output); + } + + /** + * {@inheritDoc} + */ + @Override + public final void format(final Object ignored, final StringBuilder output) { + format(output); + } + + /** + * {@inheritDoc} + */ + @Override + public final void format(final StringBuilder output, final Object... args) { + format(output); + } + + abstract void format(final StringBuilder output); + + @Override + public final boolean isVariable() { + return false; + } + + @Override + public final boolean handlesThrowable() { + return false; + } + + private static final class Noop extends SimpleLiteralPatternConverter { + private static final Noop INSTANCE = new Noop(); + + @Override + void format(final StringBuilder output) { + // no-op + } + } + + private static final class Space extends SimpleLiteralPatternConverter { + private static final Space INSTANCE = new Space(); + + @Override + void format(final StringBuilder output) { + output.append(' '); + } + } + + private static final class StringValue extends SimpleLiteralPatternConverter { + + private final String literal; + + StringValue(final String literal) { + this.literal = literal; + } + + @Override + void format(final StringBuilder output) { + output.append(literal); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java index acbf2b677ed..e102f7306a1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java @@ -21,9 +21,10 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -34,7 +35,8 @@ * disable ANSI output if no console is detected, specify option noConsoleNoAnsi=true. *

    */ -@Plugin(name = "style", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("style") @ConverterKeys({ "style" }) @PerformanceSensitive("allocation") public final class StyleConverter extends LogEventPatternConverter implements AnsiConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java index 2355c88900f..3a9e2103514 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/TextRenderer.java @@ -23,7 +23,7 @@ public interface TextRenderer { /** * Renders input text to an output. - * + * * @param input * The input * @param output @@ -35,7 +35,7 @@ public interface TextRenderer { /** * Renders input text to an output. - * + * * @param input * The input * @param output diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java index 1f5bc12bd99..ae4aca18f05 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java @@ -17,7 +17,8 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -25,7 +26,8 @@ * * @since 2.6 */ -@Plugin(name = "ThreadIdPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("ThreadIdPatternConverter") @ConverterKeys({ "T", "tid", "threadId" }) @PerformanceSensitive("allocation") public final class ThreadIdPatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java index de3e824aad8..1fe60dd78ba 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java @@ -17,13 +17,15 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * Formats the event thread name. */ -@Plugin(name = "ThreadPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("ThreadPatternConverter") @ConverterKeys({ "t", "tn", "thread", "threadName" }) @PerformanceSensitive("allocation") public final class ThreadNamePatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java index b520892b594..079cfeed634 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java @@ -17,7 +17,8 @@ package org.apache.logging.log4j.core.pattern; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** @@ -25,7 +26,8 @@ * * @since 2.6 */ -@Plugin(name = "ThreadPriorityPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("ThreadPriorityPatternConverter") @ConverterKeys({ "tp", "threadPriority" }) @PerformanceSensitive("allocation") public final class ThreadPriorityPatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java index eadb3f3960c..7c25717526b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java @@ -16,26 +16,29 @@ */ package org.apache.logging.log4j.core.pattern; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.impl.ThrowableFormatOptions; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.util.StringBuilderWriter; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.Strings; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Outputs the Throwable portion of the LoggingEvent as a full stack trace * unless this converter's option is 'short', where it just outputs the first line of the trace, or if * the number of lines to print is explicitly specified. */ -@Plugin(name = "ThrowablePatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("ThrowablePatternConverter") @ConverterKeys({ "ex", "throwable", "exception" }) public class ThrowablePatternConverter extends LogEventPatternConverter { @@ -44,6 +47,8 @@ public class ThrowablePatternConverter extends LogEventPatternConverter { */ protected final List formatters; private String rawOption; + private final boolean subShortOption; + private final boolean nonStandardLineSeparator; /** * Options. @@ -87,7 +92,13 @@ protected ThrowablePatternConverter(final String name, final String style, final } else { this.formatters = Collections.emptyList(); } - + subShortOption = ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption) || + ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption) || + ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption) || + ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption) || + ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption) || + ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption); + nonStandardLineSeparator = !Strings.LINE_SEPARATOR.equals(this.options.getSeparator()); } /** @@ -109,7 +120,7 @@ public static ThrowablePatternConverter newInstance(final Configuration config, public void format(final LogEvent event, final StringBuilder buffer) { final Throwable t = event.getThrown(); - if (isSubShortOption()) { + if (subShortOption) { formatSubShortOption(t, getSuffix(event), buffer); } else if (t != null && options.anyLines()) { @@ -117,19 +128,10 @@ else if (t != null && options.anyLines()) { } } - private boolean isSubShortOption() { - return ThrowableFormatOptions.MESSAGE.equalsIgnoreCase(rawOption) || - ThrowableFormatOptions.LOCALIZED_MESSAGE.equalsIgnoreCase(rawOption) || - ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption) || - ThrowableFormatOptions.LINE_NUMBER.equalsIgnoreCase(rawOption) || - ThrowableFormatOptions.METHOD_NAME.equalsIgnoreCase(rawOption) || - ThrowableFormatOptions.CLASS_NAME.equalsIgnoreCase(rawOption); - } - private void formatSubShortOption(final Throwable t, final String suffix, final StringBuilder buffer) { - StackTraceElement[] trace; + final StackTraceElement[] trace; StackTraceElement throwingMethod = null; - int len; + final int len; if (t != null) { trace = t.getStackTrace(); @@ -174,32 +176,29 @@ else if (ThrowableFormatOptions.FILE_NAME.equalsIgnoreCase(rawOption)) { } private void formatOption(final Throwable throwable, final String suffix, final StringBuilder buffer) { - final StringWriter w = new StringWriter(); - - throwable.printStackTrace(new PrintWriter(w)); final int len = buffer.length(); if (len > 0 && !Character.isWhitespace(buffer.charAt(len - 1))) { buffer.append(' '); } - if (!options.allLines() || !Strings.LINE_SEPARATOR.equals(options.getSeparator()) || Strings.isNotBlank(suffix)) { - final StringBuilder sb = new StringBuilder(); + if (!options.allLines() || nonStandardLineSeparator || Strings.isNotBlank(suffix)) { + final StringWriter w = new StringWriter(); + throwable.printStackTrace(new PrintWriter(w)); + final String[] array = w.toString().split(Strings.LINE_SEPARATOR); final int limit = options.minLines(array.length) - 1; final boolean suffixNotBlank = Strings.isNotBlank(suffix); for (int i = 0; i <= limit; ++i) { - sb.append(array[i]); + buffer.append(array[i]); if (suffixNotBlank) { - sb.append(' '); - sb.append(suffix); + buffer.append(' '); + buffer.append(suffix); } if (i < limit) { - sb.append(options.getSeparator()); + buffer.append(options.getSeparator()); } } - buffer.append(sb.toString()); - } else { - buffer.append(w.toString()); + throwable.printStackTrace(new PrintWriter(new StringBuilderWriter(buffer))); } } @@ -214,6 +213,9 @@ public boolean handlesThrowable() { } protected String getSuffix(final LogEvent event) { + if (formatters.isEmpty()) { + return Strings.EMPTY; + } //noinspection ForLoopReplaceableByForEach final StringBuilder toAppendTo = new StringBuilder(); for (int i = 0, size = formatters.size(); i < size; i++) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java index 439935bedbb..6d943c7d33d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java @@ -16,16 +16,18 @@ */ package org.apache.logging.log4j.core.pattern; -import java.util.UUID; - import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.util.UuidUtil; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; + +import java.util.UUID; /** * Formats the event sequence number. */ -@Plugin(name = "UuidPatternConverter", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("UuidPatternConverter") @ConverterKeys({ "u", "uuid" }) public final class UuidPatternConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java index 1f0d8274796..c513ff5cd00 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java @@ -20,14 +20,16 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.plugins.Namespace; +import org.apache.logging.log4j.plugins.Plugin; import org.apache.logging.log4j.util.PerformanceSensitive; /** * VariablesNotEmpty pattern converter. */ -@Plugin(name = "notEmpty", category = PatternConverter.CATEGORY) +@Namespace(PatternConverter.CATEGORY) +@Plugin("notEmpty") @ConverterKeys({ "notEmpty", "varsNotEmpty", "variablesNotEmpty", }) @PerformanceSensitive("allocation") public final class VariablesNotEmptyReplacementConverter extends LogEventPatternConverter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/plugins/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/plugins/package-info.java new file mode 100644 index 00000000000..33dbfd319d5 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/plugins/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * Placeholder package. The plugins class will be generated here and needs to be present . + */ +package org.apache.logging.log4j.core.plugins; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java index b33b1f21f5b..2e4fa1393e6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java @@ -16,47 +16,14 @@ */ package org.apache.logging.log4j.core.script; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.core.config.plugins.PluginValue; - /** - * Container for the language and body of a script. + * Represents int */ -@Plugin(name = Script.PLUGIN_NAME, category = Node.CATEGORY, printObject = true) -public class Script extends AbstractScript { - - private static final String ATTR_LANGUAGE = "language"; - private static final String ATTR_SCRIPT_TEXT = "scriptText"; - static final String PLUGIN_NAME = "Script"; - - public Script(final String name, final String language, final String scriptText) { - super(name, language, scriptText); - } +public interface Script { - @PluginFactory - public static Script createScript( - // @formatter:off - @PluginAttribute("name") final String name, - @PluginAttribute(ATTR_LANGUAGE) String language, - @PluginValue(ATTR_SCRIPT_TEXT) final String scriptText) { - // @formatter:on - if (language == null) { - LOGGER.error("No '{}' attribute provided for {} plugin '{}'", ATTR_LANGUAGE, PLUGIN_NAME, name); - language = DEFAULT_LANGUAGE; - } - if (scriptText == null) { - LOGGER.error("No '{}' attribute provided for {} plugin '{}'", ATTR_SCRIPT_TEXT, PLUGIN_NAME, name); - return null; - } - return new Script(name, language, scriptText); + String getLanguage(); - } + String getScriptText(); - @Override - public String toString() { - return getName() != null ? getName() : super.toString(); - } + String getName(); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptBindings.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptBindings.java new file mode 100644 index 00000000000..181b03dcbea --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptBindings.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.script; + +import java.util.Map; + +/** + * Container for javax.script.Bindings. + */ +public interface ScriptBindings extends Map { + + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptConditional.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptConditional.java new file mode 100644 index 00000000000..d00cddd03d0 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptConditional.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.script; + +import java.nio.file.Path; +import java.util.List; + +import org.apache.logging.log4j.core.appender.rolling.action.PathWithAttributes; + +/** + * Interface for the ScriptCondition plugin. + */ +public interface ScriptConditional { + List selectFilesToDelete(final Path basePath, final List candidates); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java index 1d6deaf520c..3113e71244d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java @@ -16,270 +16,32 @@ */ package org.apache.logging.log4j.core.script; -import java.io.File; -import java.io.Serializable; -import java.nio.file.Path; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import org.apache.logging.log4j.plugins.Node; +import org.apache.logging.log4j.plugins.di.Key; -import javax.script.Bindings; -import javax.script.Compilable; -import javax.script.CompiledScript; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; -import javax.script.SimpleBindings; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.util.FileWatcher; -import org.apache.logging.log4j.core.util.WatchManager; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.util.Strings; +import java.util.Set; /** - * Manages the scripts use by the Configuration. + * Script Manager. */ -public class ScriptManager implements FileWatcher, Serializable { - - private abstract class AbstractScriptRunner implements ScriptRunner { - - private static final String KEY_STATUS_LOGGER = "statusLogger"; - private static final String KEY_CONFIGURATION = "configuration"; - - @Override - public Bindings createBindings() { - final SimpleBindings bindings = new SimpleBindings(); - bindings.put(KEY_CONFIGURATION, configuration); - bindings.put(KEY_STATUS_LOGGER, logger); - return bindings; - } - - } - - private static final long serialVersionUID = -2534169384971965196L; - private static final String KEY_THREADING = "THREADING"; - private static final Logger logger = StatusLogger.getLogger(); - - private final Configuration configuration; - private final ScriptEngineManager manager = new ScriptEngineManager(); - private final ConcurrentMap scriptRunners = new ConcurrentHashMap<>(); - private final String languages; - private final WatchManager watchManager; - - public ScriptManager(final Configuration configuration, final WatchManager watchManager) { - this.configuration = configuration; - this.watchManager = watchManager; - final List factories = manager.getEngineFactories(); - if (logger.isDebugEnabled()) { - final StringBuilder sb = new StringBuilder(); - final int factorySize = factories.size(); - logger.debug("Installed {} script engine{}", factorySize, factorySize != 1 ? "s" : Strings.EMPTY); - for (final ScriptEngineFactory factory : factories) { - String threading = (String) factory.getParameter(KEY_THREADING); - if (threading == null) { - threading = "Not Thread Safe"; - } - final StringBuilder names = new StringBuilder(); - final List languageNames = factory.getNames(); - for (final String name : languageNames) { - if (names.length() > 0) { - names.append(", "); - } - names.append(name); - } - if (sb.length() > 0) { - sb.append(", "); - } - sb.append(names); - final boolean compiled = factory.getScriptEngine() instanceof Compilable; - logger.debug("{} version: {}, language: {}, threading: {}, compile: {}, names: {}, factory class: {}", - factory.getEngineName(), factory.getEngineVersion(), factory.getLanguageName(), threading, - compiled, languageNames, factory.getClass().getName()); - } - languages = sb.toString(); - } else { - final StringBuilder names = new StringBuilder(); - for (final ScriptEngineFactory factory : factories) { - for (final String name : factory.getNames()) { - if (names.length() > 0) { - names.append(", "); - } - names.append(name); - } - } - languages = names.toString(); - } - } - - public void addScript(final AbstractScript script) { - final ScriptEngine engine = manager.getEngineByName(script.getLanguage()); - if (engine == null) { - logger.error("No ScriptEngine found for language " + script.getLanguage() + ". Available languages are: " - + languages); - return; - } - if (engine.getFactory().getParameter(KEY_THREADING) == null) { - scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script)); - } else { - scriptRunners.put(script.getName(), new MainScriptRunner(engine, script)); - } - - if (script instanceof ScriptFile) { - final ScriptFile scriptFile = (ScriptFile) script; - final Path path = scriptFile.getPath(); - if (scriptFile.isWatched() && path != null) { - watchManager.watchFile(path.toFile(), this); - } - } - } - - public Bindings createBindings(final AbstractScript script) { - return getScriptRunner(script).createBindings(); - } - - public AbstractScript getScript(final String name) { - final ScriptRunner runner = scriptRunners.get(name); - return runner != null ? runner.getScript() : null; - } - - @Override - public void fileModified(final File file) { - final ScriptRunner runner = scriptRunners.get(file.toString()); - if (runner == null) { - logger.info("{} is not a running script"); - return; - } - final ScriptEngine engine = runner.getScriptEngine(); - final AbstractScript script = runner.getScript(); - if (engine.getFactory().getParameter(KEY_THREADING) == null) { - scriptRunners.put(script.getName(), new ThreadLocalScriptRunner(script)); - } else { - scriptRunners.put(script.getName(), new MainScriptRunner(engine, script)); - } - - } - - public Object execute(final String name, final Bindings bindings) { - final ScriptRunner scriptRunner = scriptRunners.get(name); - if (scriptRunner == null) { - logger.warn("No script named {} could be found"); - return null; - } - return AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - return scriptRunner.execute(bindings); - } - }); - } - - private interface ScriptRunner { - - Bindings createBindings(); - - Object execute(Bindings bindings); - - AbstractScript getScript(); - - ScriptEngine getScriptEngine(); - } - - private class MainScriptRunner extends AbstractScriptRunner { - private final AbstractScript script; - private final CompiledScript compiledScript; - private final ScriptEngine scriptEngine; - - public MainScriptRunner(final ScriptEngine scriptEngine, final AbstractScript script) { - this.script = script; - this.scriptEngine = scriptEngine; - CompiledScript compiled = null; - if (scriptEngine instanceof Compilable) { - logger.debug("Script {} is compilable", script.getName()); - compiled = AccessController.doPrivileged(new PrivilegedAction() { - @Override - public CompiledScript run() { - try { - return ((Compilable) scriptEngine).compile(script.getScriptText()); - } catch (final Throwable ex) { - /* - * ScriptException is what really should be caught here. However, beanshell's ScriptEngine - * implements Compilable but then throws Error when the compile method is called! - */ - logger.warn("Error compiling script", ex); - return null; - } - } - }); - } - compiledScript = compiled; - } - - @Override - public ScriptEngine getScriptEngine() { - return this.scriptEngine; - } - - @Override - public Object execute(final Bindings bindings) { - if (compiledScript != null) { - try { - return compiledScript.eval(bindings); - } catch (final ScriptException ex) { - logger.error("Error running script " + script.getName(), ex); - return null; - } - } - try { - return scriptEngine.eval(script.getScriptText(), bindings); - } catch (final ScriptException ex) { - logger.error("Error running script " + script.getName(), ex); - return null; - } - } - - @Override - public AbstractScript getScript() { - return script; - } - } +public interface ScriptManager { + Key KEY = new Key<>() {}; - private class ThreadLocalScriptRunner extends AbstractScriptRunner { - private final AbstractScript script; + /** + * Add scripts defined in the configuration. + * @param child The Scripts node. + */ + void addScripts(Node child); - private final ThreadLocal runners = new ThreadLocal() { - @Override - protected MainScriptRunner initialValue() { - final ScriptEngine engine = manager.getEngineByName(script.getLanguage()); - return new MainScriptRunner(engine, script); - } - }; + boolean addScript(final Script script); - public ThreadLocalScriptRunner(final AbstractScript script) { - this.script = script; - } + boolean isScriptRef(final Script script); - @Override - public Object execute(final Bindings bindings) { - return runners.get().execute(bindings); - } + Set getAllowedLanguages(); - @Override - public AbstractScript getScript() { - return script; - } + ScriptBindings createBindings(final Script script); - @Override - public ScriptEngine getScriptEngine() { - return runners.get().getScriptEngine(); - } - } + Script getScript(final String name); - private ScriptRunner getScriptRunner(final AbstractScript script) { - return scriptRunners.get(script.getName()); - } + Object execute(final String name, final ScriptBindings bindings); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManagerFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManagerFactory.java new file mode 100644 index 00000000000..d4711d3b9a7 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManagerFactory.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.script; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.util.WatchManager; + +/** + * Interface to create a ScriptManager. + */ +public interface ScriptManagerFactory { + + ScriptManager createScriptManager(Configuration configuration, WatchManager watchManager); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/package-info.java deleted file mode 100644 index 4964b309855..00000000000 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -/** - * Log4j 2 Script support. - */ -package org.apache.logging.log4j.core.script; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java index 502084dca96..4d3e0299751 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java @@ -1,66 +1,117 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.selector; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.impl.ContextAnchor; - -/** - * Returns either this Thread's context or the default LoggerContext. - */ -public class BasicContextSelector implements ContextSelector { - - private static final LoggerContext CONTEXT = new LoggerContext("Default"); - - @Override - public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { - - final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); - return ctx != null ? ctx : CONTEXT; - } - - - @Override - public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, - final URI configLocation) { - - final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); - return ctx != null ? ctx : CONTEXT; - } - - public LoggerContext locateContext(final String name, final String configLocation) { - return CONTEXT; - } - - @Override - public void removeContext(final LoggerContext context) { - // does not remove anything - } - - @Override - public List getLoggerContexts() { - final List list = new ArrayList<>(); - list.add(CONTEXT); - return Collections.unmodifiableList(list); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.selector; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.impl.ContextAnchor; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.spi.LoggerContextShutdownAware; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Lazy; + +/** + * Returns either this Thread's context or the default LoggerContext. + */ +@Singleton +public class BasicContextSelector implements ContextSelector, LoggerContextShutdownAware { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + protected final Lazy context = Lazy.lazy(this::createContext); + protected final Injector injector; + + @Inject + public BasicContextSelector(final Injector injector) { + this.injector = injector; + } + + @Override + public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext, final boolean allContexts) { + final LoggerContext ctx = getContext(fqcn, loader, currentContext); + if (ctx != null && ctx.isStarted()) { + ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS); + } + } + + @Override + public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext loggerContext) { + if (loggerContext instanceof LoggerContext) { + removeContext((LoggerContext) loggerContext); + } + } + + @Override + public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + final LoggerContext ctx = getContext(fqcn, loader, currentContext); + return ctx != null && ctx.isStarted(); + } + + @Override + public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + return getContext(fqcn, loader, currentContext, null); + } + + @Override + public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, + final URI configLocation) { + if (currentContext) { + final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); + if (ctx != null) { + return ctx; + } + } + final LoggerContext ctx = context.get(); + if (configLocation != null) { + if (ctx.getConfigLocation() == null) { + LOGGER.debug("Setting configuration to {}", configLocation); + ctx.setConfigLocation(configLocation); + } else if (!ctx.getConfigLocation().equals(configLocation)) { + LOGGER.warn("getContext called with URI {}. Existing LoggerContext has URI {}", configLocation, + ctx.getConfigLocation()); + } + } + return ctx; + } + + @Override + public void removeContext(final LoggerContext context) { + if (context == this.context.get()) { + this.context.set(null); + } + } + + @Override + public boolean isClassLoaderDependent() { + return false; + } + + @Override + public List getLoggerContexts() { + return List.of(context.value()); + } + + protected LoggerContext createContext() { + return new LoggerContext("Default", null, (URI) null, injector); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java index 8ffe5a5256e..f5f91b73836 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java @@ -24,12 +24,17 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.impl.ContextAnchor; +import org.apache.logging.log4j.plugins.Inject; +import org.apache.logging.log4j.plugins.Singleton; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.spi.LoggerContextShutdownAware; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Lazy; import org.apache.logging.log4j.util.StackLocatorUtil; /** @@ -38,19 +43,84 @@ * Loggers associated with classes loaded from different ClassLoaders to be co-mingled. This is a problem if, for * example, a web application is undeployed as some of the Loggers being released may be associated with a Class in a * parent ClassLoader, which will generally have negative consequences. - * + *

    * The main downside to this ContextSelector is that Configuration is more challenging. - * + *

    * This ContextSelector should not be used with a Servlet Filter such as the Log4jServletFilter. */ -public class ClassLoaderContextSelector implements ContextSelector { - - private static final AtomicReference DEFAULT_CONTEXT = new AtomicReference<>(); +@Singleton +public class ClassLoaderContextSelector implements ContextSelector, LoggerContextShutdownAware { protected static final StatusLogger LOGGER = StatusLogger.getLogger(); - protected static final ConcurrentMap>> CONTEXT_MAP = - new ConcurrentHashMap<>(); + protected final Lazy defaultContext = Lazy.lazy(() -> createContext(defaultContextName(), null)); + protected final Map>> contextMap = new ConcurrentHashMap<>(); + + protected final Injector injector; + + @Inject + public ClassLoaderContextSelector(final Injector injector) { + this.injector = injector; + } + + @Override + public void shutdown( + final String fqcn, final ClassLoader loader, final boolean currentContext, + final boolean allContexts) { + LoggerContext ctx = null; + if (currentContext) { + ctx = ContextAnchor.THREAD_CONTEXT.get(); + } else if (loader != null) { + ctx = findContext(loader); + } else { + final Class clazz = StackLocatorUtil.getCallerClass(fqcn); + if (clazz != null) { + ctx = findContext(clazz.getClassLoader()); + } + if (ctx == null) { + ctx = ContextAnchor.THREAD_CONTEXT.get(); + } + } + if (ctx != null) { + ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS); + } + } + + @Override + public void contextShutdown(final org.apache.logging.log4j.spi.LoggerContext loggerContext) { + if (loggerContext instanceof LoggerContext) { + removeContext((LoggerContext) loggerContext); + } + } + + @Override + public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + final LoggerContext ctx; + if (currentContext) { + ctx = ContextAnchor.THREAD_CONTEXT.get(); + } else if (loader != null) { + ctx = findContext(loader); + } else { + final Class clazz = StackLocatorUtil.getCallerClass(fqcn); + if (clazz != null) { + ctx = findContext(clazz.getClassLoader()); + } else { + ctx = ContextAnchor.THREAD_CONTEXT.get(); + } + } + return ctx != null && ctx.isStarted(); + } + + private LoggerContext findContext(final ClassLoader loaderOrNull) { + final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader(); + final String name = toContextMapKey(loader); + final AtomicReference> ref = contextMap.get(name); + if (ref != null) { + final WeakReference weakRef = ref.get(); + return weakRef.get(); + } + return null; + } @Override public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { @@ -58,8 +128,16 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin } @Override - public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, + public LoggerContext getContext( + final String fqcn, final ClassLoader loader, final boolean currentContext, final URI configLocation) { + return getContext(fqcn, loader, null, currentContext, configLocation); + } + + @Override + public LoggerContext getContext( + final String fqcn, final ClassLoader loader, final Map.Entry entry, + final boolean currentContext, final URI configLocation) { if (currentContext) { final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); if (ctx != null) { @@ -67,11 +145,11 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin } return getDefault(); } else if (loader != null) { - return locateContext(loader, configLocation); + return locateContext(loader, entry, configLocation); } else { final Class clazz = StackLocatorUtil.getCallerClass(fqcn); if (clazz != null) { - return locateContext(clazz.getClassLoader(), configLocation); + return locateContext(clazz.getClassLoader(), entry, configLocation); } final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get(); if (lc != null) { @@ -83,18 +161,24 @@ public LoggerContext getContext(final String fqcn, final ClassLoader loader, fin @Override public void removeContext(final LoggerContext context) { - for (final Map.Entry>> entry : CONTEXT_MAP.entrySet()) { + for (final Map.Entry>> entry : contextMap.entrySet()) { final LoggerContext ctx = entry.getValue().get().get(); if (ctx == context) { - CONTEXT_MAP.remove(entry.getKey()); + contextMap.remove(entry.getKey()); } } } + @Override + public boolean isClassLoaderDependent() { + // By definition the ClassLoaderContextSelector depends on the callers class loader. + return true; + } + @Override public List getLoggerContexts() { final List list = new ArrayList<>(); - final Collection>> coll = CONTEXT_MAP.values(); + final Collection>> coll = contextMap.values(); for (final AtomicReference> ref : coll) { final LoggerContext ctx = ref.get().get(); if (ctx != null) { @@ -104,17 +188,30 @@ public List getLoggerContexts() { return Collections.unmodifiableList(list); } - private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) { + private Injector getOrCopyInjector(final Map.Entry entry) { + if (entry != null) { + final Object value = entry.getValue(); + if (value instanceof Injector) { + return (Injector) value; + } + } + final Injector injector = this.injector; + return injector != null ? injector.copy() : null; + } + + private LoggerContext locateContext( + final ClassLoader loaderOrNull, final Map.Entry entry, + final URI configLocation) { // LOG4J2-477: class loader may be null final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader(); final String name = toContextMapKey(loader); - AtomicReference> ref = CONTEXT_MAP.get(name); + AtomicReference> ref = contextMap.get(name); if (ref == null) { if (configLocation == null) { ClassLoader parent = loader.getParent(); while (parent != null) { - ref = CONTEXT_MAP.get(toContextMapKey(parent)); + ref = contextMap.get(toContextMapKey(parent)); if (ref != null) { final WeakReference r = ref.get(); final LoggerContext ctx = r.get(); @@ -142,16 +239,23 @@ private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI co } */ } } - LoggerContext ctx = createContext(name, configLocation); - final AtomicReference> r = new AtomicReference<>(); - r.set(new WeakReference<>(ctx)); - CONTEXT_MAP.putIfAbsent(name, r); - ctx = CONTEXT_MAP.get(name).get().get(); - return ctx; + final LoggerContext ctx = createContext(name, configLocation, getOrCopyInjector(entry)); + if (entry != null) { + ctx.putObject(entry.getKey(), entry.getValue()); + } + final LoggerContext newContext = contextMap.computeIfAbsent(name, + k -> new AtomicReference<>(new WeakReference<>(ctx))).get().get(); + if (newContext != null && newContext == ctx) { + newContext.addShutdownListener(this); + } + return newContext; } final WeakReference weakRef = ref.get(); LoggerContext ctx = weakRef.get(); if (ctx != null) { + if (entry != null) { + ctx.putObject(entry.getKey(), entry.getValue()); + } if (ctx.getConfigLocation() == null && configLocation != null) { LOGGER.debug("Setting configuration to {}", configLocation); ctx.setConfigLocation(configLocation); @@ -162,13 +266,24 @@ private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI co } return ctx; } - ctx = createContext(name, configLocation); - ref.compareAndSet(weakRef, new WeakReference<>(ctx)); + ctx = createContext(name, configLocation, getOrCopyInjector(entry)); + if (entry != null) { + ctx.putObject(entry.getKey(), entry.getValue()); + } + if (ref.compareAndSet(weakRef, new WeakReference<>(ctx))) { + ctx.addShutdownListener(this); + } return ctx; } protected LoggerContext createContext(final String name, final URI configLocation) { - return new LoggerContext(name, null, configLocation); + final Injector injector = this.injector; + return createContext(name, configLocation, injector != null ? injector.copy() : null); + } + + protected LoggerContext createContext(final String name, final URI configLocation, final Injector injector) { + + return new LoggerContext(name, null, configLocation, injector); } protected String toContextMapKey(final ClassLoader loader) { @@ -176,12 +291,7 @@ protected String toContextMapKey(final ClassLoader loader) { } protected LoggerContext getDefault() { - final LoggerContext ctx = DEFAULT_CONTEXT.get(); - if (ctx != null) { - return ctx; - } - DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null)); - return DEFAULT_CONTEXT.get(); + return defaultContext.value(); } protected String defaultContextName() { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java index 65c4dd781be..e98323a1aa0 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java @@ -16,16 +16,51 @@ */ package org.apache.logging.log4j.core.selector; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.plugins.di.Key; + import java.net.URI; import java.util.List; - -import org.apache.logging.log4j.core.LoggerContext; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * Interface used to locate a LoggerContext. */ public interface ContextSelector { + long DEFAULT_STOP_TIMEOUT = 50; + + Key KEY = new Key<>() {}; + + /** + * Shuts down the LoggerContext. + * @param fqcn The fully qualified class name of the caller. + * @param loader The ClassLoader to use or null. + * @param currentContext If true returns the current Context, if false returns the Context appropriate + * @param allContexts if true all LoggerContexts that can be located will be shutdown. + * @since 2.13.0 + */ + default void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext, + final boolean allContexts) { + if (hasContext(fqcn, loader, currentContext)) { + getContext(fqcn, loader, currentContext).stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS); + } + } + + /** + * Checks to see if a LoggerContext is installed. The default implementation returns false. + * @param fqcn The fully qualified class name of the caller. + * @param loader The ClassLoader to use or null. + * @param currentContext If true returns the current Context, if false returns the Context appropriate + * for the caller if a more appropriate Context can be determined. + * @return true if a LoggerContext has been installed, false otherwise. + * @since 2.13.0 + */ + default boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { + return false; + } + /** * Returns the LoggerContext. * @param fqcn The fully qualified class name of the caller. @@ -36,6 +71,24 @@ public interface ContextSelector { */ LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext); + /** + * Returns the LoggerContext. + * @param fqcn The fully qualified class name of the caller. + * @param loader ClassLoader to use or null. + * @param entry An entry for the external Context map. + * @param currentContext If true returns the current Context, if false returns the Context appropriate + * for the caller if a more appropriate Context can be determined. + * @return The LoggerContext. + */ + default LoggerContext getContext(final String fqcn, final ClassLoader loader, final Map.Entry entry, final boolean currentContext) { + final LoggerContext lc = getContext(fqcn, loader, currentContext); + if (entry != null) { + lc.putObject(entry.getKey(), entry.getValue()); + } + return lc; + } + + /** * Returns the LoggerContext. * @param fqcn The fully qualified class name of the caller. @@ -47,6 +100,24 @@ public interface ContextSelector { */ LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext, URI configLocation); + /** + * Returns the LoggerContext. + * @param fqcn The fully qualified class name of the caller. + * @param loader ClassLoader to use or null. + * @param currentContext If true returns the current Context, if false returns the Context appropriate + * for the caller if a more appropriate Context can be determined. + * @param configLocation The location of the configuration for the LoggerContext. + * @return The LoggerContext. + */ + default LoggerContext getContext(final String fqcn, final ClassLoader loader, final Map.Entry entry, + final boolean currentContext, final URI configLocation) { + final LoggerContext lc = getContext(fqcn, loader, currentContext, configLocation); + if (entry != null) { + lc.putObject(entry.getKey(), entry.getValue()); + } + return lc; + } + /** * Returns a List of all the available LoggerContexts. * @return The List of LoggerContexts. @@ -58,4 +129,16 @@ public interface ContextSelector { * @param context The context to remove. */ void removeContext(LoggerContext context); + + /** + * Determines whether or not this ContextSelector depends on the callers classloader. + * This method should be overridden by implementations, however a default method is provided which always + * returns {@code true} to preserve the old behavior. + * + * @return true if the class loader is required. + * @since 2.15.0 + */ + default boolean isClassLoaderDependent() { + return true; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java index 1077e5a1778..2ee6b0d4f6f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/CoreContextSelectors.java @@ -17,9 +17,15 @@ package org.apache.logging.log4j.core.selector; import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; +import org.apache.logging.log4j.core.async.BasicAsyncLoggerContextSelector; public class CoreContextSelectors { - public static final Class[] CLASSES = new Class[] { ClassLoaderContextSelector.class, BasicContextSelector.class, AsyncLoggerContextSelector.class }; + public static final Class[] CLASSES = new Class[] { + ClassLoaderContextSelector.class, + BasicContextSelector.class, + AsyncLoggerContextSelector.class, + BasicAsyncLoggerContextSelector.class + }; } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Clock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Clock.java new file mode 100644 index 00000000000..e35ba4df625 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Clock.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time; + +import org.apache.logging.log4j.plugins.di.Key; + +/** + * Provides the time stamp used in log events. + * + * @since 2.11 + */ +public interface Clock { + Key KEY = new Key<>() {}; + + /** + * Returns the time in milliseconds since the epoch. + * + * @return the time in milliseconds since the epoch + */ + long currentTimeMillis(); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java new file mode 100644 index 00000000000..8b0ebfcbb8d --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time; + +import org.apache.logging.log4j.core.impl.Log4jProperties; +import org.apache.logging.log4j.core.time.internal.CachedClock; +import org.apache.logging.log4j.core.time.internal.CoarseCachedClock; +import org.apache.logging.log4j.core.time.internal.SystemClock; +import org.apache.logging.log4j.plugins.di.DI; +import org.apache.logging.log4j.plugins.di.Injector; +import org.apache.logging.log4j.util.Lazy; + +/** + * Factory for {@code Clock} objects. + * + * @since 2.11 + */ +public final class ClockFactory { + + /** + * Name of the system property that can be used to specify a {@code Clock} + * implementation class. The value of this property is {@value}. + */ + public static final String PROPERTY_NAME = Log4jProperties.CONFIG_CLOCK; + private static final Lazy FALLBACK = Lazy.lazy(() -> { + // TODO(ms): split out clock bindings for smaller fallback init + final Injector injector = DI.createInjector(); + injector.init(); + return injector.getInstance(Clock.KEY); + }); + + /** + * Returns a {@code Clock} instance depending on the value of system + * property {@link #PROPERTY_NAME}. + *

    + * If system property {@code log4j.Clock=CachedClock} is specified, + * this method returns an instance of {@link CachedClock}. If system + * property {@code log4j.Clock=CoarseCachedClock} is specified, this + * method returns an instance of {@link CoarseCachedClock}. + *

    + *

    + * If another value is specified, this value is taken as the fully qualified + * class name of a class that implements the {@code Clock} interface. An + * object of this class is instantiated and returned. + *

    + *

    + * If no value is specified, or if the specified value could not correctly + * be instantiated or did not implement the {@code Clock} interface, then an + * instance of {@link SystemClock} is returned. + *

    + * + * @return a {@code Clock} instance + */ + @Deprecated + public static Clock getClock() { + return FALLBACK.value(); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Instant.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Instant.java index ab50da11a69..f41b6c1fe2b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Instant.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Instant.java @@ -16,7 +16,6 @@ */ package org.apache.logging.log4j.core.time; -import org.apache.logging.log4j.core.util.Clock; import org.apache.logging.log4j.util.StringBuilderFormattable; /** @@ -25,14 +24,16 @@ * Provides methods for obtaining high precision time information similar to the * Instant class introduced in Java 8, * while also supporting the legacy millisecond precision API. - *

    + *

    + *

    * Depending on the platform, time sources ({@link Clock} implementations) may produce high precision or millisecond * precision time values. At the same time, some time value consumers (for example timestamp formatters) may only be * able to consume time values of millisecond precision, while some others may require a high precision time value. - *

    + *

    + *

    * This class bridges these two time APIs. *

    - * @since 2.11 + * @since 2.11.0 */ public interface Instant extends StringBuilderFormattable { /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/InstantFormatter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/InstantFormatter.java new file mode 100644 index 00000000000..b83bde1739c --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/InstantFormatter.java @@ -0,0 +1,372 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time; + +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; + +import org.apache.logging.log4j.core.time.internal.format.FastDateFormat; +import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; + +/** + * A composite {@link Instant} formatter trying to employ either {@link FixedDateFormat}, {@link FastDateFormat}, or {@link DateTimeFormatter} in the given order due to performance reasons. + *

    + * Note that {@code FixedDateFormat} and {@code FastDateFormat} only support millisecond precision. + * If the pattern asks for a higher precision, {@code DateTimeFormatter} will be employed, which is significantly slower. + *

    + */ +public final class InstantFormatter { + + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + + /** + * The list of formatter factories in decreasing efficiency order. + */ + private static final FormatterFactory[] FORMATTER_FACTORIES = { + new Log4jFixedFormatterFactory(), + new Log4jFastFormatterFactory(), + new JavaDateTimeFormatterFactory() + }; + + private final Formatter formatter; + + private InstantFormatter(final Builder builder) { + this.formatter = Arrays + .stream(FORMATTER_FACTORIES) + .map(formatterFactory -> { + try { + return formatterFactory.createIfSupported( + builder.getPattern(), + builder.getLocale(), + builder.getTimeZone()); + } catch (final Exception error) { + LOGGER.warn("skipping the failed formatter factory \"{}\"", formatterFactory, error); + return null; + } + }) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new AssertionError("could not find a matching formatter")); + } + + public String format(final Instant instant) { + Objects.requireNonNull(instant, "instant"); + final StringBuilder stringBuilder = new StringBuilder(); + formatter.format(instant, stringBuilder); + return stringBuilder.toString(); + } + + public void format(final Instant instant, final StringBuilder stringBuilder) { + Objects.requireNonNull(instant, "instant"); + Objects.requireNonNull(stringBuilder, "stringBuilder"); + formatter.format(instant, stringBuilder); + } + + /** + * Checks if the given {@link Instant}s are equal from the point of view of the employed formatter. + *

    + * This method should be preferred over {@link Instant#equals(Object)}. + * For instance, {@link FixedDateFormat} and {@link FastDateFormat} discard nanoseconds, hence, from their point of view, two different {@code Instant}s are equal if they match up to millisecond precision. + *

    + */ + public boolean isInstantMatching(final Instant instant1, final Instant instant2) { + return formatter.isInstantMatching(instant1, instant2); + } + + public Class getInternalImplementationClass() { + return formatter.getInternalImplementationClass(); + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static final class Builder { + + private String pattern; + + private Locale locale = Locale.getDefault(); + + private TimeZone timeZone = TimeZone.getDefault(); + + private Builder() {} + + public String getPattern() { + return pattern; + } + + public Builder setPattern(final String pattern) { + this.pattern = pattern; + return this; + } + + public Locale getLocale() { + return locale; + } + + public Builder setLocale(final Locale locale) { + this.locale = locale; + return this; + } + + public TimeZone getTimeZone() { + return timeZone; + } + + public Builder setTimeZone(final TimeZone timeZone) { + this.timeZone = timeZone; + return this; + } + + public InstantFormatter build() { + validate(); + return new InstantFormatter(this); + } + + private void validate() { + if (Strings.isBlank(pattern)) { + throw new IllegalArgumentException("blank pattern"); + } + Objects.requireNonNull(locale, "locale"); + Objects.requireNonNull(timeZone, "timeZone"); + } + + } + + private interface FormatterFactory { + + Formatter createIfSupported( + String pattern, + Locale locale, + TimeZone timeZone); + + } + + private interface Formatter { + + Class getInternalImplementationClass(); + + void format(Instant instant, StringBuilder stringBuilder); + + boolean isInstantMatching(Instant instant1, Instant instant2); + + } + + private static final class JavaDateTimeFormatterFactory implements FormatterFactory { + + @Override + public Formatter createIfSupported( + final String pattern, + final Locale locale, + final TimeZone timeZone) { + return new JavaDateTimeFormatter(pattern, locale, timeZone); + } + + } + + private static final class JavaDateTimeFormatter implements Formatter { + + private final DateTimeFormatter formatter; + + private final MutableInstant mutableInstant; + + private JavaDateTimeFormatter( + final String pattern, + final Locale locale, + final TimeZone timeZone) { + this.formatter = DateTimeFormatter + .ofPattern(pattern) + .withLocale(locale) + .withZone(timeZone.toZoneId()); + this.mutableInstant = new MutableInstant(); + } + + @Override + public Class getInternalImplementationClass() { + return DateTimeFormatter.class; + } + + @Override + public void format( + final Instant instant, + final StringBuilder stringBuilder) { + if (instant instanceof MutableInstant) { + formatMutableInstant((MutableInstant) instant, stringBuilder); + } else { + formatInstant(instant, stringBuilder); + } + } + + private void formatMutableInstant( + final MutableInstant instant, + final StringBuilder stringBuilder) { + formatter.formatTo(instant, stringBuilder); + } + + private void formatInstant( + final Instant instant, + final StringBuilder stringBuilder) { + mutableInstant.initFrom(instant); + formatMutableInstant(mutableInstant, stringBuilder); + } + + @Override + public boolean isInstantMatching(final Instant instant1, final Instant instant2) { + return instant1.getEpochSecond() == instant2.getEpochSecond() && + instant1.getNanoOfSecond() == instant2.getNanoOfSecond(); + } + + } + + private static final class Log4jFastFormatterFactory implements FormatterFactory { + + @Override + public Formatter createIfSupported( + final String pattern, + final Locale locale, + final TimeZone timeZone) { + final Log4jFastFormatter formatter = + new Log4jFastFormatter(pattern, locale, timeZone); + final boolean patternSupported = + patternSupported(pattern, locale, timeZone, formatter); + return patternSupported ? formatter : null; + } + + } + + private static final class Log4jFastFormatter implements Formatter { + + private final FastDateFormat formatter; + + private final Calendar calendar; + + private Log4jFastFormatter( + final String pattern, + final Locale locale, + final TimeZone timeZone) { + this.formatter = FastDateFormat.getInstance(pattern, timeZone, locale); + this.calendar = Calendar.getInstance(timeZone, locale); + } + + @Override + public Class getInternalImplementationClass() { + return FastDateFormat.class; + } + + @Override + public void format( + final Instant instant, + final StringBuilder stringBuilder) { + calendar.setTimeInMillis(instant.getEpochMillisecond()); + formatter.format(calendar, stringBuilder); + } + + @Override + public boolean isInstantMatching(final Instant instant1, final Instant instant2) { + return instant1.getEpochMillisecond() == instant2.getEpochMillisecond(); + } + + } + + private static final class Log4jFixedFormatterFactory implements FormatterFactory { + + @Override + public Formatter createIfSupported( + final String pattern, + final Locale locale, + final TimeZone timeZone) { + final FixedDateFormat internalFormatter = + FixedDateFormat.createIfSupported(pattern, timeZone.getID()); + if (internalFormatter == null) { + return null; + } + final Log4jFixedFormatter formatter = + new Log4jFixedFormatter(internalFormatter); + final boolean patternSupported = + patternSupported(pattern, locale, timeZone, formatter); + return patternSupported ? formatter : null; + } + + } + + private static final class Log4jFixedFormatter implements Formatter { + + private final FixedDateFormat formatter; + + private final char[] buffer; + + private Log4jFixedFormatter(final FixedDateFormat formatter) { + this.formatter = formatter; + this.buffer = new char[formatter.getFormat().length()]; + } + + @Override + public Class getInternalImplementationClass() { + return FixedDateFormat.class; + } + + @Override + public void format( + final Instant instant, + final StringBuilder stringBuilder) { + final int length = formatter.formatInstant(instant, buffer, 0); + stringBuilder.append(buffer, 0, length); + } + + @Override + public boolean isInstantMatching(final Instant instant1, final Instant instant2) { + return formatter.isEquivalent( + instant1.getEpochSecond(), + instant1.getNanoOfSecond(), + instant2.getEpochSecond(), + instant2.getNanoOfSecond()); + } + + } + + /** + * Checks if the provided formatter output matches with the one generated by {@link DateTimeFormatter}. + */ + private static boolean patternSupported( + final String pattern, + final Locale locale, + final TimeZone timeZone, + final Formatter formatter) { + final DateTimeFormatter javaFormatter = DateTimeFormatter + .ofPattern(pattern) + .withLocale(locale) + .withZone(timeZone.toZoneId()); + final MutableInstant instant = new MutableInstant(); + instant.initFromEpochSecond( + // 2021-05-17 21:41:10 + 1621280470, + // Using the highest nanosecond precision possible to differentiate formatters only supporting millisecond precision. + 123_456_789); + final String expectedFormat = javaFormatter.format(instant); + final StringBuilder stringBuilder = new StringBuilder(); + formatter.format(instant, stringBuilder); + final String actualFormat = stringBuilder.toString(); + return expectedFormat.equals(actualFormat); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java index 2e49a961bd6..1737cf326e2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java @@ -16,24 +16,34 @@ */ package org.apache.logging.log4j.core.time; -import org.apache.logging.log4j.core.util.Clock; +import java.io.Serializable; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalQueries; +import java.time.temporal.TemporalQuery; +import java.time.temporal.UnsupportedTemporalTypeException; +import java.time.temporal.ValueRange; + import org.apache.logging.log4j.util.PerformanceSensitive; -import java.io.Serializable; +import static java.time.temporal.ChronoField.*; +import static java.time.temporal.ChronoUnit.NANOS; /** * An instantaneous point on the time line, used for high-precision log event timestamps. - * Modelled on java.time.Instant, + * Modeled on java.time.Instant, * except that this version is mutable to prevent allocating temporary objects that need to be garbage-collected later. *

    * Instances of this class are not thread-safe and should not be shared between threads. *

    * - * @since 2.11 + * @since 2.11.0 */ @PerformanceSensitive("allocation") -public class MutableInstant implements Instant, Serializable { +public class MutableInstant implements Instant, Serializable, TemporalAccessor { + private static final long serialVersionUID = 1L; private static final int MILLIS_PER_SECOND = 1000; private static final int NANOS_PER_MILLI = 1000_000; private static final int NANOS_PER_SECOND = MILLIS_PER_SECOND * NANOS_PER_MILLI; @@ -54,15 +64,14 @@ public int getNanoOfSecond() { @Override public long getEpochMillisecond() { final int millis = nanoOfSecond / NANOS_PER_MILLI; - long epochMillisecond = epochSecond * MILLIS_PER_SECOND + millis; - return epochMillisecond; + return epochSecond * MILLIS_PER_SECOND + millis; } @Override public int getNanoOfMillisecond() { final int millis = nanoOfSecond / NANOS_PER_MILLI; - int nanoOfMillisecond = nanoOfSecond - (millis * NANOS_PER_MILLI); // cheaper than nanoOfSecond % NANOS_PER_MILLI - return nanoOfMillisecond; + // cheaper than nanoOfSecond % NANOS_PER_MILLI + return nanoOfSecond - (millis * NANOS_PER_MILLI); } public void initFrom(final Instant other) { @@ -121,11 +130,72 @@ private void validateNanoOfSecond(final int nano) { * the second element is the number of nanoseconds, later along the time-line, from the start of the millisecond */ public static void instantToMillisAndNanos(final long epochSecond, final int nano, final long[] result) { - int millis = nano / NANOS_PER_MILLI; + final int millis = nano / NANOS_PER_MILLI; result[0] = epochSecond * MILLIS_PER_SECOND + millis; result[1] = nano - (millis * NANOS_PER_MILLI); // cheaper than nanoOfSecond % NANOS_PER_MILLI } + @Override + public boolean isSupported(final TemporalField field) { + if (field instanceof ChronoField) { + return field == INSTANT_SECONDS || + field == NANO_OF_SECOND || + field == MICRO_OF_SECOND || + field == MILLI_OF_SECOND; + } + return field != null && field.isSupportedBy(this); + } + + @Override + public long getLong(final TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case NANO_OF_SECOND: return nanoOfSecond; + case MICRO_OF_SECOND: return nanoOfSecond / 1000; + case MILLI_OF_SECOND: return nanoOfSecond / 1000_000; + case INSTANT_SECONDS: return epochSecond; + } + throw new UnsupportedTemporalTypeException("Unsupported field: " + field); + } + return field.getFrom(this); + } + + @Override + public ValueRange range(final TemporalField field) { + return TemporalAccessor.super.range(field); + } + + @Override + public int get(final TemporalField field) { + if (field instanceof ChronoField) { + switch ((ChronoField) field) { + case NANO_OF_SECOND: return nanoOfSecond; + case MICRO_OF_SECOND: return nanoOfSecond / 1000; + case MILLI_OF_SECOND: return nanoOfSecond / 1000_000; + case INSTANT_SECONDS: INSTANT_SECONDS.checkValidIntValue(epochSecond); + } + throw new UnsupportedTemporalTypeException("Unsupported field: " + field); + } + return range(field).checkValidIntValue(field.getFrom(this), field); + } + + @Override + public R query(final TemporalQuery query) { + if (query == TemporalQueries.precision()) { + return (R) NANOS; + } + // inline TemporalAccessor.super.query(query) as an optimization + if (query == TemporalQueries.chronology() || + query == TemporalQueries.zoneId() || + query == TemporalQueries.zone() || + query == TemporalQueries.offset() || + query == TemporalQueries.localDate() || + query == TemporalQueries.localTime()) { + return null; + } + return query.queryFrom(this); + } + @Override public boolean equals(final Object object) { if (object == this) { @@ -134,7 +204,7 @@ public boolean equals(final Object object) { if (!(object instanceof MutableInstant)) { return false; } - MutableInstant other = (MutableInstant) object; + final MutableInstant other = (MutableInstant) object; return epochSecond == other.epochSecond && nanoOfSecond == other.nanoOfSecond; } @@ -157,4 +227,5 @@ public String toString() { public void formatTo(final StringBuilder buffer) { buffer.append("MutableInstant[epochSecond=").append(epochSecond).append(", nano=").append(nanoOfSecond).append("]"); } + } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NanoClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/NanoClock.java similarity index 79% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/NanoClock.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/NanoClock.java index 8fe8bf871e6..045361f1231 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NanoClock.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/NanoClock.java @@ -1,30 +1,37 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.util; - -/** - * Provides the high-resolution time stamp used in log events. - */ -public interface NanoClock { - /** - * Returns the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds. - * - * @return the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds - */ - long nanoTime(); -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.time; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.plugins.di.Key; + +/** + * Provides the {@link LogEvent#getNanoTime() high-resolution time stamp} used in log events. + * + * @since 2.11 + */ +public interface NanoClock { + Key KEY = new Key<>() {}; + + /** + * Returns the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds. + * + * @return the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds + */ + long nanoTime(); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/PreciseClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/PreciseClock.java index c2416f62ef8..c81ffc7a069 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/PreciseClock.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/PreciseClock.java @@ -16,20 +16,18 @@ */ package org.apache.logging.log4j.core.time; -import org.apache.logging.log4j.core.util.Clock; - /** * Extension of the {@link Clock} interface that is able to provide more accurate time information than milliseconds * since the epoch. {@code PreciseClock} implementations are free to return millisecond-precision time * if that is the most accurate time information available on this platform. - * @since 2.11 + * @since 2.11.0 */ public interface PreciseClock extends Clock { /** * Initializes the specified instant with time information as accurate as available on this platform. * @param mutableInstant the container to be initialized with the accurate time information - * @since 2.11 + * @since 2.11.0 */ void init(final MutableInstant mutableInstant); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemNanoClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/SystemNanoClock.java similarity index 93% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemNanoClock.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/SystemNanoClock.java index 9310e6cc2c7..048ffa128dd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemNanoClock.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/SystemNanoClock.java @@ -1,33 +1,35 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.util; - -/** - * Implementation of the {@code NanoClock} interface that returns the system nano time. - */ -public final class SystemNanoClock implements NanoClock { - - /** - * Returns the system high-resolution time. - * @return the result of calling {@code System.nanoTime()} - */ - @Override - public long nanoTime() { - return System.nanoTime(); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time; + +/** + * Implementation of the {@code NanoClock} interface that returns the system nano time. + * + * @since 2.11 + */ +public final class SystemNanoClock implements NanoClock { + + /** + * Returns the system high-resolution time. + * @return the result of calling {@code System.nanoTime()} + */ + @Override + public long nanoTime() { + return System.nanoTime(); + } + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CachedClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/CachedClock.java similarity index 76% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/CachedClock.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/CachedClock.java index f6d51ab0857..31809604f76 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CachedClock.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/CachedClock.java @@ -14,10 +14,15 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.core.time.internal; import java.util.concurrent.locks.LockSupport; +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.util.Log4jThread; +import org.apache.logging.log4j.plugins.Factory; +import org.apache.logging.log4j.util.Lazy; + /** * Implementation of the {@code Clock} interface that tracks the time in a * private long field that is updated by a background thread once every @@ -28,40 +33,27 @@ */ public final class CachedClock implements Clock { private static final int UPDATE_THRESHOLD = 1000; - private static volatile CachedClock instance; - private static final Object INSTANCE_LOCK = new Object(); + private static final Lazy INSTANCE = Lazy.lazy(CachedClock::new); private volatile long millis = System.currentTimeMillis(); private short count = 0; private CachedClock() { - final Thread updater = new Log4jThread(new Runnable() { - @Override - public void run() { - while (true) { - final long time = System.currentTimeMillis(); - millis = time; + final Thread updater = new Log4jThread(() -> { + while (true) { + millis = System.currentTimeMillis(); - // avoid explicit dependency on sun.misc.Util - LockSupport.parkNanos(1000 * 1000); - } + // avoid explicit dependency on sun.misc.Util + LockSupport.parkNanos(1000 * 1000); } }, "CachedClock Updater Thread"); updater.setDaemon(true); updater.start(); } + @Factory public static CachedClock instance() { // LOG4J2-819: use lazy initialization of threads - CachedClock result = instance; - if (result == null) { - synchronized (INSTANCE_LOCK) { - result = instance; - if (result == null) { - instance = result = new CachedClock(); - } - } - } - return result; + return INSTANCE.value(); } /** diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/CoarseCachedClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/CoarseCachedClock.java new file mode 100644 index 00000000000..1923c113eb2 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/CoarseCachedClock.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time.internal; + +import java.util.concurrent.locks.LockSupport; + +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.util.Log4jThread; +import org.apache.logging.log4j.util.Lazy; + +/** + * This Clock implementation is similar to CachedClock. It is slightly faster at + * the cost of some accuracy. + */ +public final class CoarseCachedClock implements Clock { + private static final Lazy INSTANCE = Lazy.lazy(CoarseCachedClock::new); + // ignore IDE complaints; volatile long is fine + private volatile long millis = System.currentTimeMillis(); + + private CoarseCachedClock() { + Thread updater = new Log4jThread("CoarseCachedClock Updater Thread") { + @Override + public void run() { + while (true) { + millis = System.currentTimeMillis(); + + // avoid explicit dependency on sun.misc.Util + LockSupport.parkNanos(1000 * 1000); + } + } + }; + updater.setDaemon(true); + updater.start(); + } + + /** + * Returns the singleton instance. + * + * @return the singleton instance + */ + public static CoarseCachedClock instance() { + // LOG4J2-819: use lazy initialization of threads + return INSTANCE.value(); + } + + /** + * Returns the value of a private long field that is updated by a background + * thread once every millisecond. Because timers on most platforms do not + * have millisecond granularity, the returned value may "jump" every 10 or + * 16 milliseconds. + * @return the cached time + */ + @Override + public long currentTimeMillis() { + return millis; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/DummyNanoClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/DummyNanoClock.java similarity index 92% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/DummyNanoClock.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/DummyNanoClock.java index e3d6c2b39e5..07bc51bdc71 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/DummyNanoClock.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/DummyNanoClock.java @@ -14,7 +14,9 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.core.time.internal; + +import org.apache.logging.log4j.core.time.NanoClock; /** * Implementation of the {@code NanoClock} interface that always returns a fixed value. @@ -37,7 +39,7 @@ public DummyNanoClock(final long fixedNanoTime) { /** * Returns the constructor value. - * + * * @return the constructor value */ @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/FixedPreciseClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/FixedPreciseClock.java index 868cdbe8464..eae268b8f8a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/FixedPreciseClock.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/FixedPreciseClock.java @@ -21,7 +21,7 @@ /** * Implementation of the {@code PreciseClock} interface that always returns a fixed time value. - * @since 2.11 + * @since 2.11.0 */ public class FixedPreciseClock implements PreciseClock { private final long currentTimeMillis; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/SystemClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/SystemClock.java new file mode 100644 index 00000000000..d09537c2b35 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/SystemClock.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time.internal; + +import java.time.Instant; + +import org.apache.logging.log4j.core.time.Clock; +import org.apache.logging.log4j.core.time.MutableInstant; +import org.apache.logging.log4j.core.time.PreciseClock; +import org.apache.logging.log4j.util.PropertiesUtil; + +/** + * Implementation of the {@code Clock} interface that returns the system time. + * @since 2.11.0 + */ +public final class SystemClock implements Clock, PreciseClock { + + /** + * The precise clock is not enabled by default, since access to it is not garbage free. + */ + private static final boolean USE_PRECISE_CLOCK = PropertiesUtil.getProperties() + .getBooleanProperty("log4j2.usePreciseClock", false); + /** + * Returns the system time. + * @return the result of calling {@code System.currentTimeMillis()} + */ + @Override + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + /** + * {@inheritDoc} + */ + @Override + public void init(final MutableInstant mutableInstant) { + if (USE_PRECISE_CLOCK) { + final Instant instant = java.time.Clock.systemUTC().instant(); + mutableInstant.initFromEpochSecond(instant.getEpochSecond(), instant.getNano()); + } else { + mutableInstant.initFromEpochMilli(System.currentTimeMillis(), 0); + } + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemMillisClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/SystemMillisClock.java similarity index 90% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemMillisClock.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/SystemMillisClock.java index f267320834a..82daab37215 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemMillisClock.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/SystemMillisClock.java @@ -14,11 +14,13 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util; +package org.apache.logging.log4j.core.time.internal; + +import org.apache.logging.log4j.core.time.Clock; /** * Implementation of the {@code Clock} interface that returns the system time in millisecond granularity. - * @since 2.11 + * @since 2.11.0 */ public final class SystemMillisClock implements Clock { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/DateParser.java similarity index 91% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/DateParser.java index 16940c6fa7d..9b59cb22edc 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DateParser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/DateParser.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.text.ParseException; import java.text.ParsePosition; @@ -30,34 +30,34 @@ *

    * Warning: Since binary compatible methods may be added to this interface in any * release, developers are not expected to implement this interface. - * + * *

    * Copied and modified from Apache Commons Lang. *

    - * + * * @since Apache Commons Lang 3.2 */ public interface DateParser { /** - * Equivalent to DateFormat.parse(String). - * - * See {@link java.text.DateFormat#parse(String)} for more information. - * @param source A String whose beginning should be parsed. + * Equivalent to DateFormat.parse(String). + * + * See {@link java.text.DateFormat#parse(String)} for more information. + * @param source A String whose beginning should be parsed. * @return A Date parsed from the string * @throws ParseException if the beginning of the specified string cannot be parsed. */ Date parse(String source) throws ParseException; /** - * Equivalent to DateFormat.parse(String, ParsePosition). - * - * See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information. - * + * Equivalent to DateFormat.parse(String, ParsePosition). + * + * See {@link java.text.DateFormat#parse(String, ParsePosition)} for more information. + * * @param source A String, part of which should be parsed. - * @param pos A ParsePosition object with index and error index information - * as described above. - * @return A Date parsed from the string. In case of error, returns null. + * @param pos A ParsePosition object with index and error index information + * as described above. + * @return A Date parsed from the string. In case of error, returns null. * @throws NullPointerException if text or pos is null. */ Date parse(String source, ParsePosition pos); @@ -74,7 +74,7 @@ public interface DateParser { * @return true, if source has been parsed (pos parsePosition is updated); otherwise false (and pos errorIndex is updated) * @throws IllegalArgumentException when Calendar has been set to be not lenient, and a parsed field is * out of range. - * + * * @since 3.5 */ boolean parse(String source, ParsePosition pos, Calendar calendar); @@ -83,7 +83,7 @@ public interface DateParser { //----------------------------------------------------------------------- /** *

    Gets the pattern used by this parser.

    - * + * * @return the pattern, {@link java.text.SimpleDateFormat} compatible */ String getPattern(); @@ -92,40 +92,40 @@ public interface DateParser { *

    * Gets the time zone used by this parser. *

    - * + * *

    * The default {@link TimeZone} used to create a {@link Date} when the {@link TimeZone} is not specified by * the format pattern. *

    - * + * * @return the time zone */ TimeZone getTimeZone(); /** *

    Gets the locale used by this parser.

    - * + * * @return the locale */ Locale getLocale(); /** * Parses text from a string to produce a Date. - * + * * @param source A String whose beginning should be parsed. * @return a java.util.Date object * @throws ParseException if the beginning of the specified string cannot be parsed. - * @see java.text.DateFormat#parseObject(String) + * @see java.text.DateFormat#parseObject(String) */ Object parseObject(String source) throws ParseException; /** * Parses a date/time string according to the given parse position. - * + * * @param source A String whose beginning should be parsed. * @param pos the parse position * @return a java.util.Date object - * @see java.text.DateFormat#parseObject(String, ParsePosition) + * @see java.text.DateFormat#parseObject(String, ParsePosition) */ Object parseObject(String source, ParsePosition pos); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/DatePrinter.java similarity index 98% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/DatePrinter.java index 91f5e1a0ad9..0e4464d2ca4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/DatePrinter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/DatePrinter.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.text.FieldPosition; import java.util.Calendar; @@ -23,18 +23,18 @@ import java.util.TimeZone; /** - * DatePrinter is the "missing" interface for the format methods of + * DatePrinter is the "missing" interface for the format methods of * {@link java.text.DateFormat}. You can obtain an object implementing this * interface by using one of the FastDateFormat factory methods. *

    * Warning: Since binary compatible methods may be added to this interface in any * release, developers are not expected to implement this interface. *

    - * + * *

    * Copied and modified from Apache Commons Lang. *

    - * + * * @since Apache Commons Lang 3.2 */ public interface DatePrinter { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java new file mode 100644 index 00000000000..e7e1d567346 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java @@ -0,0 +1,633 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.core.time.internal.format; + +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + *

    FastDateFormat is a fast and thread-safe version of + * {@link java.text.SimpleDateFormat}.

    + * + *

    To obtain an instance of FastDateFormat, use one of the static factory methods: + * {@link #getInstance(String, TimeZone, Locale)}, {@link #getDateInstance(int, TimeZone, Locale)}, + * {@link #getTimeInstance(int, TimeZone, Locale)}, or {@link #getDateTimeInstance(int, int, TimeZone, Locale)} + *

    + * + *

    Since FastDateFormat is thread safe, you can use a static member instance:

    + * + * private static final FastDateFormat DATE_FORMATTER = FastDateFormat.getDateTimeInstance(FastDateFormat.LONG, FastDateFormat.SHORT); + * + * + *

    This class can be used as a direct replacement to + * {@code SimpleDateFormat} in most formatting and parsing situations. + * This class is especially useful in multi-threaded server environments. + * {@code SimpleDateFormat} is not thread-safe in any JDK version, + * nor will it be as Sun have closed the bug/RFE. + *

    + * + *

    All patterns are compatible with + * SimpleDateFormat (except time zones and some year patterns - see below).

    + * + *

    Since 3.2, FastDateFormat supports parsing as well as printing.

    + * + *

    Java 1.4 introduced a new pattern letter, {@code 'Z'}, to represent + * time zones in RFC822 format (eg. {@code +0800} or {@code -1100}). + * This pattern letter can be used here (on all JDK versions).

    + * + *

    In addition, the pattern {@code 'ZZ'} has been made to represent + * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}). + * This introduces a minor incompatibility with Java 1.4, but at a gain of + * useful functionality.

    + * + *

    Javadoc cites for the year pattern: For formatting, if the number of + * pattern letters is 2, the year is truncated to 2 digits; otherwise it is + * interpreted as a number. Starting with Java 1.7 a pattern of 'Y' or + * 'YYY' will be formatted as '2003', while it was '03' in former Java + * versions. FastDateFormat implements the behavior of Java 7.

    + * + *

    + * Copied and modified from Apache Commons Lang. + *

    + * + * @since Apache Commons Lang 2.0 + */ +public class FastDateFormat extends Format implements DateParser, DatePrinter { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + @SuppressWarnings("unused") + private static final long serialVersionUID = 2L; + + /** + * FULL locale dependent date or time style. + */ + public static final int FULL = DateFormat.FULL; + + /** + * LONG locale dependent date or time style. + */ + public static final int LONG = DateFormat.LONG; + + /** + * MEDIUM locale dependent date or time style. + */ + public static final int MEDIUM = DateFormat.MEDIUM; + + /** + * SHORT locale dependent date or time style. + */ + public static final int SHORT = DateFormat.SHORT; + + private static final FormatCache cache= new FormatCache() { + @Override + protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) { + return new FastDateFormat(pattern, timeZone, locale); + } + }; + + private final FastDatePrinter printer; + private final FastDateParser parser; + + //----------------------------------------------------------------------- + /** + *

    Gets a formatter instance using the default pattern in the + * default locale.

    + * + * @return a date/time formatter + */ + public static FastDateFormat getInstance() { + return cache.getInstance(); + } + + /** + *

    Gets a formatter instance using the specified pattern in the + * default locale.

    + * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + */ + public static FastDateFormat getInstance(final String pattern) { + return cache.getInstance(pattern, null, null); + } + + /** + *

    Gets a formatter instance using the specified pattern and + * time zone.

    + * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + */ + public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) { + return cache.getInstance(pattern, timeZone, null); + } + + /** + *

    Gets a formatter instance using the specified pattern and + * locale.

    + * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @param locale optional locale, overrides system locale + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + */ + public static FastDateFormat getInstance(final String pattern, final Locale locale) { + return cache.getInstance(pattern, null, locale); + } + + /** + *

    Gets a formatter instance using the specified pattern, time zone + * and locale.

    + * + * @param pattern {@link java.text.SimpleDateFormat} compatible + * pattern + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @param locale optional locale, overrides system locale + * @return a pattern based date/time formatter + * @throws IllegalArgumentException if pattern is invalid + * or {@code null} + */ + public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) { + return cache.getInstance(pattern, timeZone, locale); + } + + //----------------------------------------------------------------------- + /** + *

    Gets a date formatter instance using the specified style in the + * default time zone and locale.

    + * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateInstance(final int style) { + return cache.getDateInstance(style, null, null); + } + + /** + *

    Gets a date formatter instance using the specified style and + * locale in the default time zone.

    + * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @param locale optional locale, overrides system locale + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateInstance(final int style, final Locale locale) { + return cache.getDateInstance(style, null, locale); + } + + /** + *

    Gets a date formatter instance using the specified style and + * time zone in the default locale.

    + * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) { + return cache.getDateInstance(style, timeZone, null); + } + + /** + *

    Gets a date formatter instance using the specified style, time + * zone and locale.

    + * + * @param style date style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @param locale optional locale, overrides system locale + * @return a localized standard date formatter + * @throws IllegalArgumentException if the Locale has no date + * pattern defined + */ + public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) { + return cache.getDateInstance(style, timeZone, locale); + } + + //----------------------------------------------------------------------- + /** + *

    Gets a time formatter instance using the specified style in the + * default time zone and locale.

    + * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getTimeInstance(final int style) { + return cache.getTimeInstance(style, null, null); + } + + /** + *

    Gets a time formatter instance using the specified style and + * locale in the default time zone.

    + * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @param locale optional locale, overrides system locale + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getTimeInstance(final int style, final Locale locale) { + return cache.getTimeInstance(style, null, locale); + } + + /** + *

    Gets a time formatter instance using the specified style and + * time zone in the default locale.

    + * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted time + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) { + return cache.getTimeInstance(style, timeZone, null); + } + + /** + *

    Gets a time formatter instance using the specified style, time + * zone and locale.

    + * + * @param style time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted time + * @param locale optional locale, overrides system locale + * @return a localized standard time formatter + * @throws IllegalArgumentException if the Locale has no time + * pattern defined + */ + public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) { + return cache.getTimeInstance(style, timeZone, locale); + } + + //----------------------------------------------------------------------- + /** + *

    Gets a date/time formatter instance using the specified style + * in the default time zone and locale.

    + * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) { + return cache.getDateTimeInstance(dateStyle, timeStyle, null, null); + } + + /** + *

    Gets a date/time formatter instance using the specified style and + * locale in the default time zone.

    + * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @param locale optional locale, overrides system locale + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) { + return cache.getDateTimeInstance(dateStyle, timeStyle, null, locale); + } + + /** + *

    Gets a date/time formatter instance using the specified style and + * time zone in the default locale.

    + * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + * @since 2.1 + */ + public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) { + return getDateTimeInstance(dateStyle, timeStyle, timeZone, null); + } + /** + *

    Gets a date/time formatter instance using the specified style, + * time zone and locale.

    + * + * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT + * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT + * @param timeZone optional time zone, overrides time zone of + * formatted date + * @param locale optional locale, overrides system locale + * @return a localized standard date/time formatter + * @throws IllegalArgumentException if the Locale has no date/time + * pattern defined + */ + public static FastDateFormat getDateTimeInstance( + final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) { + return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale); + } + + /** + *

    Constructs a new FastDateFormat.

    + * + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale non-null locale to use + * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing. If centuryStart is null, defaults to now - 80 years + * @throws NullPointerException if pattern, timeZone, or locale is null. + * @since 3.0 + */ + public static FastDateFormat getDateTimeInstance(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) { + return new FastDateFormat(pattern, timeZone, locale, centuryStart); + } + + // Constructor + //----------------------------------------------------------------------- + /** + *

    Constructs a new FastDateFormat.

    + * + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale non-null locale to use + * @throws NullPointerException if pattern, timeZone, or locale is null. + */ + protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) { + this(pattern, timeZone, locale, null); + } + + // Constructor + //----------------------------------------------------------------------- + /** + *

    Constructs a new FastDateFormat.

    + * + * @param pattern {@link java.text.SimpleDateFormat} compatible pattern + * @param timeZone non-null time zone to use + * @param locale non-null locale to use + * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing. If centuryStart is null, defaults to now - 80 years + * @throws NullPointerException if pattern, timeZone, or locale is null. + */ + protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) { + printer= new FastDatePrinter(pattern, timeZone, locale); + parser= new FastDateParser(pattern, timeZone, locale, centuryStart); + } + + // Format methods + //----------------------------------------------------------------------- + /** + *

    Formats a {@code Date}, {@code Calendar} or + * {@code Long} (milliseconds) object.

    + * This method is an implementation of {@link Format#format(Object, StringBuilder, FieldPosition)} + * + * @param obj the object to format + * @param toAppendTo the buffer to append to + * @param pos the position - ignored + * @return the buffer passed in + */ + @Override + public StringBuilder format(final Object obj, final StringBuilder toAppendTo, final FieldPosition pos) { + return toAppendTo.append(printer.format(obj)); + } + + /** + *

    Formats a millisecond {@code long} value.

    + * + * @param millis the millisecond value to format + * @return the formatted string + * @since 2.1 + */ + @Override + public String format(final long millis) { + return printer.format(millis); + } + + /** + *

    Formats a {@code Date} object using a {@code GregorianCalendar}.

    + * + * @param date the date to format + * @return the formatted string + */ + @Override + public String format(final Date date) { + return printer.format(date); + } + + /** + *

    Formats a {@code Calendar} object.

    + * + * @param calendar the calendar to format + * @return the formatted string + */ + @Override + public String format(final Calendar calendar) { + return printer.format(calendar); + } + + /** + *

    Formats a millisecond {@code long} value into the + * supplied {@code StringBuffer}.

    + * + * @param millis the millisecond value to format + * @param buf the buffer to format into + * @return the specified string buffer + * @since 3.5 + */ + @Override + public B format(final long millis, final B buf) { + return printer.format(millis, buf); + } + + /** + *

    Formats a {@code Date} object into the + * supplied {@code StringBuffer} using a {@code GregorianCalendar}.

    + * + * @param date the date to format + * @param buf the buffer to format into + * @return the specified string buffer + * @since 3.5 + */ + @Override + public B format(final Date date, final B buf) { + return printer.format(date, buf); + } + + /** + *

    Formats a {@code Calendar} object into the + * supplied {@code StringBuffer}.

    + * + * @param calendar the calendar to format + * @param buf the buffer to format into + * @return the specified string buffer + * @since 3.5 + */ + @Override + public B format(final Calendar calendar, final B buf) { + return printer.format(calendar, buf); + } + + // Parsing + //----------------------------------------------------------------------- + + + /* (non-Javadoc) + * @see DateParser#parse(java.lang.String) + */ + @Override + public Date parse(final String source) throws ParseException { + return parser.parse(source); + } + + /* (non-Javadoc) + * @see DateParser#parse(java.lang.String, java.text.ParsePosition) + */ + @Override + public Date parse(final String source, final ParsePosition pos) { + return parser.parse(source, pos); + } + + /* + * (non-Javadoc) + * @see org.apache.commons.lang3.time.DateParser#parse(java.lang.String, java.text.ParsePosition, java.util.Calendar) + */ + @Override + public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) { + return parser.parse(source, pos, calendar); + } + + /* (non-Javadoc) + * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) + */ + @Override + public Object parseObject(final String source, final ParsePosition pos) { + return parser.parseObject(source, pos); + } + + // Accessors + //----------------------------------------------------------------------- + /** + *

    Gets the pattern used by this formatter.

    + * + * @return the pattern, {@link java.text.SimpleDateFormat} compatible + */ + @Override + public String getPattern() { + return printer.getPattern(); + } + + /** + *

    Gets the time zone used by this formatter.

    + * + *

    This zone is always used for {@code Date} formatting.

    + * + * @return the time zone + */ + @Override + public TimeZone getTimeZone() { + return printer.getTimeZone(); + } + + /** + *

    Gets the locale used by this formatter.

    + * + * @return the locale + */ + @Override + public Locale getLocale() { + return printer.getLocale(); + } + + /** + *

    Gets an estimate for the maximum string length that the + * formatter will produce.

    + * + *

    The actual formatted length will almost always be less than or + * equal to this amount.

    + * + * @return the maximum formatted length + */ + public int getMaxLengthEstimate() { + return printer.getMaxLengthEstimate(); + } + + // Basics + //----------------------------------------------------------------------- + /** + *

    Compares two objects for equality.

    + * + * @param obj the object to compare to + * @return {@code true} if equal + */ + @Override + public boolean equals(final Object obj) { + if (obj instanceof FastDateFormat == false) { + return false; + } + final FastDateFormat other = (FastDateFormat) obj; + // no need to check parser, as it has same invariants as printer + return printer.equals(other.printer); + } + + /** + *

    Returns a hash code compatible with equals.

    + * + * @return a hash code compatible with equals + */ + @Override + public int hashCode() { + return printer.hashCode(); + } + + /** + *

    Gets a debugging string version of this formatter.

    + * + * @return a debugging string + */ + @Override + public String toString() { + return "FastDateFormat[" + printer.getPattern() + "," + printer.getLocale() + "," + printer.getTimeZone().getID() + "]"; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser.java similarity index 97% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser.java index 4ba56685728..344e51c66f7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateParser.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.io.IOException; import java.io.ObjectInputStream; @@ -43,14 +43,14 @@ *

    FastDateParser is a fast and thread-safe version of * {@link java.text.SimpleDateFormat}.

    * - *

    To obtain a proxy to a FastDateParser, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} + *

    To obtain a proxy to a FastDateParser, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} * or another variation of the factory methods of {@link FastDateFormat}.

    - * + * *

    Since FastDateParser is thread safe, you can use a static member instance:

    * * private static final DateParser DATE_PARSER = FastDateFormat.getInstance("yyyy-MM-dd"); * - * + * *

    This class can be used as a direct replacement for * SimpleDateFormat in most parsing situations. * This class is especially useful in multi-threaded server environments. @@ -66,11 +66,11 @@ * *

    Timing tests indicate this class is as about as fast as SimpleDateFormat * in single thread applications and about 25% faster in multi-thread applications.

    - * + * *

    * Copied and modified from Apache Commons Lang. *

    - * + * * @since Apache Commons Lang 3.2 * @see FastDatePrinter */ @@ -98,17 +98,12 @@ public class FastDateParser implements DateParser, Serializable { // comparator used to sort regex alternatives // alternatives should be ordered longer first, and shorter last. ('february' before 'feb') // all entries must be lowercase by locale. - private static final Comparator LONGER_FIRST_LOWERCASE = new Comparator() { - @Override - public int compare(final String left, final String right) { - return right.compareTo(left); - } - }; + private static final Comparator LONGER_FIRST_LOWERCASE = (left, right) -> right.compareTo(left); /** *

    Constructs a new FastDateParser.

    - * - * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the + * + * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the * factory methods of {@link FastDateFormat} to get a cached FastDateParser instance. * * @param pattern non-null {@link java.text.SimpleDateFormat} compatible @@ -138,7 +133,7 @@ protected FastDateParser(final String pattern, final TimeZone timeZone, final Lo final Calendar definingCalendar = Calendar.getInstance(timeZone, locale); - int centuryStartYear; + final int centuryStartYear; if(centuryStart!=null) { definingCalendar.setTime(centuryStart); centuryStartYear= definingCalendar.get(Calendar.YEAR); @@ -385,8 +380,8 @@ public Object parseObject(final String source, final ParsePosition pos) { /** * This implementation updates the ParsePosition if the parse succeeds. - * However, it sets the error index to the position before the failed field unlike - * the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} which sets + * However, it sets the error index to the position before the failed field unlike + * the method {@link java.text.SimpleDateFormat#parse(String, ParsePosition)} which sets * the error index to after the failed field. *

    * To determine if the parse has succeeded, the caller must check if the current parse position @@ -409,7 +404,7 @@ public Date parse(final String source, final ParsePosition pos) { * Upon success, the ParsePosition index is updated to indicate how much of the source text was consumed. * Not all source text needs to be consumed. Upon parse failure, ParsePosition error index is updated to * the offset of the source text which does not match the supplied format. - * + * * @param source The text to parse. * @param pos On input, the position in the source to start parsing, on output, updated position. * @param calendar The calendar into which to set parsed fields. @@ -570,10 +565,10 @@ private Strategy getStrategy(final char f, final int width, final Calendar defin return getLocaleSpecificStrategy(Calendar.ERA, definingCalendar); case 'H': // Hour in day (0-23) return HOUR_OF_DAY_STRATEGY; - case 'K': // Hour in am/pm (0-11) + case 'K': // Hour in am/pm (0-11) return HOUR_STRATEGY; case 'M': - return width>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY; + return width >= 3 ? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) : NUMBER_MONTH_STRATEGY; case 'S': return MILLISECOND_STRATEGY; case 'W': @@ -596,7 +591,7 @@ private Strategy getStrategy(final char f, final int width, final Calendar defin return WEEK_OF_YEAR_STRATEGY; case 'y': case 'Y': - return width>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY; + return width > 2 ? LITERAL_YEAR_STRATEGY : ABBREVIATED_YEAR_STRATEGY; case 'X': return ISO8601TimeZoneStrategy.getStrategy(width); case 'Z': @@ -636,7 +631,7 @@ private Strategy getLocaleSpecificStrategy(final int field, final Calendar defin final ConcurrentMap cache = getCache(field); Strategy strategy = cache.get(locale); if (strategy == null) { - strategy = field == Calendar.ZONE_OFFSET + strategy = field == Calendar.ZONE_OFFSET ? new TimeZoneStrategy(locale) : new CaseInsensitiveTextStrategy(field, definingCalendar, locale); final Strategy inCache = cache.putIfAbsent(locale, strategy); @@ -705,7 +700,7 @@ private static class CaseInsensitiveTextStrategy extends PatternStrategy { CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) { this.field = field; this.locale = locale; - + final StringBuilder regex = new StringBuilder(); regex.append("((?iu)"); lKeyValues = appendDisplayNames(definingCalendar, locale, field, regex); @@ -905,9 +900,9 @@ void setCalendar(final FastDateParser parser, final Calendar cal, final String v } } } - + private static class ISO8601TimeZoneStrategy extends PatternStrategy { - // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm + // Z, +hh, -hh, +hhmm, -hhmm, +hh:mm or -hh:mm /** * Construct a Strategy that parses a TimeZone @@ -916,7 +911,7 @@ private static class ISO8601TimeZoneStrategy extends PatternStrategy { ISO8601TimeZoneStrategy(final String pattern) { createPattern(pattern); } - + /** * {@inheritDoc} */ @@ -928,14 +923,14 @@ void setCalendar(final FastDateParser parser, final Calendar cal, final String v cal.setTimeZone(TimeZone.getTimeZone("GMT" + value)); } } - + private static final Strategy ISO_8601_1_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}))"); private static final Strategy ISO_8601_2_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}\\d{2}))"); private static final Strategy ISO_8601_3_STRATEGY = new ISO8601TimeZoneStrategy("(Z|(?:[+-]\\d{2}(?::)\\d{2}))"); /** * Factory method for ISO8601TimeZoneStrategies. - * + * * @param tokenLen a token indicating the length of the TimeZone String to be formatted. * @return a ISO8601TimeZoneStrategy that can format TimeZone String of length {@code tokenLen}. If no such * strategy exists, an IllegalArgumentException will be thrown. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDatePrinter.java similarity index 98% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDatePrinter.java index 4f33b6bd118..8c5b67f544a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDatePrinter.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDatePrinter.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.io.IOException; import java.io.ObjectInputStream; @@ -37,14 +37,14 @@ *

    FastDatePrinter is a fast and thread-safe version of * {@link java.text.SimpleDateFormat}.

    * - *

    To obtain a FastDatePrinter, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} + *

    To obtain a FastDatePrinter, use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} * or another variation of the factory methods of {@link FastDateFormat}.

    - * + * *

    Since FastDatePrinter is thread safe, you can use a static member instance:

    * * private static final DatePrinter DATE_PRINTER = FastDateFormat.getInstance("yyyy-MM-dd"); * - * + * *

    This class can be used as a direct replacement to * {@code SimpleDateFormat} in most formatting situations. * This class is especially useful in multi-threaded server environments. @@ -63,7 +63,7 @@ * ISO 8601 extended format time zones (eg. {@code +08:00} or {@code -11:00}). * This introduces a minor incompatibility with Java 1.4, but at a gain of * useful functionality.

    - * + * *

    Starting with JDK7, ISO 8601 support was added using the pattern {@code 'X'}. * To maintain compatibility, {@code 'ZZ'} will continue to be supported, but using * one of the {@code 'X'} formats is recommended. @@ -73,11 +73,11 @@ * interpreted as a number. Starting with Java 1.7 a pattern of 'Y' or * 'YYY' will be formatted as '2003', while it was '03' in former Java * versions. FastDatePrinter implements the behavior of Java 7.

    - * + * *

    * Copied and modified from Apache Commons Lang. *

    - * + * * @since Apache Commons Lang 3.2 * @see FastDateParser */ @@ -143,7 +143,7 @@ public class FastDatePrinter implements DatePrinter, Serializable { //----------------------------------------------------------------------- /** *

    Constructs a new FastDatePrinter.

    - * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the + * Use {@link FastDateFormat#getInstance(String, TimeZone, Locale)} or another variation of the * factory methods of {@link FastDateFormat} to get a cached FastDatePrinter instance. * * @param pattern {@link java.text.SimpleDateFormat} compatible pattern @@ -164,7 +164,7 @@ protected FastDatePrinter(final String pattern, final TimeZone timeZone, final L */ private void init() { final List rulesList = parsePattern(); - mRules = rulesList.toArray(new Rule[rulesList.size()]); + mRules = rulesList.toArray(Rule.EMPTY_ARRAY); int len = 0; for (int i=mRules.length; --i >= 0; ) { @@ -280,9 +280,9 @@ protected List parsePattern() { case 'K': // hour in am/pm (0..11) rule = selectNumberRule(Calendar.HOUR, tokenLen); break; - case 'X': // ISO 8601 + case 'X': // ISO 8601 rule = Iso8601_Rule.getRule(tokenLen); - break; + break; case 'z': // time zone (text) if (tokenLen >= 4) { rule = new TimeZoneNameRule(mTimeZone, mLocale, TimeZone.LONG); @@ -518,21 +518,6 @@ public B format(Calendar calendar, final B buf) { return applyRules(calendar, buf); } - /** - * Performs the formatting by applying the rules to the - * specified calendar. - * - * @param calendar the calendar to format - * @param buf the buffer to format into - * @return the specified string buffer - * - * @deprecated use {@link #format(Calendar)} or {@link #format(Calendar, Appendable)} - */ - @Deprecated - protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) { - return (StringBuffer) applyRules(calendar, (Appendable)buf); - } - /** *

    Performs the formatting by applying the rules to the * specified calendar.

    @@ -607,7 +592,7 @@ public boolean equals(final Object obj) { } final FastDatePrinter other = (FastDatePrinter) obj; return mPattern.equals(other.mPattern) - && mTimeZone.equals(other.mTimeZone) + && mTimeZone.equals(other.mTimeZone) && mLocale.equals(other.mLocale); } @@ -737,6 +722,9 @@ private static void appendFullDigits(final Appendable buffer, int value, int min *

    Inner class defining a rule.

    */ private interface Rule { + + Rule[] EMPTY_ARRAY = {}; + /** * Returns the estimated length of the result. * @@ -1322,7 +1310,7 @@ private static class TimeZoneNameRule implements Rule { TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { mLocale = locale; mStyle = style; - + mStandard = getTimeZoneDisplay(timeZone, false, style, locale); mDaylight = getTimeZoneDisplay(timeZone, true, style, locale); } @@ -1359,7 +1347,7 @@ public void appendTo(final Appendable buffer, final Calendar calendar) throws IO private static class TimeZoneNumberRule implements Rule { static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true); static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false); - + final boolean mColon; /** @@ -1384,7 +1372,7 @@ public int estimateLength() { */ @Override public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException { - + int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); if (offset < 0) { @@ -1411,9 +1399,9 @@ public void appendTo(final Appendable buffer, final Calendar calendar) throws IO * or {@code +/-HH:MM}.

    */ private static class Iso8601_Rule implements Rule { - + // Sign TwoDigitHours or Z - static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3); + static final Iso8601_Rule ISO8601_HOURS = new Iso8601_Rule(3); // Sign TwoDigitHours Minutes or Z static final Iso8601_Rule ISO8601_HOURS_MINUTES = new Iso8601_Rule(5); // Sign TwoDigitHours : Minutes or Z @@ -1435,10 +1423,10 @@ static Iso8601_Rule getRule(final int tokenLen) { case 3: return Iso8601_Rule.ISO8601_HOURS_COLON_MINUTES; default: - throw new IllegalArgumentException("invalid number of X"); + throw new IllegalArgumentException("invalid number of X"); } - } - + } + final int length; /** @@ -1468,7 +1456,7 @@ public void appendTo(final Appendable buffer, final Calendar calendar) throws IO buffer.append("Z"); return; } - + if (offset < 0) { buffer.append('-'); offset = -offset; @@ -1482,7 +1470,7 @@ public void appendTo(final Appendable buffer, final Calendar calendar) throws IO if (length<5) { return; } - + if (length==6) { buffer.append(':'); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormat.java new file mode 100644 index 00000000000..ba7e5c9d74d --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormat.java @@ -0,0 +1,737 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.time.internal.format; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Objects; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.core.time.Instant; + +/** + * Custom time formatter that trades flexibility for performance. This formatter only supports the date patterns defined + * in {@link FixedFormat}. For any other date patterns use {@link FastDateFormat}. + *

    + * Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and + * /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java + *

    + */ +public class FixedDateFormat { + + /** + * Enumeration over the supported date/time format patterns. + *

    + * Package protected for unit tests. + *

    + */ + public enum FixedFormat { + /** + * ABSOLUTE time format: {@code "HH:mm:ss,SSS"}. + */ + ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3, null), + /** + * ABSOLUTE time format with microsecond precision: {@code "HH:mm:ss,nnnnnn"}. + */ + ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6, null), + /** + * ABSOLUTE time format with nanosecond precision: {@code "HH:mm:ss,nnnnnnnnn"}. + */ + ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9, null), + + /** + * ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}. + */ + ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3, null), + + /** + * COMPACT time format: {@code "yyyyMMddHHmmssSSS"}. + */ + COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3, null), + + /** + * DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}. + */ + DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3, null), + + /** + * DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}. + */ + DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3, null), + + /** + * DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}. + */ + DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3, null), + /** + * DEFAULT time format with microsecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnn"}. + */ + DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6, null), + /** + * DEFAULT time format with nanosecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"}. + */ + DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9, null), + + /** + * DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}. + */ + DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3, null), + + /** + * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}. + */ + ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3, null), + + /** + * ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss.SSS"}. + */ + ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3, null), + + /** + * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}. + */ + ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, null), + + // TODO Do we even want a format without seconds? +// /** +// * ISO8601_OFFSET_DATE_TIME time format: {@code "yyyy-MM-dd'T'HH:mmXXX"}. +// */ +// // Would need work in org.apache.logging.log4j.core.util.datetime.FixedDateFormat.writeTime(int, char[], int) +// ISO8601_OFFSET_DATE_TIME("yyyy-MM-dd'T'HH:mmXXX", "yyyy-MM-dd'T'", 2, ':', 1, ' ', 0, 0, FixedTimeZoneFormat.XXX), + + /** + * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSX"} with a time zone like {@code -07}. + */ + ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, + FixedTimeZoneFormat.HH), + + /** + * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXX"} with a time zone like {@code -0700}. + */ + ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, + FixedTimeZoneFormat.HHMM), + + /** + * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} with a time zone like {@code -07:00}. + */ + ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, + FixedTimeZoneFormat.HHCMM), + + /** + * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss.SSS"}. + */ + ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3, null), + + /** + * ISO8601 time format with support for microsecond precision: {@code "yyyy-MM-dd'T'HH:mm:ss.nnnnnn"}. + */ + ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 6, null), + + /** + * American date/time format with 2-digit year: {@code "dd/MM/yy HH:mm:ss.SSS"}. + */ + US_MONTH_DAY_YEAR2_TIME("dd/MM/yy HH:mm:ss.SSS", "dd/MM/yy ", 0, ':', 1, '.', 1, 3, null), + + /** + * American date/time format with 4-digit year: {@code "dd/MM/yyyy HH:mm:ss.SSS"}. + */ + US_MONTH_DAY_YEAR4_TIME("dd/MM/yyyy HH:mm:ss.SSS", "dd/MM/yyyy ", 0, ':', 1, '.', 1, 3, null); + + private static final String DEFAULT_SECOND_FRACTION_PATTERN = "SSS"; + private static final int MILLI_FRACTION_DIGITS = DEFAULT_SECOND_FRACTION_PATTERN.length(); + private static final char SECOND_FRACTION_PATTERN = 'n'; + + private final String pattern; + private final String datePattern; + private final int escapeCount; + private final char timeSeparatorChar; + private final int timeSeparatorLength; + private final char millisSeparatorChar; + private final int millisSeparatorLength; + private final int secondFractionDigits; + private final FixedTimeZoneFormat fixedTimeZoneFormat; + + FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator, + final int timeSepLength, final char millisSeparator, final int millisSepLength, + final int secondFractionDigits, final FixedTimeZoneFormat fixedTimeZoneFormat) { + this.timeSeparatorChar = timeSeparator; + this.timeSeparatorLength = timeSepLength; + this.millisSeparatorChar = millisSeparator; + this.millisSeparatorLength = millisSepLength; + this.pattern = Objects.requireNonNull(pattern); + this.datePattern = datePattern; // may be null + this.escapeCount = escapeCount; + this.secondFractionDigits = secondFractionDigits; + this.fixedTimeZoneFormat = fixedTimeZoneFormat; + } + + /** + * Returns the full pattern. + * + * @return the full pattern + */ + public String getPattern() { + return pattern; + } + + /** + * Returns the date part of the pattern. + * + * @return the date part of the pattern + */ + public String getDatePattern() { + return datePattern; + } + + /** + * Returns the FixedFormat with the name or pattern matching the specified string or {@code null} if not found. + * + * @param nameOrPattern the name or pattern to find a FixedFormat for + * @return the FixedFormat with the name or pattern matching the specified string + */ + public static FixedFormat lookup(final String nameOrPattern) { + for (final FixedFormat type : FixedFormat.values()) { + if (type.name().equals(nameOrPattern) || type.getPattern().equals(nameOrPattern)) { + return type; + } + } + return null; + } + + static FixedFormat lookupIgnoringNanos(final String pattern) { + final int[] nanoRange = nanoRange(pattern); + final int nanoStart = nanoRange[0]; + final int nanoEnd = nanoRange[1]; + if (nanoStart > 0) { + final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN + + pattern.substring(nanoEnd, pattern.length()); + for (final FixedFormat type : FixedFormat.values()) { + if (type.getPattern().equals(subPattern)) { + return type; + } + } + } + return null; + } + + private final static int[] EMPTY_RANGE = { -1, -1 }; + + /** + * @return int[0] start index inclusive; int[1] end index exclusive + */ + private static int[] nanoRange(final String pattern) { + final int indexStart = pattern.indexOf(SECOND_FRACTION_PATTERN); + int indexEnd = -1; + if (indexStart >= 0) { + indexEnd = pattern.indexOf('Z', indexStart); + indexEnd = indexEnd < 0 ? pattern.indexOf('X', indexStart) : indexEnd; + indexEnd = indexEnd < 0 ? pattern.length() : indexEnd; + for (int i = indexStart + 1; i < indexEnd; i++) { + if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) { + return EMPTY_RANGE; + } + } + } + return new int [] {indexStart, indexEnd}; + } + + /** + * Returns the optional time zone format. + * @return the optional time zone format, may be null. + */ + public FixedTimeZoneFormat getTimeZoneFormat() { + return fixedTimeZoneFormat; + } + + /** + * Returns the length of the resulting formatted date and time strings. + * + * @return the length of the resulting formatted date and time strings + */ + public int getLength() { + return pattern.length() - escapeCount; + } + + /** + * Returns the length of the date part of the resulting formatted string. + * + * @return the length of the date part of the resulting formatted string + */ + public int getDatePatternLength() { + return getDatePattern() == null ? 0 : getDatePattern().length() - escapeCount; + } + + /** + * Returns the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} if the + * pattern does not have a date part. + * + * @return the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} + */ + public FastDateFormat getFastDateFormat() { + return getFastDateFormat(null); + } + + /** + * Returns the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} if the + * pattern does not have a date part. + * + * @param tz the time zone to use + * @return the {@code FastDateFormat} object for formatting the date part of the pattern or {@code null} + */ + public FastDateFormat getFastDateFormat(final TimeZone tz) { + return getDatePattern() == null ? null : FastDateFormat.getInstance(getDatePattern(), tz); + } + + /** + * Returns the number of digits specifying the fraction of the second to show + * @return 3 for millisecond precision, 6 for microsecond precision or 9 for nanosecond precision + */ + public int getSecondFractionDigits() { + return secondFractionDigits; + } + } + + private static final char NONE = (char) 0; + + /** + * Fixed time zone formats. The enum names are symbols from Java's DateTimeFormatter. + * + * @see DateTimeFormatter + */ + public enum FixedTimeZoneFormat { + + /** + * Offset like {@code -07} + */ + HH(NONE, false, 3), + + /** + * Offset like {@code -0700}. + * Same as Z. + */ + HHMM(NONE, true, 5), + + /** + * Offset like {@code -07:00} + */ + HHCMM(':', true, 6); + + FixedTimeZoneFormat() { + this(NONE, true, 4); + } + + FixedTimeZoneFormat(final char timeSeparatorChar, final boolean minutes, final int length) { + this.timeSeparatorChar = timeSeparatorChar; + this.timeSeparatorCharLen = timeSeparatorChar != NONE ? 1 : 0; + this.useMinutes = minutes; + this.length = length; + } + + private final char timeSeparatorChar; + private final int timeSeparatorCharLen; + private final boolean useMinutes; + // The length includes 1 for the leading sign + private final int length; + + public int getLength() { + return length; + } + + // Profiling showed this method is important to log4j performance. Modify with care! + // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux) + private int write(final int offset, final char[] buffer, int pos) { + // This method duplicates part of writeTime() + + buffer[pos++] = offset < 0 ? '-' : '+'; + final int absOffset = Math.abs(offset); + final int hours = absOffset / 3600000; + int ms = absOffset - (3600000 * hours); + + // Hour + int temp = hours / 10; + buffer[pos++] = ((char) (temp + '0')); + + // Do subtract to get remainder instead of doing % 10 + buffer[pos++] = ((char) (hours - 10 * temp + '0')); + + // Minute + if (useMinutes) { + buffer[pos] = timeSeparatorChar; + pos += timeSeparatorCharLen; + final int minutes = ms / 60000; + ms -= 60000 * minutes; + + temp = minutes / 10; + buffer[pos++] = ((char) (temp + '0')); + + // Do subtract to get remainder instead of doing % 10 + buffer[pos++] = ((char) (minutes - 10 * temp + '0')); + } + return pos; + } + + } + + private final FixedFormat fixedFormat; + private final TimeZone timeZone; + private final int length; + private final int secondFractionDigits; + private final FastDateFormat fastDateFormat; // may be null + private final char timeSeparatorChar; + private final char millisSeparatorChar; + private final int timeSeparatorLength; + private final int millisSeparatorLength; + private final FixedTimeZoneFormat fixedTimeZoneFormat; + + + private volatile long midnightToday; + private volatile long midnightTomorrow; + private final int[] dstOffsets = new int[25]; + + // cachedDate does not need to be volatile because + // there is a write to a volatile field *after* cachedDate is modified, + // and there is a read from a volatile field *before* cachedDate is read. + // The Java memory model guarantees that because of the above, + // changes to cachedDate in one thread are visible to other threads. + // See http://g.oswego.edu/dl/jmm/cookbook.html + private char[] cachedDate; // may be null + private int dateLength; + + /** + * Constructs a FixedDateFormat for the specified fixed format. + *

    + * Package protected for unit tests. + *

    + * + * @param fixedFormat the fixed format + * @param tz time zone + */ + FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz) { + this(fixedFormat, tz, fixedFormat.getSecondFractionDigits()); + } + + /** + * Constructs a FixedDateFormat for the specified fixed format. + *

    + * Package protected for unit tests. + *

    + * + * @param fixedFormat the fixed format + * @param tz time zone + * @param secondFractionDigits the number of digits specifying the fraction of the second to show + */ + FixedDateFormat(final FixedFormat fixedFormat, final TimeZone tz, final int secondFractionDigits) { + this.fixedFormat = Objects.requireNonNull(fixedFormat); + this.timeZone = Objects.requireNonNull(tz); + this.timeSeparatorChar = fixedFormat.timeSeparatorChar; + this.timeSeparatorLength = fixedFormat.timeSeparatorLength; + this.millisSeparatorChar = fixedFormat.millisSeparatorChar; + this.millisSeparatorLength = fixedFormat.millisSeparatorLength; + this.fixedTimeZoneFormat = fixedFormat.fixedTimeZoneFormat; + this.length = fixedFormat.getLength(); + this.secondFractionDigits = Math.max(1, Math.min(9, secondFractionDigits)); + this.fastDateFormat = fixedFormat.getFastDateFormat(tz); + } + + public static FixedDateFormat createIfSupported(final String... options) { + if (options == null || options.length == 0 || options[0] == null) { + return new FixedDateFormat(FixedFormat.DEFAULT, TimeZone.getDefault()); + } + final TimeZone tz; + if (options.length > 1) { + if (options[1] != null) { + String zoneId = options[1]; + if (zoneId.startsWith("-") || zoneId.startsWith("+")) { + zoneId = "GMT" + zoneId; + } + tz = TimeZone.getTimeZone(zoneId); + } else { + tz = TimeZone.getDefault(); + } + } else { + tz = TimeZone.getDefault(); + } + + final String option0 = options[0]; + final FixedFormat withoutNanos = FixedFormat.lookupIgnoringNanos(option0); + if (withoutNanos != null) { + final int[] nanoRange = FixedFormat.nanoRange(option0); + final int nanoStart = nanoRange[0]; + final int nanoEnd = nanoRange[1]; + final int secondFractionDigits = nanoEnd - nanoStart; + return new FixedDateFormat(withoutNanos, tz, secondFractionDigits); + } + final FixedFormat type = FixedFormat.lookup(option0); + return type == null ? null : new FixedDateFormat(type, tz); + } + + /** + * Returns a new {@code FixedDateFormat} object for the specified {@code FixedFormat} and a {@code TimeZone.getDefault()} TimeZone. + * + * @param format the format to use + * @return a new {@code FixedDateFormat} object + */ + public static FixedDateFormat create(final FixedFormat format) { + return new FixedDateFormat(format, TimeZone.getDefault()); + } + + /** + * Returns a new {@code FixedDateFormat} object for the specified {@code FixedFormat} and TimeZone. + * + * @param format the format to use + * @param tz the time zone to use + * @return a new {@code FixedDateFormat} object + */ + public static FixedDateFormat create(final FixedFormat format, final TimeZone tz) { + return new FixedDateFormat(format, tz != null ? tz : TimeZone.getDefault()); + } + + /** + * Returns the full pattern of the selected fixed format. + * + * @return the full date-time pattern + */ + public String getFormat() { + return fixedFormat.getPattern(); + } + + /** + * Returns the time zone. + * + * @return the time zone + */ + + public TimeZone getTimeZone() { + return timeZone; + } + + /** + *

    Returns the number of milliseconds since midnight in the time zone that this {@code FixedDateFormat} + * was constructed with for the specified currentTime.

    + *

    As a side effect, this method updates the cached formatted date and the cached date demarcation timestamps + * when the specified current time is outside the previously set demarcation timestamps for the start or end + * of the current day.

    + * @param currentTime the current time in millis since the epoch + * @return the number of milliseconds since midnight for the specified time + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 30 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) + public long millisSinceMidnight(final long currentTime) { + if (currentTime >= midnightTomorrow || currentTime < midnightToday) { + updateMidnightMillis(currentTime); + } + return currentTime - midnightToday; + } + + private void updateMidnightMillis(final long now) { + if (now >= midnightTomorrow || now < midnightToday) { + synchronized (this) { + updateCachedDate(now); + midnightToday = calcMidnightMillis(now, 0); + midnightTomorrow = calcMidnightMillis(now, 1); + + updateDaylightSavingTime(); + } + } + } + + private long calcMidnightMillis(final long time, final int addDays) { + final Calendar cal = Calendar.getInstance(timeZone); + cal.setTimeInMillis(time); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.add(Calendar.DATE, addDays); + return cal.getTimeInMillis(); + } + + private void updateDaylightSavingTime() { + Arrays.fill(dstOffsets, 0); + final int ONE_HOUR = (int) TimeUnit.HOURS.toMillis(1); + if (timeZone.getOffset(midnightToday) != timeZone.getOffset(midnightToday + 23 * ONE_HOUR)) { + for (int i = 0; i < dstOffsets.length; i++) { + final long time = midnightToday + i * ONE_HOUR; + dstOffsets[i] = timeZone.getOffset(time) - timeZone.getRawOffset(); + } + if (dstOffsets[0] > dstOffsets[23]) { // clock is moved backwards. + // we obtain midnightTonight with Calendar.getInstance(TimeZone), so it already includes raw offset + for (int i = dstOffsets.length - 1; i >= 0; i--) { + dstOffsets[i] -= dstOffsets[0]; // + } + } + } + } + + private void updateCachedDate(final long now) { + if (fastDateFormat != null) { + final StringBuilder result = fastDateFormat.format(now, new StringBuilder()); + cachedDate = result.toString().toCharArray(); + dateLength = result.length(); + } + } + + public String formatInstant(final Instant instant) { + final char[] result = new char[length << 1]; // double size for locales with lengthy DateFormatSymbols + final int written = formatInstant(instant, result, 0); + return new String(result, 0, written); + } + + public int formatInstant(final Instant instant, final char[] buffer, final int startPos) { + final long epochMillisecond = instant.getEpochMillisecond(); + int result = format(epochMillisecond, buffer, startPos); + result -= digitsLessThanThree(); + final int pos = formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result); + return writeTimeZone(epochMillisecond, buffer, pos); + } + + private int digitsLessThanThree() { // in case user specified only 1 or 2 'n' format characters + return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits); + } + + // Profiling showed this method is important to log4j performance. Modify with care! + // 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) + public String format(final long epochMillis) { + final char[] result = new char[length << 1]; // double size for locales with lengthy DateFormatSymbols + final int written = format(epochMillis, result, 0); + return new String(result, 0, written); + } + + // Profiling showed this method is important to log4j performance. Modify with care! + // 31 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) + public int format(final long epochMillis, final char[] buffer, final int startPos) { + // Calculate values by getting the ms values first and do then + // calculate the hour minute and second values divisions. + + // Get daytime in ms: this does fit into an int + // int ms = (int) (time % 86400000); + final int ms = (int) (millisSinceMidnight(epochMillis)); + writeDate(buffer, startPos); + final int pos = writeTime(ms, buffer, startPos + dateLength); + return pos - startPos; + } + + // Profiling showed this method is important to log4j performance. Modify with care! + // 22 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes) + private void writeDate(final char[] buffer, final int startPos) { + if (cachedDate != null) { + System.arraycopy(cachedDate, 0, buffer, startPos, dateLength); + } + } + + // Profiling showed this method is important to log4j performance. Modify with care! + // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux) + private int writeTime(int ms, final char[] buffer, int pos) { + final int hourOfDay = ms / 3600000; + final int hours = hourOfDay + daylightSavingTime(hourOfDay) / 3600000; + ms -= 3600000 * hourOfDay; + + final int minutes = ms / 60000; + ms -= 60000 * minutes; + + final int seconds = ms / 1000; + ms -= 1000 * seconds; + + // Hour + int temp = hours / 10; + buffer[pos++] = ((char) (temp + '0')); + + // Do subtract to get remainder instead of doing % 10 + buffer[pos++] = ((char) (hours - 10 * temp + '0')); + buffer[pos] = timeSeparatorChar; + pos += timeSeparatorLength; + + // Minute + temp = minutes / 10; + buffer[pos++] = ((char) (temp + '0')); + + // Do subtract to get remainder instead of doing % 10 + buffer[pos++] = ((char) (minutes - 10 * temp + '0')); + buffer[pos] = timeSeparatorChar; + pos += timeSeparatorLength; + + // Second + temp = seconds / 10; + buffer[pos++] = ((char) (temp + '0')); + buffer[pos++] = ((char) (seconds - 10 * temp + '0')); + buffer[pos] = millisSeparatorChar; + pos += millisSeparatorLength; + + // Millisecond + temp = ms / 100; + buffer[pos++] = ((char) (temp + '0')); + + ms -= 100 * temp; + temp = ms / 10; + buffer[pos++] = ((char) (temp + '0')); + + ms -= 10 * temp; + buffer[pos++] = ((char) (ms + '0')); + return pos; + } + + private int writeTimeZone(final long epochMillis, final char[] buffer, int pos) { + if (fixedTimeZoneFormat != null) { + pos = fixedTimeZoneFormat.write(timeZone.getOffset(epochMillis), buffer, pos); + } + return pos; + } + + static final int[] TABLE = { + 100000, // 0 + 10000, // 1 + 1000, // 2 + 100, // 3 + 10, // 4 + 1, // 5 + }; + + private int formatNanoOfMillisecond(final int nanoOfMillisecond, final char[] buffer, int pos) { + int temp; + int remain = nanoOfMillisecond; + for (int i = 0; i < secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS; i++) { + final int divisor = TABLE[i]; + temp = remain / divisor; + buffer[pos++] = ((char) (temp + '0')); + remain -= divisor * temp; // equivalent of remain % 10 + } + return pos; + } + + private int daylightSavingTime(final int hourOfDay) { + return hourOfDay > 23 ? dstOffsets[23] : dstOffsets[hourOfDay]; + } + + /** + * Returns {@code true} if the old and new date values will result in the same formatted output, {@code false} + * if results may differ. + */ + public boolean isEquivalent(long oldEpochSecond, int oldNanoOfSecond, long epochSecond, int nanoOfSecond) { + if (oldEpochSecond == epochSecond) { + if (secondFractionDigits <= 3) { + // Convert nanos to milliseconds for comparison if the format only requires milliseconds. + return (oldNanoOfSecond / 1000_000L) == (nanoOfSecond / 1000_000L); + } + return oldNanoOfSecond == nanoOfSecond; + } + return false; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/Format.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/Format.java similarity index 96% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/Format.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/Format.java index a4a2f6951ea..f87e2fdc4e2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/Format.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/Format.java @@ -14,7 +14,7 @@ * See the license for the specific language governing permissions and * limitations under the license. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.text.FieldPosition; import java.text.ParseException; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FormatCache.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FormatCache.java similarity index 95% rename from log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FormatCache.java rename to log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FormatCache.java index e146a1dda2a..eac2b1866b2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FormatCache.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FormatCache.java @@ -15,7 +15,7 @@ * limitations under the license. */ -package org.apache.logging.log4j.core.util.datetime; +package org.apache.logging.log4j.core.time.internal.format; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -27,31 +27,31 @@ /** *

    FormatCache is a cache and factory for {@link Format}s.

    - * + * *

    * Copied and modified from Apache Commons Lang. *

    - * + * * @since Apache Commons Lang 3.0 */ // TODO: Before making public move from getDateTimeInstance(Integer,...) to int; or some other approach. abstract class FormatCache { - + /** * No date or no time. Used in same parameters as DateFormat.SHORT or DateFormat.LONG */ static final int NONE= -1; - - private final ConcurrentMap cInstanceCache + + private final ConcurrentMap cInstanceCache = new ConcurrentHashMap<>(7); - - private static final ConcurrentMap cDateTimeInstanceCache + + private static final ConcurrentMap cDateTimeInstanceCache = new ConcurrentHashMap<>(7); /** *

    Gets a formatter instance using the default pattern in the * default timezone and locale.

    - * + * * @return a date/time formatter */ public F getInstance() { @@ -61,7 +61,7 @@ public F getInstance() { /** *

    Gets a formatter instance using the specified pattern, time zone * and locale.

    - * + * * @param pattern {@link java.text.SimpleDateFormat} compatible * pattern, non-null * @param timeZone the time zone, null means use the default TimeZone @@ -82,22 +82,22 @@ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { } final MultipartKey key = new MultipartKey(pattern, timeZone, locale); F format = cInstanceCache.get(key); - if (format == null) { + if (format == null) { format = createInstance(pattern, timeZone, locale); final F previousValue= cInstanceCache.putIfAbsent(key, format); if (previousValue != null) { // another thread snuck in and did the same work // we should return the instance that is in ConcurrentMap - format= previousValue; + format= previousValue; } } return format; } - + /** *

    Create a format instance using the specified pattern, time zone * and locale.

    - * + * * @param pattern {@link java.text.SimpleDateFormat} compatible pattern, this will not be null. * @param timeZone time zone, this will not be null. * @param locale locale, this will not be null. @@ -106,11 +106,11 @@ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { * or null */ abstract protected F createInstance(String pattern, TimeZone timeZone, Locale locale); - + /** *

    Gets a date/time formatter instance using the specified style, * time zone and locale.

    - * + * * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format * @param timeZone optional time zone, overrides time zone of @@ -120,7 +120,7 @@ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { * @throws IllegalArgumentException if the Locale has no date/time * pattern defined */ - // This must remain private, see LANG-884 + // This must remain private, see LANG-884 private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) { if (locale == null) { locale = Locale.getDefault(); @@ -132,7 +132,7 @@ private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, /** *

    Gets a date/time formatter instance using the specified style, * time zone and locale.

    - * + * * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT * @param timeZone optional time zone, overrides time zone of @@ -150,7 +150,7 @@ F getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone t /** *

    Gets a date formatter instance using the specified style, * time zone and locale.

    - * + * * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT * @param timeZone optional time zone, overrides time zone of * formatted date, null means use default Locale @@ -167,7 +167,7 @@ F getDateInstance(final int dateStyle, final TimeZone timeZone, final Locale loc /** *

    Gets a time formatter instance using the specified style, * time zone and locale.

    - * + * * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT * @param timeZone optional time zone, overrides time zone of * formatted date, null means use default Locale @@ -183,7 +183,7 @@ F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale loc /** *

    Gets a date/time format for the specified styles and locale.

    - * + * * @param dateStyle date style: FULL, LONG, MEDIUM, or SHORT, null indicates no date in format * @param timeStyle time style: FULL, LONG, MEDIUM, or SHORT, null indicates no time in format * @param locale The non-null locale of the desired format @@ -197,12 +197,12 @@ static String getPatternForStyle(final Integer dateStyle, final Integer timeStyl String pattern = cDateTimeInstanceCache.get(key); if (pattern == null) { try { - DateFormat formatter; + final DateFormat formatter; if (dateStyle == null) { - formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale); + formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale); } else if (timeStyle == null) { - formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale); + formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale); } else { formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/package-info.java new file mode 100644 index 00000000000..aec2664fa43 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/package-info.java @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +/** + * Log4j 2 date formatting classes. + */ +package org.apache.logging.log4j.core.time.internal.format; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/Generate.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/Generate.java index 0fca21ec82e..0e68bc3c1ec 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/Generate.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/Generate.java @@ -50,22 +50,22 @@ public final class Generate { static final String PACKAGE_DECLARATION = "package %s;%n%n"; - static enum Type { + enum Type { CUSTOM { @Override String imports() { //@formatter:off - return "" - + "import java.io.Serializable;%n" - + "import org.apache.logging.log4j.Level;%n" - + "import org.apache.logging.log4j.LogManager;%n" - + "import org.apache.logging.log4j.Logger;%n" - + "import org.apache.logging.log4j.Marker;%n" - + "import org.apache.logging.log4j.message.Message;%n" - + "import org.apache.logging.log4j.message.MessageFactory;%n" - + "import org.apache.logging.log4j.spi.AbstractLogger;%n" - + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" - + "import org.apache.logging.log4j.util.MessageSupplier;%n" + return "" + + "import java.io.Serializable;%n" + + "import org.apache.logging.log4j.Level;%n" + + "import org.apache.logging.log4j.LogManager;%n" + + "import org.apache.logging.log4j.Logger;%n" + + "import org.apache.logging.log4j.Marker;%n" + + "import org.apache.logging.log4j.message.Message;%n" + + "import org.apache.logging.log4j.message.MessageFactory;%n" + + "import org.apache.logging.log4j.spi.AbstractLogger;%n" + + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" + + "import org.apache.logging.log4j.util.MessageSupplier;%n" + "import org.apache.logging.log4j.util.Supplier;%n" + "%n"; //@formatter:on @@ -74,15 +74,15 @@ String imports() { @Override String declaration() { //@formatter:off - return "" - + "/**%n" - + " * Custom Logger interface with convenience methods for%n" - + " * %s%n" - + " *

    Compatible with Log4j 2.6 or higher.

    %n" - + " */%n" - + "public final class %s implements Serializable {%n" - + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n" - + " private final ExtendedLoggerWrapper logger;%n" + return "" + + "/**%n" + + " * Custom Logger interface with convenience methods for%n" + + " * %s%n" + + " *

    Compatible with Log4j 2.6 or higher.

    %n" + + " */%n" + + "public final class %s implements Serializable {%n" + + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n" + + " private final ExtendedLoggerWrapper logger;%n" + "%n"; //@formatter:on } @@ -90,11 +90,11 @@ String declaration() { @Override String constructor() { //@formatter:off - return "" - + "%n" - + " private %s(final Logger logger) {%n" + return "" + + "%n" + + " private %s(final Logger logger) {%n" + " this.logger = new ExtendedLoggerWrapper((AbstractLogger) logger, logger.getName(), " - + "logger.getMessageFactory());%n" + + "logger.getMessageFactory());%n" + " }%n"; //@formatter:on } @@ -108,16 +108,16 @@ Class generator() { @Override String imports() { //@formatter:off - return "" - + "import org.apache.logging.log4j.Level;%n" - + "import org.apache.logging.log4j.LogManager;%n" - + "import org.apache.logging.log4j.Logger;%n" - + "import org.apache.logging.log4j.Marker;%n" - + "import org.apache.logging.log4j.message.Message;%n" - + "import org.apache.logging.log4j.message.MessageFactory;%n" - + "import org.apache.logging.log4j.spi.AbstractLogger;%n" - + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" - + "import org.apache.logging.log4j.util.MessageSupplier;%n" + return "" + + "import org.apache.logging.log4j.Level;%n" + + "import org.apache.logging.log4j.LogManager;%n" + + "import org.apache.logging.log4j.Logger;%n" + + "import org.apache.logging.log4j.Marker;%n" + + "import org.apache.logging.log4j.message.Message;%n" + + "import org.apache.logging.log4j.message.MessageFactory;%n" + + "import org.apache.logging.log4j.spi.AbstractLogger;%n" + + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" + + "import org.apache.logging.log4j.util.MessageSupplier;%n" + "import org.apache.logging.log4j.util.Supplier;%n" + "%n"; //@formatter:on @@ -126,15 +126,15 @@ String imports() { @Override String declaration() { //@formatter:off - return "" - + "/**%n" - + " * Extended Logger interface with convenience methods for%n" - + " * %s%n" - + " *

    Compatible with Log4j 2.6 or higher.

    %n" - + " */%n" - + "public final class %s extends ExtendedLoggerWrapper {%n" - + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n" - + " private final ExtendedLoggerWrapper logger;%n" + return "" + + "/**%n" + + " * Extended Logger interface with convenience methods for%n" + + " * %s%n" + + " *

    Compatible with Log4j 2.6 or higher.

    %n" + + " */%n" + + "public final class %s extends ExtendedLoggerWrapper {%n" + + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n" + + " private final ExtendedLoggerWrapper logger;%n" + "%n"; //@formatter:on } @@ -142,11 +142,11 @@ String declaration() { @Override String constructor() { //@formatter:off - return "" - + "%n" - + " private %s(final Logger logger) {%n" - + " super((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n" - + " this.logger = this;%n" + return "" + + "%n" + + " private %s(final Logger logger) {%n" + + " super((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n" + + " this.logger = this;%n" + " }%n"; //@formatter:on } @@ -165,826 +165,826 @@ Class generator() { abstract Class generator(); } - static final String FQCN_FIELD = "" + static final String FQCN_FIELD = "" + " private static final String FQCN = %s.class.getName();%n"; - static final String LEVEL_FIELD = "" + static final String LEVEL_FIELD = "" + " private static final Level %s = Level.forName(\"%s\", %d);%n"; - static final String FACTORY_METHODS = "" + static final String FACTORY_METHODS = "" //@formatter:off - + "%n" - + " /**%n" - + " * Returns a custom Logger with the name of the calling class.%n" - + " * %n" - + " * @return The custom Logger for the calling class.%n" - + " */%n" - + " public static CLASSNAME create() {%n" - + " final Logger wrapped = LogManager.getLogger();%n" - + " return new CLASSNAME(wrapped);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Returns a custom Logger using the fully qualified name of the Class as%n" - + " * the Logger name.%n" - + " * %n" - + " * @param loggerName The Class whose name should be used as the Logger name.%n" - + " * If null it will default to the calling class.%n" - + " * @return The custom Logger.%n" - + " */%n" - + " public static CLASSNAME create(final Class loggerName) {%n" - + " final Logger wrapped = LogManager.getLogger(loggerName);%n" - + " return new CLASSNAME(wrapped);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Returns a custom Logger using the fully qualified name of the Class as%n" - + " * the Logger name.%n" - + " * %n" - + " * @param loggerName The Class whose name should be used as the Logger name.%n" - + " * If null it will default to the calling class.%n" - + " * @param messageFactory The message factory is used only when creating a%n" - + " * logger, subsequent use does not change the logger but will log%n" - + " * a warning if mismatched.%n" - + " * @return The custom Logger.%n" - + " */%n" - + " public static CLASSNAME create(final Class loggerName, final MessageFactory" - + " messageFactory) {%n" - + " final Logger wrapped = LogManager.getLogger(loggerName, messageFactory);%n" - + " return new CLASSNAME(wrapped);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Returns a custom Logger using the fully qualified class name of the value%n" - + " * as the Logger name.%n" - + " * %n" - + " * @param value The value whose class name should be used as the Logger%n" - + " * name. If null the name of the calling class will be used as%n" - + " * the logger name.%n" - + " * @return The custom Logger.%n" - + " */%n" - + " public static CLASSNAME create(final Object value) {%n" - + " final Logger wrapped = LogManager.getLogger(value);%n" - + " return new CLASSNAME(wrapped);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Returns a custom Logger using the fully qualified class name of the value%n" - + " * as the Logger name.%n" - + " * %n" - + " * @param value The value whose class name should be used as the Logger%n" - + " * name. If null the name of the calling class will be used as%n" - + " * the logger name.%n" - + " * @param messageFactory The message factory is used only when creating a%n" - + " * logger, subsequent use does not change the logger but will log%n" - + " * a warning if mismatched.%n" - + " * @return The custom Logger.%n" - + " */%n" - + " public static CLASSNAME create(final Object value, final MessageFactory messageFactory) {%n" - + " final Logger wrapped = LogManager.getLogger(value, messageFactory);%n" - + " return new CLASSNAME(wrapped);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Returns a custom Logger with the specified name.%n" - + " * %n" - + " * @param name The logger name. If null the name of the calling class will%n" - + " * be used.%n" - + " * @return The custom Logger.%n" - + " */%n" - + " public static CLASSNAME create(final String name) {%n" - + " final Logger wrapped = LogManager.getLogger(name);%n" - + " return new CLASSNAME(wrapped);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Returns a custom Logger with the specified name.%n" - + " * %n" - + " * @param name The logger name. If null the name of the calling class will%n" - + " * be used.%n" - + " * @param messageFactory The message factory is used only when creating a%n" - + " * logger, subsequent use does not change the logger but will log%n" - + " * a warning if mismatched.%n" - + " * @return The custom Logger.%n" - + " */%n" - + " public static CLASSNAME create(final String name, final MessageFactory messageFactory) {%n" - + " final Logger wrapped = LogManager.getLogger(name, messageFactory);%n" - + " return new CLASSNAME(wrapped);%n" + + "%n" + + " /**%n" + + " * Returns a custom Logger with the name of the calling class.%n" + + " * %n" + + " * @return The custom Logger for the calling class.%n" + + " */%n" + + " public static CLASSNAME create() {%n" + + " final Logger wrapped = LogManager.getLogger();%n" + + " return new CLASSNAME(wrapped);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Returns a custom Logger using the fully qualified name of the Class as%n" + + " * the Logger name.%n" + + " * %n" + + " * @param loggerName The Class whose name should be used as the Logger name.%n" + + " * If null it will default to the calling class.%n" + + " * @return The custom Logger.%n" + + " */%n" + + " public static CLASSNAME create(final Class loggerName) {%n" + + " final Logger wrapped = LogManager.getLogger(loggerName);%n" + + " return new CLASSNAME(wrapped);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Returns a custom Logger using the fully qualified name of the Class as%n" + + " * the Logger name.%n" + + " * %n" + + " * @param loggerName The Class whose name should be used as the Logger name.%n" + + " * If null it will default to the calling class.%n" + + " * @param messageFactory The message factory is used only when creating a%n" + + " * logger, subsequent use does not change the logger but will log%n" + + " * a warning if mismatched.%n" + + " * @return The custom Logger.%n" + + " */%n" + + " public static CLASSNAME create(final Class loggerName, final MessageFactory" + + " messageFactory) {%n" + + " final Logger wrapped = LogManager.getLogger(loggerName, messageFactory);%n" + + " return new CLASSNAME(wrapped);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Returns a custom Logger using the fully qualified class name of the value%n" + + " * as the Logger name.%n" + + " * %n" + + " * @param value The value whose class name should be used as the Logger%n" + + " * name. If null the name of the calling class will be used as%n" + + " * the logger name.%n" + + " * @return The custom Logger.%n" + + " */%n" + + " public static CLASSNAME create(final Object value) {%n" + + " final Logger wrapped = LogManager.getLogger(value);%n" + + " return new CLASSNAME(wrapped);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Returns a custom Logger using the fully qualified class name of the value%n" + + " * as the Logger name.%n" + + " * %n" + + " * @param value The value whose class name should be used as the Logger%n" + + " * name. If null the name of the calling class will be used as%n" + + " * the logger name.%n" + + " * @param messageFactory The message factory is used only when creating a%n" + + " * logger, subsequent use does not change the logger but will log%n" + + " * a warning if mismatched.%n" + + " * @return The custom Logger.%n" + + " */%n" + + " public static CLASSNAME create(final Object value, final MessageFactory messageFactory) {%n" + + " final Logger wrapped = LogManager.getLogger(value, messageFactory);%n" + + " return new CLASSNAME(wrapped);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Returns a custom Logger with the specified name.%n" + + " * %n" + + " * @param name The logger name. If null the name of the calling class will%n" + + " * be used.%n" + + " * @return The custom Logger.%n" + + " */%n" + + " public static CLASSNAME create(final String name) {%n" + + " final Logger wrapped = LogManager.getLogger(name);%n" + + " return new CLASSNAME(wrapped);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Returns a custom Logger with the specified name.%n" + + " * %n" + + " * @param name The logger name. If null the name of the calling class will%n" + + " * be used.%n" + + " * @param messageFactory The message factory is used only when creating a%n" + + " * logger, subsequent use does not change the logger but will log%n" + + " * a warning if mismatched.%n" + + " * @return The custom Logger.%n" + + " */%n" + + " public static CLASSNAME create(final String name, final MessageFactory messageFactory) {%n" + + " final Logger wrapped = LogManager.getLogger(name, messageFactory);%n" + + " return new CLASSNAME(wrapped);%n" + " }%n"; //@formatter:on - static final String METHODS = "" + static final String METHODS = "" //@formatter:off - + "%n" - + " /**%n" - + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param msg the message string to be logged%n" - + " */%n" - + " public void methodName(final Marker marker, final Message msg) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param msg the message string to be logged%n" - + " * @param t A Throwable or null.%n" - + " */%n" - + " public void methodName(final Marker marker, final Message msg, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message object to log.%n" - + " */%n" - + " public void methodName(final Marker marker, final Object message) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message CharSequence with the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message CharSequence to log.%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final CharSequence message) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" - + " * the {@link Throwable} {@code t} passed as parameter.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " */%n" - + " public void methodName(final Marker marker, final Object message, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" - + " * the {@link Throwable} {@code t} passed as parameter.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the CharSequence to log.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final CharSequence message, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message object to log.%n" - + " */%n" - + " public void methodName(final Marker marker, final String message) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param params parameters to the message.%n" - + " * @see #getMessageFactory()%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object... params) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, params);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " + + "%n" + + " /**%n" + + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param msg the message string to be logged%n" + + " */%n" + + " public void methodName(final Marker marker, final Message msg) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param msg the message string to be logged%n" + + " * @param t A Throwable or null.%n" + + " */%n" + + " public void methodName(final Marker marker, final Message msg, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message object to log.%n" + + " */%n" + + " public void methodName(final Marker marker, final Object message) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message CharSequence with the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message CharSequence to log.%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final CharSequence message) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" + + " * the {@link Throwable} {@code t} passed as parameter.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " */%n" + + " public void methodName(final Marker marker, final Object message, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" + + " * the {@link Throwable} {@code t} passed as parameter.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the CharSequence to log.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final CharSequence message, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message object to log.%n" + + " */%n" + + " public void methodName(final Marker marker, final String message) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param params parameters to the message.%n" + + " * @see #getMessageFactory()%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object... params) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, params);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @param p7 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6,%n" + + " final Object p7) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @param p7 parameter to the message.%n" + + " * @param p8 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6,%n" + + " final Object p7, final Object p8) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, " + + "p8);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @param p7 parameter to the message.%n" + + " * @param p8 parameter to the message.%n" + + " * @param p9 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6,%n" + + " final Object p7, final Object p8, final Object p9) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, " + + "p8, p9);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" + + " * the {@link Throwable} {@code t} passed as parameter.%n" + + " * %n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param msg the message string to be logged%n" + + " */%n" + + " public void methodName(final Message msg) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param msg the message string to be logged%n" + + " * @param t A Throwable or null.%n" + + " */%n" + + " public void methodName(final Message msg, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message object to log.%n" + + " */%n" + + " public void methodName(final Object message) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" + + " * the {@link Throwable} {@code t} passed as parameter.%n" + + " * %n" + + " * @param message the message to log.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " */%n" + + " public void methodName(final Object message, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message CharSequence with the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message CharSequence to log.%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final CharSequence message) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a CharSequence at the {@code CUSTOM_LEVEL} level including the stack trace of%n" + + " * the {@link Throwable} {@code t} passed as parameter.%n" + + " * %n" + + " * @param message the CharSequence to log.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final CharSequence message, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message object to log.%n" + + " */%n" + + " public void methodName(final String message) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param params parameters to the message.%n" + + " * @see #getMessageFactory()%n" + + " */%n" + + " public void methodName(final String message, final Object... params) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, params);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + "final Object p1) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @param p7 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6,%n" - + " final Object p7) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @param p7 parameter to the message.%n" - + " * @param p8 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6,%n" - + " final Object p7, final Object p8) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, " - + "p8);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @param p7 parameter to the message.%n" - + " * @param p8 parameter to the message.%n" - + " * @param p9 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6,%n" - + " final Object p7, final Object p8, final Object p9) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, p0, p1, p2, p3, p4, p5, p6, p7, " - + "p8, p9);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" - + " * the {@link Throwable} {@code t} passed as parameter.%n" - + " * %n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param msg the message string to be logged%n" - + " */%n" - + " public void methodName(final Message msg) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param msg the message string to be logged%n" - + " * @param t A Throwable or null.%n" - + " */%n" - + " public void methodName(final Message msg, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message object to log.%n" - + " */%n" - + " public void methodName(final Object message) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" - + " * the {@link Throwable} {@code t} passed as parameter.%n" - + " * %n" - + " * @param message the message to log.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " */%n" - + " public void methodName(final Object message, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message CharSequence with the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message CharSequence to log.%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final CharSequence message) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a CharSequence at the {@code CUSTOM_LEVEL} level including the stack trace of%n" - + " * the {@link Throwable} {@code t} passed as parameter.%n" - + " * %n" - + " * @param message the CharSequence to log.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final CharSequence message, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message object to log.%n" - + " */%n" - + " public void methodName(final String message) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param params parameters to the message.%n" - + " * @see #getMessageFactory()%n" - + " */%n" - + " public void methodName(final String message, final Object... params) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, params);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @param p7 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6,%n" - + " final Object p7) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @param p7 parameter to the message.%n" - + " * @param p8 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6,%n" - + " final Object p7, final Object p8) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, " - + "p8);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" - + " * %n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param p0 parameter to the message.%n" - + " * @param p1 parameter to the message.%n" - + " * @param p2 parameter to the message.%n" - + " * @param p3 parameter to the message.%n" - + " * @param p4 parameter to the message.%n" - + " * @param p5 parameter to the message.%n" - + " * @param p6 parameter to the message.%n" - + " * @param p7 parameter to the message.%n" - + " * @param p8 parameter to the message.%n" - + " * @param p9 parameter to the message.%n" - + " * @see #getMessageFactory()%n" - + " * @since Log4j-2.6%n" - + " */%n" - + " public void methodName(final String message, final Object p0, " - + "final Object p1, final Object p2,%n" - + " final Object p3, final Object p4, final Object p5, final Object p6,%n" - + " final Object p7, final Object p8, final Object p9) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, " - + "p8, p9);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" - + " * the {@link Throwable} {@code t} passed as parameter.%n" - + " * %n" - + " * @param message the message to log.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " */%n" - + " public void methodName(final String message, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @param p7 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6,%n" + + " final Object p7) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @param p7 parameter to the message.%n" + + " * @param p8 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6,%n" + + " final Object p7, final Object p8) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, " + + "p8);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" + + " * %n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param p0 parameter to the message.%n" + + " * @param p1 parameter to the message.%n" + + " * @param p2 parameter to the message.%n" + + " * @param p3 parameter to the message.%n" + + " * @param p4 parameter to the message.%n" + + " * @param p5 parameter to the message.%n" + + " * @param p6 parameter to the message.%n" + + " * @param p7 parameter to the message.%n" + + " * @param p8 parameter to the message.%n" + + " * @param p9 parameter to the message.%n" + + " * @see #getMessageFactory()%n" + + " * @since Log4j-2.6%n" + + " */%n" + + " public void methodName(final String message, final Object p0, " + + "final Object p1, final Object p2,%n" + + " final Object p3, final Object p4, final Object p5, final Object p6,%n" + + " final Object p7, final Object p8, final Object p9) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, p0, p1, p2, p3, p4, p5, p6, p7, " + + "p8, p9);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" + + " * the {@link Throwable} {@code t} passed as parameter.%n" + + " * %n" + + " * @param message the message to log.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " */%n" + + " public void methodName(final String message, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" + " }%n" - + "%n" - + " /**%n" + + "%n" + + " /**%n" + " * Logs a message which is only to be constructed if the logging level is the {@code CUSTOM_LEVEL}" - + "level.%n" - + " *%n" - + " * @param msgSupplier A function, which when called, produces the desired log message;%n" - + " * the format depends on the message factory.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final Supplier msgSupplier) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" + + "level.%n" + + " *%n" + + " * @param msgSupplier A function, which when called, produces the desired log message;%n" + + " * the format depends on the message factory.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final Supplier msgSupplier) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" + " * level) including the stack trace of the {@link Throwable} t passed as parameter.%n" - + " *%n" - + " * @param msgSupplier A function, which when called, produces the desired log message;%n" - + " * the format depends on the message factory.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final Supplier msgSupplier, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message which is only to be constructed if the logging level is the%n" - + " * {@code CUSTOM_LEVEL} level with the specified Marker.%n" - + " *%n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param msgSupplier A function, which when called, produces the desired log message;%n" - + " * the format depends on the message factory.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final Marker marker, final Supplier msgSupplier) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters which are only to be constructed if the logging level is the%n" - + " * {@code CUSTOM_LEVEL} level.%n" - + " *%n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param paramSuppliers An array of functions, which when called, produce the desired log" - + " message parameters.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final Marker marker, final String message, final Supplier..." - + " paramSuppliers) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, paramSuppliers);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" - + " * level) with the specified Marker and including the stack trace of the {@link Throwable}%n" + + " *%n" + + " * @param msgSupplier A function, which when called, produces the desired log message;%n" + + " * the format depends on the message factory.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final Supplier msgSupplier, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message which is only to be constructed if the logging level is the%n" + + " * {@code CUSTOM_LEVEL} level with the specified Marker.%n" + + " *%n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param msgSupplier A function, which when called, produces the desired log message;%n" + + " * the format depends on the message factory.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final Marker marker, final Supplier msgSupplier) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters which are only to be constructed if the logging level is the%n" + + " * {@code CUSTOM_LEVEL} level.%n" + + " *%n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param paramSuppliers An array of functions, which when called, produce the desired log" + + " message parameters.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final Marker marker, final String message, final Supplier..." + + " paramSuppliers) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, paramSuppliers);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" + + " * level) with the specified Marker and including the stack trace of the {@link Throwable}%n" + " * t passed as parameter.%n" - + " *%n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param msgSupplier A function, which when called, produces the desired log message;%n" - + " * the format depends on the message factory.%n" - + " * @param t A Throwable or null.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final Marker marker, final Supplier msgSupplier, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message with parameters which are only to be constructed if the logging level is%n" - + " * the {@code CUSTOM_LEVEL} level.%n" - + " *%n" - + " * @param message the message to log; the format depends on the message factory.%n" - + " * @param paramSuppliers An array of functions, which when called, produce the desired log" - + " message parameters.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final String message, final Supplier... paramSuppliers) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, paramSuppliers);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message which is only to be constructed if the logging level is the%n" - + " * {@code CUSTOM_LEVEL} level with the specified Marker. The {@code MessageSupplier} may or may%n" - + " * not use the {@link MessageFactory} to construct the {@code Message}.%n" - + " *%n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param msgSupplier A function, which when called, produces the desired log message.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final Marker marker, final MessageSupplier msgSupplier) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" - + " * level) with the specified Marker and including the stack trace of the {@link Throwable}%n" - + " * t passed as parameter. The {@code MessageSupplier} may or may not use the%n" + + " *%n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param msgSupplier A function, which when called, produces the desired log message;%n" + + " * the format depends on the message factory.%n" + + " * @param t A Throwable or null.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final Marker marker, final Supplier msgSupplier, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message with parameters which are only to be constructed if the logging level is%n" + + " * the {@code CUSTOM_LEVEL} level.%n" + + " *%n" + + " * @param message the message to log; the format depends on the message factory.%n" + + " * @param paramSuppliers An array of functions, which when called, produce the desired log" + + " message parameters.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final String message, final Supplier... paramSuppliers) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, paramSuppliers);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message which is only to be constructed if the logging level is the%n" + + " * {@code CUSTOM_LEVEL} level with the specified Marker. The {@code MessageSupplier} may or may%n" + + " * not use the {@link MessageFactory} to construct the {@code Message}.%n" + + " *%n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param msgSupplier A function, which when called, produces the desired log message.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final Marker marker, final MessageSupplier msgSupplier) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" + + " * level) with the specified Marker and including the stack trace of the {@link Throwable}%n" + + " * t passed as parameter. The {@code MessageSupplier} may or may not use the%n" + " * {@link MessageFactory} to construct the {@code Message}.%n" - + " *%n" - + " * @param marker the marker data specific to this log statement%n" - + " * @param msgSupplier A function, which when called, produces the desired log message.%n" - + " * @param t A Throwable or null.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final Marker marker, final MessageSupplier msgSupplier, final " - + "Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, t);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message which is only to be constructed if the logging level is the%n" - + " * {@code CUSTOM_LEVEL} level. The {@code MessageSupplier} may or may not use the%n" + + " *%n" + + " * @param marker the marker data specific to this log statement%n" + + " * @param msgSupplier A function, which when called, produces the desired log message.%n" + + " * @param t A Throwable or null.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final Marker marker, final MessageSupplier msgSupplier, final " + + "Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msgSupplier, t);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message which is only to be constructed if the logging level is the%n" + + " * {@code CUSTOM_LEVEL} level. The {@code MessageSupplier} may or may not use the%n" + " * {@link MessageFactory} to construct the {@code Message}.%n" - + " *%n" - + " * @param msgSupplier A function, which when called, produces the desired log message.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final MessageSupplier msgSupplier) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, (Throwable) null);%n" - + " }%n" - + "%n" - + " /**%n" - + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" + + " *%n" + + " * @param msgSupplier A function, which when called, produces the desired log message.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final MessageSupplier msgSupplier) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, (Throwable) null);%n" + + " }%n" + + "%n" + + " /**%n" + + " * Logs a message (only to be constructed if the logging level is the {@code CUSTOM_LEVEL}%n" + " * level) including the stack trace of the {@link Throwable} t passed as parameter.%n" - + " * The {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the%n" + + " * The {@code MessageSupplier} may or may not use the {@link MessageFactory} to construct the%n" + " * {@code Message}.%n" - + " *%n" - + " * @param msgSupplier A function, which when called, produces the desired log message.%n" - + " * @param t the exception to log, including its stack trace.%n" - + " * @since Log4j-2.4%n" - + " */%n" - + " public void methodName(final MessageSupplier msgSupplier, final Throwable t) {%n" - + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, t);%n" + + " *%n" + + " * @param msgSupplier A function, which when called, produces the desired log message.%n" + + " * @param t the exception to log, including its stack trace.%n" + + " * @since Log4j-2.4%n" + + " */%n" + + " public void methodName(final MessageSupplier msgSupplier, final Throwable t) {%n" + + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msgSupplier, t);%n" + " }%n"; //@formatter:on @@ -1061,6 +1061,30 @@ private static void generate(final String[] args, final Type type) { generate(args, type, System.out); } + /** + * Generates source code for extended logger wrappers that provide convenience methods for the specified custom + * levels. + * + * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log + * level to generate convenience methods for + * @param printStream the stream to write the generated source code to + */ + public static void generateExtend(final String[] args, final PrintStream printStream) { + generate(args, Type.EXTEND, printStream); + } + + /** + * Generates source code for custom logger wrappers that only provide convenience methods for the specified + * custom levels, not for the standard built-in levels. + * + * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log + * level to generate convenience methods for + * @param printStream the stream to write the generated source code to + */ + public static void generateCustom(final String[] args, final PrintStream printStream) { + generate(args, Type.CUSTOM, printStream); + } + static void generate(final String[] args, final Type type, final PrintStream printStream) { if (!validate(args)) { usage(printStream, type.generator()); @@ -1073,10 +1097,7 @@ static void generate(final String[] args, final Type type, final PrintStream pri } static boolean validate(final String[] args) { - if (args.length < 2) { - return false; - } - return true; + return args.length >= 2; } private static void usage(final PrintStream out, final Class generator) { @@ -1089,7 +1110,7 @@ private static void usage(final PrintStream out, final Class generator) { static String generateSource(final String classNameFQN, final List levels, final Type type) { final StringBuilder sb = new StringBuilder(10000 * levels.size()); final int lastDot = classNameFQN.lastIndexOf('.'); - final String pkg = classNameFQN.substring(0, lastDot >= 0 ? lastDot : 0); + final String pkg = classNameFQN.substring(0, Math.max(lastDot, 0)); if (!pkg.isEmpty()) { sb.append(String.format(PACKAGE_DECLARATION, pkg)); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java index a4cc8d2e9fb..53be2233ef2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java @@ -16,6 +16,10 @@ */ package org.apache.logging.log4j.core.tools.picocli; +import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle; +import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Style; +import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Text; + import java.io.File; import java.io.PrintStream; import java.lang.annotation.ElementType; @@ -64,10 +68,6 @@ import java.util.concurrent.Callable; import java.util.regex.Pattern; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Style; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Text; - import static java.util.Locale.ENGLISH; import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.SPAN; import static org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Column.Overflow.TRUNCATE; @@ -139,11 +139,11 @@ public class CommandLine { private String commandName = Help.DEFAULT_COMMAND_NAME; private boolean overwrittenOptionsAllowed = false; private boolean unmatchedArgumentsAllowed = false; - private List unmatchedArguments = new ArrayList(); + private final List unmatchedArguments = new ArrayList(); private CommandLine parent; private boolean usageHelpRequested; private boolean versionHelpRequested; - private List versionLines = new ArrayList(); + private final List versionLines = new ArrayList(); /** * Constructs a new {@code CommandLine} interpreter with the specified annotated object. @@ -152,7 +152,7 @@ public class CommandLine { * @param command the object to initialize from the command line arguments * @throws InitializationException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation */ - public CommandLine(Object command) { + public CommandLine(final Object command) { interpreter = new Interpreter(command); } @@ -197,8 +197,8 @@ public CommandLine(Object command) { * @since 0.9.7 * @see Command#subcommands() */ - public CommandLine addSubcommand(String name, Object command) { - CommandLine commandLine = toCommandLine(command); + public CommandLine addSubcommand(final String name, final Object command) { + final CommandLine commandLine = toCommandLine(command); commandLine.parent = this; interpreter.commands.put(name, commandLine); return this; @@ -259,9 +259,9 @@ public boolean isOverwrittenOptionsAllowed() { * @return this {@code CommandLine} object, to allow method chaining * @since 0.9.7 */ - public CommandLine setOverwrittenOptionsAllowed(boolean newValue) { + public CommandLine setOverwrittenOptionsAllowed(final boolean newValue) { this.overwrittenOptionsAllowed = newValue; - for (CommandLine command : interpreter.commands.values()) { + for (final CommandLine command : interpreter.commands.values()) { command.setOverwrittenOptionsAllowed(newValue); } return this; @@ -288,9 +288,9 @@ public boolean isUnmatchedArgumentsAllowed() { * @since 0.9.7 * @see #getUnmatchedArguments() */ - public CommandLine setUnmatchedArgumentsAllowed(boolean newValue) { + public CommandLine setUnmatchedArgumentsAllowed(final boolean newValue) { this.unmatchedArgumentsAllowed = newValue; - for (CommandLine command : interpreter.commands.values()) { + for (final CommandLine command : interpreter.commands.values()) { command.setUnmatchedArgumentsAllowed(newValue); } return this; @@ -325,8 +325,8 @@ public List getUnmatchedArguments() { * @throws ParameterException if the specified command line arguments are invalid * @since 0.9.7 */ - public static T populateCommand(T command, String... args) { - CommandLine cli = toCommandLine(command); + public static T populateCommand(final T command, final String... args) { + final CommandLine cli = toCommandLine(command); cli.parse(args); return command; } @@ -345,7 +345,7 @@ public static T populateCommand(T command, String... args) { * @throws ParameterException if the specified command line arguments are invalid; use * {@link ParameterException#getCommandLine()} to get the command or subcommand whose user input was invalid */ - public List parse(String... args) { + public List parse(final String... args) { return interpreter.parse(args); } /** @@ -361,7 +361,7 @@ public List parse(String... args) { * @see RunLast * @see RunAll * @since 2.0 */ - public static interface IParseResultHandler { + public interface IParseResultHandler { /** Processes a List of {@code CommandLine} objects resulting from successfully * {@linkplain #parse(String...) parsing} the command line arguments and optionally returns a list of results. * @param parsedCommands the {@code CommandLine} objects that resulted from successfully parsing the command line arguments @@ -384,7 +384,7 @@ public static interface IParseResultHandler { *

    * @see DefaultExceptionHandler * @since 2.0 */ - public static interface IExceptionHandler { + public interface IExceptionHandler { /** Handles a {@code ParameterException} that occurred while {@linkplain #parse(String...) parsing} the command * line arguments and optionally returns a list of results. * @param ex the ParameterException describing the problem that occurred while parsing the command line arguments, @@ -407,7 +407,7 @@ public static interface IExceptionHandler { * @since 2.0 */ public static class DefaultExceptionHandler implements IExceptionHandler { @Override - public List handleException(ParameterException ex, PrintStream out, Help.Ansi ansi, String... args) { + public List handleException(final ParameterException ex, final PrintStream out, final Help.Ansi ansi, final String... args) { out.println(ex.getMessage()); ex.getCommandLine().usage(out, ansi); return Collections.emptyList(); @@ -429,8 +429,8 @@ public List handleException(ParameterException ex, PrintStream out, Help * @param ansi for printing help messages using ANSI styles and colors * @return {@code true} if help was printed, {@code false} otherwise * @since 2.0 */ - public static boolean printHelpIfRequested(List parsedCommands, PrintStream out, Help.Ansi ansi) { - for (CommandLine parsed : parsedCommands) { + public static boolean printHelpIfRequested(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { + for (final CommandLine parsed : parsedCommands) { if (parsed.isUsageHelpRequested()) { parsed.usage(out, ansi); return true; @@ -441,19 +441,19 @@ public static boolean printHelpIfRequested(List parsedCommands, Pri } return false; } - private static Object execute(CommandLine parsed) { - Object command = parsed.getCommand(); + private static Object execute(final CommandLine parsed) { + final Object command = parsed.getCommand(); if (command instanceof Runnable) { try { ((Runnable) command).run(); return null; - } catch (Exception ex) { + } catch (final Exception ex) { throw new ExecutionException(parsed, "Error while running command (" + command + ")", ex); } } else if (command instanceof Callable) { try { return ((Callable) command).call(); - } catch (Exception ex) { + } catch (final Exception ex) { throw new ExecutionException(parsed, "Error while calling command (" + command + ")", ex); } } @@ -482,7 +482,7 @@ public static class RunFirst implements IParseResultHandler { * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed */ @Override - public List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) { + public List handleParseResult(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); } return Arrays.asList(execute(parsedCommands.get(0))); } @@ -533,9 +533,9 @@ public static class RunLast implements IParseResultHandler { * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed */ @Override - public List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) { + public List handleParseResult(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { if (printHelpIfRequested(parsedCommands, out, ansi)) { return Collections.emptyList(); } - CommandLine last = parsedCommands.get(parsedCommands.size() - 1); + final CommandLine last = parsedCommands.get(parsedCommands.size() - 1); return Arrays.asList(execute(last)); } } @@ -559,12 +559,12 @@ public static class RunAll implements IParseResultHandler { * {@link ExecutionException#getCommandLine()} to get the command or subcommand where processing failed */ @Override - public List handleParseResult(List parsedCommands, PrintStream out, Help.Ansi ansi) { + public List handleParseResult(final List parsedCommands, final PrintStream out, final Help.Ansi ansi) { if (printHelpIfRequested(parsedCommands, out, ansi)) { return null; } - List result = new ArrayList(); - for (CommandLine parsed : parsedCommands) { + final List result = new ArrayList(); + for (final CommandLine parsed : parsedCommands) { result.add(execute(parsed)); } return result; @@ -607,7 +607,7 @@ public List handleParseResult(List parsedCommands, PrintStr * @see RunLast * @see RunAll * @since 2.0 */ - public List parseWithHandler(IParseResultHandler handler, PrintStream out, String... args) { + public List parseWithHandler(final IParseResultHandler handler, final PrintStream out, final String... args) { return parseWithHandlers(handler, out, Help.Ansi.AUTO, new DefaultExceptionHandler(), args); } /** @@ -652,11 +652,11 @@ public List parseWithHandler(IParseResultHandler handler, PrintStream ou * @see RunAll * @see DefaultExceptionHandler * @since 2.0 */ - public List parseWithHandlers(IParseResultHandler handler, PrintStream out, Help.Ansi ansi, IExceptionHandler exceptionHandler, String... args) { + public List parseWithHandlers(final IParseResultHandler handler, final PrintStream out, final Help.Ansi ansi, final IExceptionHandler exceptionHandler, final String... args) { try { - List result = parse(args); + final List result = parse(args); return handler.handleParseResult(result, out, ansi); - } catch (ParameterException ex) { + } catch (final ParameterException ex) { return exceptionHandler.handleException(ex, out, ansi, args); } } @@ -666,7 +666,7 @@ public List parseWithHandlers(IParseResultHandler handler, PrintStream o * @param out the print stream to print the help message to * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation */ - public static void usage(Object command, PrintStream out) { + public static void usage(final Object command, final PrintStream out) { toCommandLine(command).usage(out); } @@ -678,7 +678,7 @@ public static void usage(Object command, PrintStream out) { * @param ansi whether the usage message should contain ANSI escape codes or not * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation */ - public static void usage(Object command, PrintStream out, Help.Ansi ansi) { + public static void usage(final Object command, final PrintStream out, final Help.Ansi ansi) { toCommandLine(command).usage(out, ansi); } @@ -690,7 +690,7 @@ public static void usage(Object command, PrintStream out, Help.Ansi ansi) { * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled * @throws IllegalArgumentException if the specified command object does not have a {@link Command}, {@link Option} or {@link Parameters} annotation */ - public static void usage(Object command, PrintStream out, Help.ColorScheme colorScheme) { + public static void usage(final Object command, final PrintStream out, final Help.ColorScheme colorScheme) { toCommandLine(command).usage(out, colorScheme); } @@ -699,7 +699,7 @@ public static void usage(Object command, PrintStream out, Help.ColorScheme color * @param out the printStream to print to * @see #usage(PrintStream, Help.ColorScheme) */ - public void usage(PrintStream out) { + public void usage(final PrintStream out) { usage(out, Help.Ansi.AUTO); } @@ -709,7 +709,7 @@ public void usage(PrintStream out) { * @param ansi whether the usage message should include ANSI escape codes or not * @see #usage(PrintStream, Help.ColorScheme) */ - public void usage(PrintStream out, Help.Ansi ansi) { + public void usage(final PrintStream out, final Help.Ansi ansi) { usage(out, Help.defaultColorScheme(ansi)); } /** @@ -744,8 +744,8 @@ public void usage(PrintStream out, Help.Ansi ansi) { * @param out the {@code PrintStream} to print the usage help message to * @param colorScheme the {@code ColorScheme} defining the styles for options, parameters and commands when ANSI is enabled */ - public void usage(PrintStream out, Help.ColorScheme colorScheme) { - Help help = new Help(interpreter.command, colorScheme).addAllSubcommands(getSubcommands()); + public void usage(final PrintStream out, final Help.ColorScheme colorScheme) { + final Help help = new Help(interpreter.command, colorScheme).addAllSubcommands(getSubcommands()); if (!Help.DEFAULT_SEPARATOR.equals(getSeparator())) { help.separator = getSeparator(); help.parameterLabelRenderer = help.createDefaultParamLabelRenderer(); // update for new separator @@ -753,7 +753,7 @@ public void usage(PrintStream out, Help.ColorScheme colorScheme) { if (!Help.DEFAULT_COMMAND_NAME.equals(getCommandName())) { help.commandName = getCommandName(); } - StringBuilder sb = new StringBuilder() + final StringBuilder sb = new StringBuilder() .append(help.headerHeading()) .append(help.header()) .append(help.synopsisHeading()) //e.g. Usage: @@ -777,7 +777,7 @@ public void usage(PrintStream out, Help.ColorScheme colorScheme) { * @see #printVersionHelp(PrintStream, Help.Ansi) * @since 0.9.8 */ - public void printVersionHelp(PrintStream out) { printVersionHelp(out, Help.Ansi.AUTO); } + public void printVersionHelp(final PrintStream out) { printVersionHelp(out, Help.Ansi.AUTO); } /** * Prints version information from the {@link Command#version()} annotation to the specified {@code PrintStream}. @@ -790,8 +790,8 @@ public void usage(PrintStream out, Help.ColorScheme colorScheme) { * @see #isVersionHelpRequested() * @since 0.9.8 */ - public void printVersionHelp(PrintStream out, Help.Ansi ansi) { - for (String versionInfo : versionLines) { + public void printVersionHelp(final PrintStream out, final Help.Ansi ansi) { + for (final String versionInfo : versionLines) { out.println(ansi.new Text(versionInfo)); } } @@ -808,8 +808,8 @@ public void printVersionHelp(PrintStream out, Help.Ansi ansi) { * @see #isVersionHelpRequested() * @since 1.0.0 */ - public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) { - for (String versionInfo : versionLines) { + public void printVersionHelp(final PrintStream out, final Help.Ansi ansi, final Object... params) { + for (final String versionInfo : versionLines) { out.println(ansi.new Text(String.format(versionInfo, params))); } } @@ -832,7 +832,7 @@ public void printVersionHelp(PrintStream out, Help.Ansi ansi, Object... params) * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunFirst */ - public static , T> T call(C callable, PrintStream out, String... args) { + public static , T> T call(final C callable, final PrintStream out, final String... args) { return call(callable, out, Help.Ansi.AUTO, args); } /** @@ -880,9 +880,9 @@ public static , T> T call(C callable, PrintStream out, Str * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunLast */ - public static , T> T call(C callable, PrintStream out, Help.Ansi ansi, String... args) { - CommandLine cmd = new CommandLine(callable); // validate command outside of try-catch - List results = cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args); + public static , T> T call(final C callable, final PrintStream out, final Help.Ansi ansi, final String... args) { + final CommandLine cmd = new CommandLine(callable); // validate command outside of try-catch + final List results = cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args); return results == null || results.isEmpty() ? null : (T) results.get(0); } @@ -902,7 +902,7 @@ public static , T> T call(C callable, PrintStream out, Hel * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunFirst */ - public static void run(R runnable, PrintStream out, String... args) { + public static void run(final R runnable, final PrintStream out, final String... args) { run(runnable, out, Help.Ansi.AUTO, args); } /** @@ -948,8 +948,8 @@ public static void run(R runnable, PrintStream out, String. * @see #parseWithHandlers(IParseResultHandler, PrintStream, Help.Ansi, IExceptionHandler, String...) * @see RunLast */ - public static void run(R runnable, PrintStream out, Help.Ansi ansi, String... args) { - CommandLine cmd = new CommandLine(runnable); // validate command outside of try-catch + public static void run(final R runnable, final PrintStream out, final Help.Ansi ansi, final String... args) { + final CommandLine cmd = new CommandLine(runnable); // validate command outside of try-catch cmd.parseWithHandlers(new RunLast(), out, ansi, new DefaultExceptionHandler(), args); } @@ -996,9 +996,9 @@ public static void run(R runnable, PrintStream out, Help.An * @return this CommandLine object, to allow method chaining * @see #addSubcommand(String, Object) */ - public CommandLine registerConverter(Class cls, ITypeConverter converter) { + public CommandLine registerConverter(final Class cls, final ITypeConverter converter) { interpreter.converterRegistry.put(Assert.notNull(cls, "class"), Assert.notNull(converter, "converter")); - for (CommandLine command : interpreter.commands.values()) { + for (final CommandLine command : interpreter.commands.values()) { command.registerConverter(cls, converter); } return this; @@ -1014,7 +1014,7 @@ public String getSeparator() { * The separator may also be set declaratively with the {@link CommandLine.Command#separator()} annotation attribute. * @param separator the String that separates option names from option values * @return this {@code CommandLine} object, to allow method chaining */ - public CommandLine setSeparator(String separator) { + public CommandLine setSeparator(final String separator) { interpreter.separator = Assert.notNull(separator, "separator"); return this; } @@ -1030,35 +1030,35 @@ public String getCommandName() { * The command name may also be set declaratively with the {@link CommandLine.Command#name()} annotation attribute. * @param commandName command name (also called program name) displayed in the usage help synopsis * @return this {@code CommandLine} object, to allow method chaining */ - public CommandLine setCommandName(String commandName) { + public CommandLine setCommandName(final String commandName) { this.commandName = Assert.notNull(commandName, "commandName"); return this; } - private static boolean empty(String str) { return str == null || str.trim().length() == 0; } - private static boolean empty(Object[] array) { return array == null || array.length == 0; } - private static boolean empty(Text txt) { return txt == null || txt.plain.toString().trim().length() == 0; } - private static String str(String[] arr, int i) { return (arr == null || arr.length == 0) ? "" : arr[i]; } - private static boolean isBoolean(Class type) { return type == Boolean.class || type == Boolean.TYPE; } - private static CommandLine toCommandLine(Object obj) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);} - private static boolean isMultiValue(Field field) { return isMultiValue(field.getType()); } - private static boolean isMultiValue(Class cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); } - private static Class[] getTypeAttribute(Field field) { - Class[] explicit = field.isAnnotationPresent(Parameters.class) ? field.getAnnotation(Parameters.class).type() : field.getAnnotation(Option.class).type(); + private static boolean empty(final String str) { return str == null || str.trim().length() == 0; } + private static boolean empty(final Object[] array) { return array == null || array.length == 0; } + private static boolean empty(final Text txt) { return txt == null || txt.plain.toString().trim().length() == 0; } + private static String str(final String[] arr, final int i) { return (arr == null || arr.length == 0) ? "" : arr[i]; } + private static boolean isBoolean(final Class type) { return type == Boolean.class || type == Boolean.TYPE; } + private static CommandLine toCommandLine(final Object obj) { return obj instanceof CommandLine ? (CommandLine) obj : new CommandLine(obj);} + private static boolean isMultiValue(final Field field) { return isMultiValue(field.getType()); } + private static boolean isMultiValue(final Class cls) { return cls.isArray() || Collection.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls); } + private static Class[] getTypeAttribute(final Field field) { + final Class[] explicit = field.isAnnotationPresent(Parameters.class) ? field.getAnnotation(Parameters.class).type() : field.getAnnotation(Option.class).type(); if (explicit.length > 0) { return explicit; } if (field.getType().isArray()) { return new Class[] { field.getType().getComponentType() }; } if (isMultiValue(field)) { - Type type = field.getGenericType(); // e.g. Map + final Type type = field.getGenericType(); // e.g. Map if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number - Class[] result = new Class[paramTypes.length]; + final ParameterizedType parameterizedType = (ParameterizedType) type; + final Type[] paramTypes = parameterizedType.getActualTypeArguments(); // e.g. ? extends Number + final Class[] result = new Class[paramTypes.length]; for (int i = 0; i < paramTypes.length; i++) { if (paramTypes[i] instanceof Class) { result[i] = (Class) paramTypes[i]; continue; } // e.g. Long if (paramTypes[i] instanceof WildcardType) { // e.g. ? extends Number - WildcardType wildcardType = (WildcardType) paramTypes[i]; - Type[] lower = wildcardType.getLowerBounds(); // e.g. [] + final WildcardType wildcardType = (WildcardType) paramTypes[i]; + final Type[] lower = wildcardType.getLowerBounds(); // e.g. [] if (lower.length > 0 && lower[0] instanceof Class) { result[i] = (Class) lower[0]; continue; } - Type[] upper = wildcardType.getUpperBounds(); // e.g. Number + final Type[] upper = wildcardType.getUpperBounds(); // e.g. Number if (upper.length > 0 && upper[0] instanceof Class) { result[i] = (Class) upper[0]; continue; } } Arrays.fill(result, String.class); return result; // too convoluted generic type, giving up @@ -1656,7 +1656,7 @@ public static class Range implements Comparable { * @param unspecified {@code true} if no arity was specified on the option/parameter (value is based on type) * @param originalValue the original value that was specified on the option or parameter */ - public Range(int min, int max, boolean variable, boolean unspecified, String originalValue) { + public Range(final int min, final int max, final boolean variable, final boolean unspecified, final String originalValue) { this.min = min; this.max = max; this.isVariable = variable; @@ -1667,7 +1667,7 @@ public Range(int min, int max, boolean variable, boolean unspecified, String ori * or the field type's default arity if no arity was specified. * @param field the field whose Option annotation to inspect * @return a new {@code Range} based on the Option arity annotation on the specified field */ - public static Range optionArity(Field field) { + public static Range optionArity(final Field field) { return field.isAnnotationPresent(Option.class) ? adjustForType(Range.valueOf(field.getAnnotation(Option.class).arity()), field) : new Range(0, 0, false, true, "0"); @@ -1676,7 +1676,7 @@ public static Range optionArity(Field field) { * or the field type's default arity if no arity was specified. * @param field the field whose Parameters annotation to inspect * @return a new {@code Range} based on the Parameters arity annotation on the specified field */ - public static Range parameterArity(Field field) { + public static Range parameterArity(final Field field) { return field.isAnnotationPresent(Parameters.class) ? adjustForType(Range.valueOf(field.getAnnotation(Parameters.class).arity()), field) : new Range(0, 0, false, true, "0"); @@ -1684,12 +1684,12 @@ public static Range parameterArity(Field field) { /** Returns a new {@code Range} based on the {@link Parameters#index()} annotation on the specified field. * @param field the field whose Parameters annotation to inspect * @return a new {@code Range} based on the Parameters index annotation on the specified field */ - public static Range parameterIndex(Field field) { + public static Range parameterIndex(final Field field) { return field.isAnnotationPresent(Parameters.class) ? Range.valueOf(field.getAnnotation(Parameters.class).index()) : new Range(0, 0, false, true, "0"); } - static Range adjustForType(Range result, Field field) { + static Range adjustForType(final Range result, final Field field) { return result.isUnspecified ? defaultArity(field) : result; } /** Returns the default arity {@code Range}: for {@link Option options} this is 0 for booleans and 1 for @@ -1698,8 +1698,8 @@ static Range adjustForType(Range result, Field field) { * @param field the field whose default arity to return * @return a new {@code Range} indicating the default arity of the specified field * @since 2.0 */ - public static Range defaultArity(Field field) { - Class type = field.getType(); + public static Range defaultArity(final Field field) { + final Class type = field.getType(); if (field.isAnnotationPresent(Option.class)) { return defaultArity(type); } @@ -1711,14 +1711,14 @@ public static Range defaultArity(Field field) { /** Returns the default arity {@code Range} for {@link Option options}: booleans have arity 0, other types have arity 1. * @param type the type whose default arity to return * @return a new {@code Range} indicating the default arity of the specified type */ - public static Range defaultArity(Class type) { + public static Range defaultArity(final Class type) { return isBoolean(type) ? Range.valueOf("0") : Range.valueOf("1"); } private int size() { return 1 + max - min; } - static Range parameterCapacity(Field field) { - Range arity = parameterArity(field); + static Range parameterCapacity(final Field field) { + final Range arity = parameterArity(field); if (!isMultiValue(field)) { return arity; } - Range index = parameterIndex(field); + final Range index = parameterIndex(field); if (arity.max == 0) { return arity; } if (index.size() == 1) { return arity; } if (index.isVariable) { return Range.valueOf(arity.min + "..*"); } @@ -1734,7 +1734,7 @@ static Range parameterCapacity(Field field) { * @return a new {@code Range} value */ public static Range valueOf(String range) { range = range.trim(); - boolean unspecified = range.length() == 0 || range.startsWith(".."); // || range.endsWith(".."); + final boolean unspecified = range.length() == 0 || range.startsWith(".."); // || range.endsWith(".."); int min = -1, max = -1; boolean variable = false; int dots = -1; @@ -1747,13 +1747,12 @@ public static Range valueOf(String range) { variable = max == Integer.MAX_VALUE; min = variable ? 0 : max; } - Range result = new Range(min, max, variable, unspecified, range); - return result; + return new Range(min, max, variable, unspecified, range); } - private static int parseInt(String str, int defaultValue) { + private static int parseInt(final String str, final int defaultValue) { try { return Integer.parseInt(str); - } catch (Exception ex) { + } catch (final Exception ex) { return defaultValue; } } @@ -1761,25 +1760,25 @@ private static int parseInt(String str, int defaultValue) { * The {@code max} of the returned Range is guaranteed not to be less than the new {@code min} value. * @param newMin the {@code min} value of the returned Range object * @return a new Range object with the specified {@code min} value */ - public Range min(int newMin) { return new Range(newMin, Math.max(newMin, max), isVariable, isUnspecified, originalValue); } + public Range min(final int newMin) { return new Range(newMin, Math.max(newMin, max), isVariable, isUnspecified, originalValue); } /** Returns a new Range object with the {@code max} value replaced by the specified value. * The {@code min} of the returned Range is guaranteed not to be greater than the new {@code max} value. * @param newMax the {@code max} value of the returned Range object * @return a new Range object with the specified {@code max} value */ - public Range max(int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); } + public Range max(final int newMax) { return new Range(Math.min(min, newMax), newMax, isVariable, isUnspecified, originalValue); } /** * Returns {@code true} if this Range includes the specified value, {@code false} otherwise. * @param value the value to check * @return {@code true} if the specified value is not less than the minimum and not greater than the maximum of this Range */ - public boolean contains(int value) { return min <= value && max >= value; } + public boolean contains(final int value) { return min <= value && max >= value; } @Override - public boolean equals(Object object) { + public boolean equals(final Object object) { if (!(object instanceof Range)) { return false; } - Range other = (Range) object; + final Range other = (Range) object; return other.max == this.max && other.min == this.min && other.isVariable == this.isVariable; } @Override @@ -1791,32 +1790,32 @@ public String toString() { return min == max ? String.valueOf(min) : min + ".." + (isVariable ? "*" : max); } @Override - public int compareTo(Range other) { - int result = min - other.min; + public int compareTo(final Range other) { + final int result = min - other.min; return (result == 0) ? max - other.max : result; } } - static void init(Class cls, - List requiredFields, - Map optionName2Field, - Map singleCharOption2Field, - List positionalParametersFields) { - Field[] declaredFields = cls.getDeclaredFields(); - for (Field field : declaredFields) { + static void init(final Class cls, + final List requiredFields, + final Map optionName2Field, + final Map singleCharOption2Field, + final List positionalParametersFields) { + final Field[] declaredFields = cls.getDeclaredFields(); + for (final Field field : declaredFields) { field.setAccessible(true); if (field.isAnnotationPresent(Option.class)) { - Option option = field.getAnnotation(Option.class); + final Option option = field.getAnnotation(Option.class); if (option.required()) { requiredFields.add(field); } - for (String name : option.names()) { // cannot be null or empty - Field existing = optionName2Field.put(name, field); + for (final String name : option.names()) { // cannot be null or empty + final Field existing = optionName2Field.put(name, field); if (existing != null && existing != field) { throw DuplicateOptionAnnotationsException.create(name, field, existing); } if (name.length() == 2 && name.startsWith("-")) { - char flag = name.charAt(1); - Field existing2 = singleCharOption2Field.put(flag, field); + final char flag = name.charAt(1); + final Field existing2 = singleCharOption2Field.put(flag, field); if (existing2 != null && existing2 != field) { throw DuplicateOptionAnnotationsException.create(name, field, existing2); } @@ -1829,17 +1828,17 @@ static void init(Class cls, + field.getName() + "' is both."); } positionalParametersFields.add(field); - Range arity = Range.parameterArity(field); + final Range arity = Range.parameterArity(field); if (arity.min > 0) { requiredFields.add(field); } } } } - static void validatePositionalParameters(List positionalParametersFields) { + static void validatePositionalParameters(final List positionalParametersFields) { int min = 0; - for (Field field : positionalParametersFields) { - Range index = Range.parameterIndex(field); + for (final Field field : positionalParametersFields) { + final Range index = Range.parameterIndex(field); if (index.min > min) { throw new ParameterIndexGapException("Missing field annotated with @Parameter(index=" + min + "). Nearest field '" + field.getName() + "' has index=" + index.min); @@ -1848,7 +1847,7 @@ static void validatePositionalParameters(List positionalParametersFields) min = min == Integer.MAX_VALUE ? min : min + 1; } } - private static Stack reverse(Stack stack) { + private static Stack reverse(final Stack stack) { Collections.reverse(stack); return stack; } @@ -1867,7 +1866,7 @@ private class Interpreter { private String separator = Help.DEFAULT_SEPARATOR; private int position; - Interpreter(Object command) { + Interpreter(final Object command) { converterRegistry.put(Path.class, new BuiltIn.PathConverter()); converterRegistry.put(Object.class, new BuiltIn.StringConverter()); converterRegistry.put(String.class, new BuiltIn.StringConverter()); @@ -1910,28 +1909,28 @@ private class Interpreter { init(cls, requiredFields, optionName2Field, singleCharOption2Field, positionalParametersFields); if (cls.isAnnotationPresent(Command.class)) { hasCommandAnnotation = true; - Command cmd = cls.getAnnotation(Command.class); + final Command cmd = cls.getAnnotation(Command.class); declaredSeparator = (declaredSeparator == null) ? cmd.separator() : declaredSeparator; declaredName = (declaredName == null) ? cmd.name() : declaredName; CommandLine.this.versionLines.addAll(Arrays.asList(cmd.version())); - for (Class sub : cmd.subcommands()) { - Command subCommand = sub.getAnnotation(Command.class); + for (final Class sub : cmd.subcommands()) { + final Command subCommand = sub.getAnnotation(Command.class); if (subCommand == null || Help.DEFAULT_COMMAND_NAME.equals(subCommand.name())) { throw new InitializationException("Subcommand " + sub.getName() + " is missing the mandatory @Command annotation with a 'name' attribute"); } try { - Constructor constructor = sub.getDeclaredConstructor(); + final Constructor constructor = sub.getDeclaredConstructor(); constructor.setAccessible(true); - CommandLine commandLine = toCommandLine(constructor.newInstance()); + final CommandLine commandLine = toCommandLine(constructor.newInstance()); commandLine.parent = CommandLine.this; commands.put(subCommand.name(), commandLine); } - catch (InitializationException ex) { throw ex; } - catch (NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " + + catch (final InitializationException ex) { throw ex; } + catch (final NoSuchMethodException ex) { throw new InitializationException("Cannot instantiate subcommand " + sub.getName() + ": the class has no constructor", ex); } - catch (Exception ex) { + catch (final Exception ex) { throw new InitializationException("Could not instantiate and add subcommand " + sub.getName() + ": " + ex, ex); } @@ -1956,41 +1955,41 @@ private class Interpreter { * @return a list with all commands and subcommands initialized by this method * @throws ParameterException if the specified command line arguments are invalid */ - List parse(String... args) { + List parse(final String... args) { Assert.notNull(args, "argument array"); if (tracer.isInfo()) {tracer.info("Parsing %d command line args %s%n", args.length, Arrays.toString(args));} - Stack arguments = new Stack(); + final Stack arguments = new Stack(); for (int i = args.length - 1; i >= 0; i--) { arguments.push(args[i]); } - List result = new ArrayList(); + final List result = new ArrayList(); parse(result, arguments, args); return result; } - private void parse(List parsedCommands, Stack argumentStack, String[] originalArgs) { + private void parse(final List parsedCommands, final Stack argumentStack, final String[] originalArgs) { // first reset any state in case this CommandLine instance is being reused isHelpRequested = false; CommandLine.this.versionHelpRequested = false; CommandLine.this.usageHelpRequested = false; - Class cmdClass = this.command.getClass(); + final Class cmdClass = this.command.getClass(); if (tracer.isDebug()) {tracer.debug("Initializing %s: %d options, %d positional parameters, %d required, %d subcommands.%n", cmdClass.getName(), new HashSet(optionName2Field.values()).size(), positionalParametersFields.size(), requiredFields.size(), commands.size());} parsedCommands.add(CommandLine.this); - List required = new ArrayList(requiredFields); - Set initialized = new HashSet(); + final List required = new ArrayList(requiredFields); + final Set initialized = new HashSet(); Collections.sort(required, new PositionalParametersSorter()); try { processArguments(parsedCommands, argumentStack, required, initialized, originalArgs); - } catch (ParameterException ex) { + } catch (final ParameterException ex) { throw ex; - } catch (Exception ex) { - int offendingArgIndex = originalArgs.length - argumentStack.size() - 1; - String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?"; + } catch (final Exception ex) { + final int offendingArgIndex = originalArgs.length - argumentStack.size() - 1; + final String arg = offendingArgIndex >= 0 && offendingArgIndex < originalArgs.length ? originalArgs[offendingArgIndex] : "?"; throw ParameterException.create(CommandLine.this, ex, arg, offendingArgIndex, originalArgs); } if (!isAnyHelpRequested() && !required.isEmpty()) { - for (Field missing : required) { + for (final Field missing : required) { if (missing.isAnnotationPresent(Option.class)) { throw MissingParameterException.create(CommandLine.this, required, separator); } else { @@ -2004,11 +2003,11 @@ private void parse(List parsedCommands, Stack argumentStack } } - private void processArguments(List parsedCommands, - Stack args, - Collection required, - Set initialized, - String[] originalArgs) throws Exception { + private void processArguments(final List parsedCommands, + final Stack args, + final Collection required, + final Set initialized, + final String[] originalArgs) throws Exception { // arg must be one of: // 1. the "--" double dash separating options from positional arguments // 1. a stand-alone flag, like "-v" or "--verbose": no value required, must map to boolean or Boolean field @@ -2045,13 +2044,13 @@ private void processArguments(List parsedCommands, // or an option may have one or more option parameters. // A parameter may be attached to the option. boolean paramAttachedToOption = false; - int separatorIndex = arg.indexOf(separator); + final int separatorIndex = arg.indexOf(separator); if (separatorIndex > 0) { - String key = arg.substring(0, separatorIndex); + final String key = arg.substring(0, separatorIndex); // be greedy. Consume the whole arg as an option if possible. if (optionName2Field.containsKey(key) && !optionName2Field.containsKey(arg)) { paramAttachedToOption = true; - String optionParam = arg.substring(separatorIndex + separator.length()); + final String optionParam = arg.substring(separatorIndex + separator.length()); args.push(optionParam); arg = key; if (tracer.isDebug()) {tracer.debug("Separated '%s' option from '%s' option parameter%n", key, optionParam);} @@ -2081,43 +2080,43 @@ else if (arg.length() > 2 && arg.startsWith("-")) { } } } - private boolean resemblesOption(String arg) { + private boolean resemblesOption(final String arg) { int count = 0; - for (String optionName : optionName2Field.keySet()) { + for (final String optionName : optionName2Field.keySet()) { for (int i = 0; i < arg.length(); i++) { if (optionName.length() > i && arg.charAt(i) == optionName.charAt(i)) { count++; } else { break; } } } - boolean result = count > 0 && count * 10 >= optionName2Field.size() * 9; // at least one prefix char in common with 9 out of 10 options + final boolean result = count > 0 && count * 10 >= optionName2Field.size() * 9; // at least one prefix char in common with 9 out of 10 options if (tracer.isDebug()) {tracer.debug("%s %s an option: %d matching prefix chars out of %d option names%n", arg, (result ? "resembles" : "doesn't resemble"), count, optionName2Field.size());} return result; } - private void handleUnmatchedArguments(String arg) {Stack args = new Stack(); args.add(arg); handleUnmatchedArguments(args);} - private void handleUnmatchedArguments(Stack args) { + private void handleUnmatchedArguments(final String arg) { + final Stack args = new Stack(); args.add(arg); handleUnmatchedArguments(args);} + private void handleUnmatchedArguments(final Stack args) { while (!args.isEmpty()) { unmatchedArguments.add(args.pop()); } // addAll would give args in reverse order } - private void processRemainderAsPositionalParameters(Collection required, Set initialized, Stack args) throws Exception { + private void processRemainderAsPositionalParameters(final Collection required, final Set initialized, final Stack args) throws Exception { while (!args.empty()) { processPositionalParameter(required, initialized, args); } } - private void processPositionalParameter(Collection required, Set initialized, Stack args) throws Exception { + private void processPositionalParameter(final Collection required, final Set initialized, final Stack args) throws Exception { if (tracer.isDebug()) {tracer.debug("Processing next arg as a positional parameter at index=%d. Remainder=%s%n", position, reverse((Stack) args.clone()));} int consumed = 0; - for (Field positionalParam : positionalParametersFields) { - Range indexRange = Range.parameterIndex(positionalParam); + for (final Field positionalParam : positionalParametersFields) { + final Range indexRange = Range.parameterIndex(positionalParam); if (!indexRange.contains(position)) { continue; } - @SuppressWarnings("unchecked") - Stack argsCopy = (Stack) args.clone(); - Range arity = Range.parameterArity(positionalParam); + @SuppressWarnings("unchecked") final Stack argsCopy = (Stack) args.clone(); + final Range arity = Range.parameterArity(positionalParam); if (tracer.isDebug()) {tracer.debug("Position %d is in index range %s. Trying to assign args to %s, arity=%s%n", position, indexRange, positionalParam, arity);} assertNoMissingParameters(positionalParam, arity.min, argsCopy); - int originalSize = argsCopy.size(); + final int originalSize = argsCopy.size(); applyOption(positionalParam, Parameters.class, arity, false, argsCopy, initialized, "args[" + indexRange + "] at position " + position); - int count = originalSize - argsCopy.size(); + final int count = originalSize - argsCopy.size(); if (count > 0) { required.remove(positionalParam); } consumed = Math.max(consumed, count); } @@ -2130,12 +2129,12 @@ private void processPositionalParameter(Collection required, Set i } } - private void processStandaloneOption(Collection required, - Set initialized, - String arg, - Stack args, - boolean paramAttachedToKey) throws Exception { - Field field = optionName2Field.get(arg); + private void processStandaloneOption(final Collection required, + final Set initialized, + final String arg, + final Stack args, + final boolean paramAttachedToKey) throws Exception { + final Field field = optionName2Field.get(arg); required.remove(field); Range arity = Range.optionArity(field); if (paramAttachedToKey) { @@ -2145,19 +2144,19 @@ private void processStandaloneOption(Collection required, applyOption(field, Option.class, arity, paramAttachedToKey, args, initialized, "option " + arg); } - private void processClusteredShortOptions(Collection required, - Set initialized, - String arg, - Stack args) + private void processClusteredShortOptions(final Collection required, + final Set initialized, + final String arg, + final Stack args) throws Exception { - String prefix = arg.substring(0, 1); + final String prefix = arg.substring(0, 1); String cluster = arg.substring(1); boolean paramAttachedToOption = true; do { if (cluster.length() > 0 && singleCharOption2Field.containsKey(cluster.charAt(0))) { - Field field = singleCharOption2Field.get(cluster.charAt(0)); + final Field field = singleCharOption2Field.get(cluster.charAt(0)); Range arity = Range.optionArity(field); - String argDescription = "option " + prefix + cluster.charAt(0); + final String argDescription = "option " + prefix + cluster.charAt(0); if (tracer.isDebug()) {tracer.debug("Found option '%s%s' in %s: field %s, arity=%s%n", prefix, cluster.charAt(0), arg, field, arity);} required.remove(field); cluster = cluster.length() > 0 ? cluster.substring(1) : ""; @@ -2175,7 +2174,7 @@ private void processClusteredShortOptions(Collection required, if (!empty(cluster)) { args.push(cluster); // interpret remainder as option parameter } - int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription); + final int consumed = applyOption(field, Option.class, arity, paramAttachedToOption, args, initialized, argDescription); // only return if cluster (and maybe more) was consumed, otherwise continue do-while loop if (empty(cluster) || consumed > 0 || args.isEmpty()) { return; @@ -2208,15 +2207,15 @@ private void processClusteredShortOptions(Collection required, } while (true); } - private int applyOption(Field field, - Class annotation, - Range arity, - boolean valueAttachedToOption, - Stack args, - Set initialized, - String argDescription) throws Exception { + private int applyOption(final Field field, + final Class annotation, + final Range arity, + final boolean valueAttachedToOption, + final Stack args, + final Set initialized, + final String argDescription) throws Exception { updateHelpRequested(field); - int length = args.size(); + final int length = args.size(); assertNoMissingParameters(field, arity.min, args); Class cls = field.getType(); @@ -2233,13 +2232,13 @@ private int applyOption(Field field, return applyValueToSingleValuedField(field, arity, args, cls, initialized, argDescription); } - private int applyValueToSingleValuedField(Field field, - Range arity, - Stack args, - Class cls, - Set initialized, - String argDescription) throws Exception { - boolean noMoreValues = args.isEmpty(); + private int applyValueToSingleValuedField(final Field field, + final Range arity, + final Stack args, + final Class cls, + final Set initialized, + final String argDescription) throws Exception { + final boolean noMoreValues = args.isEmpty(); String value = args.isEmpty() ? null : trim(args.pop()); // unquote the value int result = arity.min; // the number or args we need to consume @@ -2253,16 +2252,16 @@ private int applyValueToSingleValuedField(Field field, if (value != null) { args.push(value); // we don't consume the value } - Boolean currentValue = (Boolean) field.get(command); - value = String.valueOf(currentValue == null ? true : !currentValue); // #147 toggle existing boolean value + final Boolean currentValue = (Boolean) field.get(command); + value = String.valueOf(currentValue == null || !currentValue); // #147 toggle existing boolean value } } if (noMoreValues && value == null) { return 0; } - ITypeConverter converter = getTypeConverter(cls, field); - Object newValue = tryConvert(field, -1, converter, value, cls); - Object oldValue = field.get(command); + final ITypeConverter converter = getTypeConverter(cls, field); + final Object newValue = tryConvert(field, -1, converter, value, cls); + final Object oldValue = field.get(command); TraceLevel level = TraceLevel.INFO; String traceMessage = "Setting %s field '%s.%s' to '%5$s' (was '%4$s') for %6$s%n"; if (initialized != null) { @@ -2281,34 +2280,34 @@ private int applyValueToSingleValuedField(Field field, field.set(command, newValue); return result; } - private int applyValuesToMapField(Field field, - Class annotation, - Range arity, - Stack args, - Class cls, - String argDescription) throws Exception { - Class[] classes = getTypeAttribute(field); + private int applyValuesToMapField(final Field field, + final Class annotation, + final Range arity, + final Stack args, + final Class cls, + final String argDescription) throws Exception { + final Class[] classes = getTypeAttribute(field); if (classes.length < 2) { throw new ParameterException(CommandLine.this, "Field " + field + " needs two types (one for the map key, one for the value) but only has " + classes.length + " types configured."); } - ITypeConverter keyConverter = getTypeConverter(classes[0], field); - ITypeConverter valueConverter = getTypeConverter(classes[1], field); + final ITypeConverter keyConverter = getTypeConverter(classes[0], field); + final ITypeConverter valueConverter = getTypeConverter(classes[1], field); Map result = (Map) field.get(command); if (result == null) { result = createMap(cls); field.set(command, result); } - int originalSize = result.size(); + final int originalSize = result.size(); consumeMapArguments(field, arity, args, classes, keyConverter, valueConverter, result, argDescription); return result.size() - originalSize; } - private void consumeMapArguments(Field field, - Range arity, - Stack args, - Class[] classes, - ITypeConverter keyConverter, - ITypeConverter valueConverter, - Map result, - String argDescription) throws Exception { + private void consumeMapArguments(final Field field, + final Range arity, + final Stack args, + final Class[] classes, + final ITypeConverter keyConverter, + final ITypeConverter valueConverter, + final Map result, + final String argDescription) throws Exception { // first do the arity.min mandatory parameters for (int i = 0; i < arity.min; i++) { consumeOneMapArgument(field, arity, args, classes, keyConverter, valueConverter, result, i, argDescription); @@ -2324,19 +2323,19 @@ private void consumeMapArguments(Field field, } } - private void consumeOneMapArgument(Field field, - Range arity, - Stack args, - Class[] classes, - ITypeConverter keyConverter, ITypeConverter valueConverter, - Map result, - int index, - String argDescription) throws Exception { - String[] values = split(trim(args.pop()), field); - for (String value : values) { - String[] keyValue = value.split("="); + private void consumeOneMapArgument(final Field field, + final Range arity, + final Stack args, + final Class[] classes, + final ITypeConverter keyConverter, final ITypeConverter valueConverter, + final Map result, + final int index, + final String argDescription) throws Exception { + final String[] values = split(trim(args.pop()), field); + for (final String value : values) { + final String[] keyValue = value.split("="); if (keyValue.length < 2) { - String splitRegex = splitRegex(field); + final String splitRegex = splitRegex(field); if (splitRegex.length() == 0) { throw new ParameterException(CommandLine.this, "Value for option " + optionDescription("", field, 0) + " should be in KEY=VALUE format but was " + value); @@ -2345,44 +2344,44 @@ private void consumeOneMapArgument(Field field, 0) + " should be in KEY=VALUE[" + splitRegex + "KEY=VALUE]... format but was " + value); } } - Object mapKey = tryConvert(field, index, keyConverter, keyValue[0], classes[0]); - Object mapValue = tryConvert(field, index, valueConverter, keyValue[1], classes[1]); + final Object mapKey = tryConvert(field, index, keyConverter, keyValue[0], classes[0]); + final Object mapValue = tryConvert(field, index, valueConverter, keyValue[1], classes[1]); result.put(mapKey, mapValue); if (tracer.isInfo()) {tracer.info("Putting [%s : %s] in %s<%s, %s> field '%s.%s' for %s%n", String.valueOf(mapKey), String.valueOf(mapValue), result.getClass().getSimpleName(), classes[0].getSimpleName(), classes[1].getSimpleName(), field.getDeclaringClass().getSimpleName(), field.getName(), argDescription);} } } - private void checkMaxArityExceeded(Range arity, int remainder, Field field, String[] values) { + private void checkMaxArityExceeded(final Range arity, final int remainder, final Field field, final String[] values) { if (values.length <= remainder) { return; } - String desc = arity.max == remainder ? "" + remainder : arity + ", remainder=" + remainder; + final String desc = arity.max == remainder ? "" + remainder : arity + ", remainder=" + remainder; throw new MaxValuesforFieldExceededException(CommandLine.this, optionDescription("", field, -1) + " max number of values (" + arity.max + ") exceeded: remainder is " + remainder + " but " + values.length + " values were specified: " + Arrays.toString(values)); } - private int applyValuesToArrayField(Field field, - Class annotation, - Range arity, - Stack args, - Class cls, - String argDescription) throws Exception { - Object existing = field.get(command); - int length = existing == null ? 0 : Array.getLength(existing); - Class type = getTypeAttribute(field)[0]; - List converted = consumeArguments(field, annotation, arity, args, type, length, argDescription); - List newValues = new ArrayList(); + private int applyValuesToArrayField(final Field field, + final Class annotation, + final Range arity, + final Stack args, + final Class cls, + final String argDescription) throws Exception { + final Object existing = field.get(command); + final int length = existing == null ? 0 : Array.getLength(existing); + final Class type = getTypeAttribute(field)[0]; + final List converted = consumeArguments(field, annotation, arity, args, type, length, argDescription); + final List newValues = new ArrayList(); for (int i = 0; i < length; i++) { newValues.add(Array.get(existing, i)); } - for (Object obj : converted) { + for (final Object obj : converted) { if (obj instanceof Collection) { newValues.addAll((Collection) obj); } else { newValues.add(obj); } } - Object array = Array.newInstance(type, newValues.size()); + final Object array = Array.newInstance(type, newValues.size()); field.set(command, array); for (int i = 0; i < newValues.size(); i++) { Array.set(array, i, newValues.get(i)); @@ -2391,21 +2390,21 @@ private int applyValuesToArrayField(Field field, } @SuppressWarnings("unchecked") - private int applyValuesToCollectionField(Field field, - Class annotation, - Range arity, - Stack args, - Class cls, - String argDescription) throws Exception { + private int applyValuesToCollectionField(final Field field, + final Class annotation, + final Range arity, + final Stack args, + final Class cls, + final String argDescription) throws Exception { Collection collection = (Collection) field.get(command); - Class type = getTypeAttribute(field)[0]; - int length = collection == null ? 0 : collection.size(); - List converted = consumeArguments(field, annotation, arity, args, type, length, argDescription); + final Class type = getTypeAttribute(field)[0]; + final int length = collection == null ? 0 : collection.size(); + final List converted = consumeArguments(field, annotation, arity, args, type, length, argDescription); if (collection == null) { collection = createCollection(cls); field.set(command, collection); } - for (Object element : converted) { + for (final Object element : converted) { if (element instanceof Collection) { collection.addAll((Collection) element); } else { @@ -2415,14 +2414,14 @@ private int applyValuesToCollectionField(Field field, return converted.size(); } - private List consumeArguments(Field field, - Class annotation, - Range arity, - Stack args, - Class type, - int originalSize, - String argDescription) throws Exception { - List result = new ArrayList(); + private List consumeArguments(final Field field, + final Class annotation, + final Range arity, + final Stack args, + final Class type, + final int originalSize, + final String argDescription) throws Exception { + final List result = new ArrayList(); // first do the arity.min mandatory parameters for (int i = 0; i < arity.min; i++) { @@ -2440,16 +2439,16 @@ private List consumeArguments(Field field, return result; } - private int consumeOneArgument(Field field, - Range arity, - Stack args, - Class type, - List result, + private int consumeOneArgument(final Field field, + final Range arity, + final Stack args, + final Class type, + final List result, int index, - int originalSize, - String argDescription) throws Exception { - String[] values = split(trim(args.pop()), field); - ITypeConverter converter = getTypeConverter(type, field); + final int originalSize, + final String argDescription) throws Exception { + final String[] values = split(trim(args.pop()), field); + final ITypeConverter converter = getTypeConverter(type, field); for (int j = 0; j < values.length; j++) { result.add(tryConvert(field, index, converter, values[j], type)); @@ -2465,13 +2464,13 @@ private int consumeOneArgument(Field field, return ++index; } - private String splitRegex(Field field) { + private String splitRegex(final Field field) { if (field.isAnnotationPresent(Option.class)) { return field.getAnnotation(Option.class).split(); } if (field.isAnnotationPresent(Parameters.class)) { return field.getAnnotation(Parameters.class).split(); } return ""; } - private String[] split(String value, Field field) { - String regex = splitRegex(field); + private String[] split(final String value, final Field field) { + final String regex = splitRegex(field); return regex.length() == 0 ? new String[] {value} : value.split(regex); } @@ -2481,7 +2480,7 @@ private String[] split(String value, Field field) { * @param arg the string to determine whether it is an option or not * @return true if it is an option, false otherwise */ - private boolean isOption(String arg) { + private boolean isOption(final String arg) { if ("--".equals(arg)) { return true; } @@ -2489,7 +2488,7 @@ private boolean isOption(String arg) { if (optionName2Field.containsKey(arg)) { // -v or -f or --file (not attached to param or other option) return true; } - int separatorIndex = arg.indexOf(separator); + final int separatorIndex = arg.indexOf(separator); if (separatorIndex > 0) { // -f=FILE or --file==FILE (attached to param via separator) if (optionName2Field.containsKey(arg.substring(0, separatorIndex))) { return true; @@ -2497,33 +2496,33 @@ private boolean isOption(String arg) { } return (arg.length() > 2 && arg.startsWith("-") && singleCharOption2Field.containsKey(arg.charAt(1))); } - private Object tryConvert(Field field, int index, ITypeConverter converter, String value, Class type) + private Object tryConvert(final Field field, final int index, final ITypeConverter converter, final String value, final Class type) throws Exception { try { return converter.convert(value); - } catch (TypeConversionException ex) { + } catch (final TypeConversionException ex) { throw new ParameterException(CommandLine.this, ex.getMessage() + optionDescription(" for ", field, index)); - } catch (Exception other) { - String desc = optionDescription(" for ", field, index) + ": " + other; + } catch (final Exception other) { + final String desc = optionDescription(" for ", field, index) + ": " + other; throw new ParameterException(CommandLine.this, "Could not convert '" + value + "' to " + type.getSimpleName() + desc, other); } } - private String optionDescription(String prefix, Field field, int index) { - Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer(); + private String optionDescription(final String prefix, final Field field, final int index) { + final Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer(); String desc = ""; if (field.isAnnotationPresent(Option.class)) { desc = prefix + "option '" + field.getAnnotation(Option.class).names()[0] + "'"; if (index >= 0) { - Range arity = Range.optionArity(field); + final Range arity = Range.optionArity(field); if (arity.max > 1) { desc += " at index " + index; } desc += " (" + labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.emptyList()) + ")"; } } else if (field.isAnnotationPresent(Parameters.class)) { - Range indexRange = Range.parameterIndex(field); - Text label = labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.emptyList()); + final Range indexRange = Range.parameterIndex(field); + final Text label = labelRenderer.renderParameterLabel(field, Help.Ansi.OFF, Collections.emptyList()); desc = prefix + "positional parameter at index " + indexRange + " (" + label + ")"; } return desc; @@ -2531,19 +2530,19 @@ private String optionDescription(String prefix, Field field, int index) { private boolean isAnyHelpRequested() { return isHelpRequested || versionHelpRequested || usageHelpRequested; } - private void updateHelpRequested(Field field) { + private void updateHelpRequested(final Field field) { if (field.isAnnotationPresent(Option.class)) { isHelpRequested |= is(field, "help", field.getAnnotation(Option.class).help()); CommandLine.this.versionHelpRequested |= is(field, "versionHelp", field.getAnnotation(Option.class).versionHelp()); CommandLine.this.usageHelpRequested |= is(field, "usageHelp", field.getAnnotation(Option.class).usageHelp()); } } - private boolean is(Field f, String description, boolean value) { + private boolean is(final Field f, final String description, final boolean value) { if (value) { if (tracer.isInfo()) {tracer.info("Field '%s.%s' has '%s' annotation: not validating required fields%n", f.getDeclaringClass().getSimpleName(), f.getName(), description); }} return value; } @SuppressWarnings("unchecked") - private Collection createCollection(Class collectionClass) throws Exception { + private Collection createCollection(final Class collectionClass) throws Exception { if (collectionClass.isInterface()) { if (List.class.isAssignableFrom(collectionClass)) { return new ArrayList(); @@ -2559,51 +2558,45 @@ private Collection createCollection(Class collectionClass) throws Exc // custom Collection implementation class must have default constructor return (Collection) collectionClass.newInstance(); } - private Map createMap(Class mapClass) throws Exception { + private Map createMap(final Class mapClass) throws Exception { try { // if it is an implementation class, instantiate it return (Map) mapClass.newInstance(); - } catch (Exception ignored) {} + } catch (final Exception ignored) {} return new LinkedHashMap(); } - private ITypeConverter getTypeConverter(final Class type, Field field) { - ITypeConverter result = converterRegistry.get(type); + private ITypeConverter getTypeConverter(final Class type, final Field field) { + final ITypeConverter result = converterRegistry.get(type); if (result != null) { return result; } if (type.isEnum()) { - return new ITypeConverter() { - @Override - @SuppressWarnings("unchecked") - public Object convert(String value) throws Exception { - return Enum.valueOf((Class) type, value); - } - }; + return (ITypeConverter) value -> Enum.valueOf((Class) type, value); } throw new MissingTypeConverterException(CommandLine.this, "No TypeConverter registered for " + type.getName() + " of field " + field); } - private void assertNoMissingParameters(Field field, int arity, Stack args) { + private void assertNoMissingParameters(final Field field, final int arity, final Stack args) { if (arity > args.size()) { if (arity == 1) { if (field.isAnnotationPresent(Option.class)) { throw new MissingParameterException(CommandLine.this, "Missing required parameter for " + optionDescription("", field, 0)); } - Range indexRange = Range.parameterIndex(field); - Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer(); + final Range indexRange = Range.parameterIndex(field); + final Help.IParamLabelRenderer labelRenderer = Help.createMinimalParamLabelRenderer(); String sep = ""; - String names = ""; + final StringBuilder names = new StringBuilder(); int count = 0; for (int i = indexRange.min; i < positionalParametersFields.size(); i++) { if (Range.parameterArity(positionalParametersFields.get(i)).min > 0) { - names += sep + labelRenderer.renderParameterLabel(positionalParametersFields.get(i), - Help.Ansi.OFF, Collections.emptyList()); + names.append(sep).append(labelRenderer.renderParameterLabel(positionalParametersFields.get(i), + Help.Ansi.OFF, Collections.emptyList())); sep = ", "; count++; } } String msg = "Missing required parameter"; - Range paramArity = Range.parameterArity(field); + final Range paramArity = Range.parameterArity(field); if (paramArity.isVariable) { msg += "s at positions " + indexRange + ": "; } else { @@ -2619,11 +2612,11 @@ private void assertNoMissingParameters(Field field, int arity, Stack arg " requires at least " + arity + " values, but only " + args.size() + " were specified: " + reverse(args)); } } - private String trim(String value) { + private String trim(final String value) { return unquote(value); } - private String unquote(String value) { + private String unquote(final String value) { return value == null ? null : (value.length() > 1 && value.startsWith("\"") && value.endsWith("\"")) @@ -2633,8 +2626,8 @@ private String unquote(String value) { } private static class PositionalParametersSorter implements Comparator { @Override - public int compare(Field o1, Field o2) { - int result = Range.parameterIndex(o1).compareTo(Range.parameterIndex(o2)); + public int compare(final Field o1, final Field o2) { + final int result = Range.parameterIndex(o1).compareTo(Range.parameterIndex(o2)); return (result == 0) ? Range.parameterArity(o1).compareTo(Range.parameterArity(o2)) : result; } } @@ -2647,25 +2640,25 @@ static class PathConverter implements ITypeConverter { } static class StringConverter implements ITypeConverter { @Override - public String convert(String value) { return value; } + public String convert(final String value) { return value; } } static class StringBuilderConverter implements ITypeConverter { @Override - public StringBuilder convert(String value) { return new StringBuilder(value); } + public StringBuilder convert(final String value) { return new StringBuilder(value); } } static class CharSequenceConverter implements ITypeConverter { @Override - public String convert(String value) { return value; } + public String convert(final String value) { return value; } } /** Converts text to a {@code Byte} by delegating to {@link Byte#valueOf(String)}.*/ static class ByteConverter implements ITypeConverter { @Override - public Byte convert(String value) { return Byte.valueOf(value); } + public Byte convert(final String value) { return Byte.valueOf(value); } } /** Converts {@code "true"} or {@code "false"} to a {@code Boolean}. Other values result in a ParameterException.*/ static class BooleanConverter implements ITypeConverter { @Override - public Boolean convert(String value) { + public Boolean convert(final String value) { if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { return Boolean.parseBoolean(value); } else { @@ -2675,7 +2668,7 @@ public Boolean convert(String value) { } static class CharacterConverter implements ITypeConverter { @Override - public Character convert(String value) { + public Character convert(final String value) { if (value.length() > 1) { throw new TypeConversionException("'" + value + "' is not a single character"); } @@ -2685,45 +2678,45 @@ public Character convert(String value) { /** Converts text to a {@code Short} by delegating to {@link Short#valueOf(String)}.*/ static class ShortConverter implements ITypeConverter { @Override - public Short convert(String value) { return Short.valueOf(value); } + public Short convert(final String value) { return Short.valueOf(value); } } /** Converts text to an {@code Integer} by delegating to {@link Integer#valueOf(String)}.*/ static class IntegerConverter implements ITypeConverter { @Override - public Integer convert(String value) { return Integer.valueOf(value); } + public Integer convert(final String value) { return Integer.valueOf(value); } } /** Converts text to a {@code Long} by delegating to {@link Long#valueOf(String)}.*/ static class LongConverter implements ITypeConverter { @Override - public Long convert(String value) { return Long.valueOf(value); } + public Long convert(final String value) { return Long.valueOf(value); } } static class FloatConverter implements ITypeConverter { @Override - public Float convert(String value) { return Float.valueOf(value); } + public Float convert(final String value) { return Float.valueOf(value); } } static class DoubleConverter implements ITypeConverter { @Override - public Double convert(String value) { return Double.valueOf(value); } + public Double convert(final String value) { return Double.valueOf(value); } } static class FileConverter implements ITypeConverter { @Override - public File convert(String value) { return new File(value); } + public File convert(final String value) { return new File(value); } } static class URLConverter implements ITypeConverter { @Override - public URL convert(String value) throws MalformedURLException { return new URL(value); } + public URL convert(final String value) throws MalformedURLException { return new URL(value); } } static class URIConverter implements ITypeConverter { @Override - public URI convert(String value) throws URISyntaxException { return new URI(value); } + public URI convert(final String value) throws URISyntaxException { return new URI(value); } } /** Converts text in {@code yyyy-mm-dd} format to a {@code java.util.Date}. ParameterException on failure. */ static class ISO8601DateConverter implements ITypeConverter { @Override - public Date convert(String value) { + public Date convert(final String value) { try { return new SimpleDateFormat("yyyy-MM-dd").parse(value); - } catch (ParseException e) { + } catch (final ParseException e) { throw new TypeConversionException("'" + value + "' is not a yyyy-MM-dd date"); } } @@ -2732,7 +2725,7 @@ public Date convert(String value) { * {@code HH:mm:ss.SSS}, {@code HH:mm:ss,SSS}. Other formats result in a ParameterException. */ static class ISO8601TimeConverter implements ITypeConverter
    "; - - @Test - public void testDefaultContentType() { - final HtmlLayout layout = HtmlLayout.createDefaultLayout(); - assertEquals("text/html; charset=UTF-8", layout.getContentType()); - } - - @Test - public void testContentType() { - final HtmlLayout layout = HtmlLayout.newBuilder() - .withContentType("text/html; charset=UTF-16") - .build(); - assertEquals("text/html; charset=UTF-16", layout.getContentType()); - // TODO: make sure this following bit works as well -// assertEquals(Charset.forName("UTF-16"), layout.getCharset()); - } - - @Test - public void testDefaultCharset() { - final HtmlLayout layout = HtmlLayout.createDefaultLayout(); - assertEquals(StandardCharsets.UTF_8, layout.getCharset()); - } - - /** - * Test case for MDC conversion pattern. - */ - @Test - public void testLayoutIncludeLocationNo() throws Exception { - testLayout(false); - } - - @Test - public void testLayoutIncludeLocationYes() throws Exception { - testLayout(true); - } - - private void testLayout(final boolean includeLocation) throws Exception { - final Map appenders = root.getAppenders(); - for (final Appender appender : appenders.values()) { - root.removeAppender(appender); - } - // set up appender - final HtmlLayout layout = HtmlLayout.newBuilder() - .withLocationInfo(includeLocation) - .build(); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - // output starting message - root.debug("starting mdc pattern test"); - - root.debug("empty mdc"); - - root.debug("First line\nSecond line"); - - ThreadContext.put("key1", "value1"); - ThreadContext.put("key2", "value2"); - - root.debug("filled mdc"); - - ThreadContext.remove("key1"); - ThreadContext.remove("key2"); - - root.error("finished mdc pattern test", new NullPointerException("test")); - - appender.stop(); - - final List list = appender.getMessages(); - final StringBuilder sb = new StringBuilder(); - for (final String string : list) { - sb.append(string); - } - final String html = sb.toString(); - assertTrue("Incorrect number of lines. Require at least 85 " + list.size(), list.size() > 85); - final String string = list.get(3); - assertTrue("Incorrect header: " + string, string.equals("")); - assertTrue("Incorrect title", list.get(4).equals("Log4j Log Messages")); - assertTrue("Incorrect footer", list.get(list.size() - 1).equals("")); - if (includeLocation) { - assertTrue("Incorrect multiline", list.get(50).equals(multiLine)); - assertTrue("Missing location", html.contains("HtmlLayoutTest.java:")); - assertTrue("Incorrect body", list.get(71).equals(body)); - } else { - assertFalse("Location should not be in the output table", html.contains(" + + + + +|base64 +|Base64 encoded data. The format is `${base64:Base64_encoded_data}`. +For example: `${base64:SGVsbG8gV29ybGQhCg==}` yields `Hello World!`. + +|bundle +|Resource bundle. The format is `${bundle:BundleName:BundleKey}`. +The bundle name follows package naming conventions, for example: +`${bundle:com.domain.Messages:MyKey}`. + +|ctx +|Thread Context Map (MDC) + +|date +|Inserts the current date and/or time using the specified format + +|env +|System environment variables. The formats are `${env:ENV_NAME}` and `${env:ENV_NAME:-default_value}`. + +|jndi +|A value set in the default JNDI Context. + +|jvmrunargs +|A JVM input argument accessed through JMX, but not a main argument; see +https://docs.oracle.com/javase/6/docs/api/java/lang/management/RuntimeMXBean.html#getInputArguments--[RuntimeMXBean.getInputArguments()]. +Not available on Android. + +|log4j +|Log4j configuration properties. The expressions +`${log4j:configLocation}` and `${log4j:configParentLocation}` +respectively provide the absolute path to the log4j configuration file +and its parent folder. + +|main +|A value set with +../log4j-core/apidocs/org/apache/logging/log4j/core/lookup/MapLookup.html#setMainArguments-java.lang.String:A-[MapLookup.setMainArguments(String[])] + +|map +|A value from a MapMessage + +|sd +|A value from a StructuredDataMessage. The key "id" will return the +name of the StructuredDataId without the enterprise number. The key +"type" will return the message type. Other keys will retrieve individual +elements from the Map. + +|sys +|System properties. The formats are `${sys:some.property}` and +`${sys:some.property:-default_value}`. +|=== + +[#DefaultProperties] +== Default Properties +A default property map can be declared in the configuration file by placing a Properties +element directly after the Configuration element and before any Loggers, Filters, +Appenders, etc. are declared. If the value cannot be located in the specified lookup the +value in the default property map will be used. The default map is pre-populated with a value +for "hostName" that is the current system's host name or IP address and +the "contextName" with is the value of the current logging context. See many places +a Properties element is used in this section for examples. + +Default properties may also be specified in the Lookup by using the syntax `${lookupName:key:-defaultValue}`. +In some cases the key might contain a leading '-'. When this is the case an escape character must be +included, such as ``${main:\--file:-app.properties}`. This would use the +`MainMapLookup` for a key named `--file`. If the key is not found then +app.properties would be used as the default value. + +[#EnablingMessagePatternLookups] +== Enabling Message Pattern Lookups +A message is processed (by default) without using lookups, for example if you defined +`FOO_BAR`, then `logger.info("${foo.bar}")` will output `${foo.bar}` instead of `FOO_BAR`. +You could enable message pattern lookups by defining the message pattern using %m{lookups}. + +[#RuntimeLookup] +== Lookup Variables with Multiple Leading '$' Characters + +An interesting feature of StrLookup processing is that when a variable +reference is declared with multiple leading '$' characters each time the +variable is resolved the leading '$' is simply removed. In the previous +example the "Routes" element is capable of resolving the variable at +runtime. To allow this the prefix value is specified as a variable with +two leading '$' characters. When the configuration file is first +processed the first '$' character is simply removed. Thus, when the +Routes element is evaluated at runtime it is the variable declaration +"$\{sd:type}" which causes the event to be inspected for a +StructuredDataMessage and if one is present the value of its type +attribute to be used as the routing key. Not all elements support +resolving variables at runtime. Components that do will specifically +call that out in their documentation. + +If no value is found for the key in the Lookup associated with the +prefix then the value associated with the key in the properties +declaration in the configuration file will be used. If no value is found +the variable declaration will be returned as the value. Default values +may be declared in the configuration by doing: + +[source,xml] +---- + + + + Audit + + ... + +---- + +_As a footnote, it is worth pointing out that the variables in the +RollingFile appender declaration will also not be evaluated when the +configuration is processed. This is simply because the resolution of the +whole RollingFile element is deferred until a match occurs. See +link:appenders.html#RoutingAppender[RoutingAppender] for more +information._ + +[#Scripts] +== Scripts + +Log4j provides support for +https://docs.oracle.com/javase/6/docs/technotes/guides/scripting/[JSR +223] scripting languages to be used in some of its components. Any +language that provides support for the JSR 223 scripting engine may be +used. A list of the languages and bindings for them can be found at the +https://java.net/projects/scripting/sources/svn/show/trunk/engines[Scripting +Engine] web site. However, some of the languages listed there, such as +JavaScript, Groovy and Beanshell, directly support the JSR 223 scripting +framework and only require that the jars for that language be installed. + +As of Log4j 2.17.2 the languages to be supported must be specified as a comma separated list in the +`log4j2.Script.enableLanguages` system property. + +The components that support using scripts do so by allowing a ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +If the status attribute on the Configuration element is set to DEBUG the +list of script engines currently installed and their attributes will be +listed. Although some engines may say they are not thread safe, Log4j +takes steps to insure that the scripts will run in a thread-safe manner +if the engine advertises that it is not thread safe. + +.... +2015-09-27 16:13:22,925 main DEBUG Installed script engines +2015-09-27 16:13:22,963 main DEBUG AppleScriptEngine Version: 1.1, Language: AppleScript, Threading: Not Thread Safe, + Compile: false, Names: {AppleScriptEngine, AppleScript, OSA} +2015-09-27 16:13:22,983 main DEBUG Groovy Scripting Engine Version: 2.0, Language: Groovy, Threading: MULTITHREADED, + Compile: true, Names: {groovy, Groovy} +2015-09-27 16:13:23,030 main DEBUG BeanShell Engine Version: 1.0, Language: BeanShell, Threading: MULTITHREADED, + Compile: true, Names: {beanshell, bsh, java} +2015-09-27 16:13:23,039 main DEBUG Mozilla Rhino Version: 1.7 release 3 PRERELEASE, Language: ECMAScript, Threading: MULTITHREADED, + Compile: true, Names: {js, rhino, JavaScript, javascript, ECMAScript, ecmascript} +.... + +When the scripts are executed they will be provided with a set of +variables that should allow them to accomplish whatever task they are +expected to perform. See the documentation for the individual components +for the list of variables that are available to the script. + +The components that support scripting expect a return value to be passed +back to the calling Java code. This is not a problem for several of the +scripting languages, but Javascript does not allow a return statement +unless it is within a function. However, Javascript will return the +value of the last statement executed in the script. As a consequence, +code such as that shown below will result in the desired behavior. + +[source,javascript] +---- +var result; +if (logEvent.getLoggerName().equals("JavascriptNoLocation")) { + result = "NoLocation"; +} else if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) { + result = "Flow"; +} +result; +---- + +=== A special note on Beanshell + +JSR 223 scripting engines are supposed to identify that they support the +Compilable interface if they support compiling their scripts. Beanshell +does this. However, whenever the compile method is called it throws an +Error (not an Exception). Log4j catches this but will log the warning +shown below for each Beanshell script when it tries to compile them. All +Beanshell scripts will then be interpreted on each execution. + +.... +2015-09-27 16:13:23,095 main DEBUG Script BeanShellSelector is compilable +2015-09-27 16:13:23,096 main WARN Error compiling script java.lang.Error: unimplemented + at bsh.engine.BshScriptEngine.compile(BshScriptEngine.java:175) + at bsh.engine.BshScriptEngine.compile(BshScriptEngine.java:154) + at org.apache.logging.log4j.core.script.ScriptManager$MainScriptRunner.(ScriptManager.java:125) + at org.apache.logging.log4j.core.script.ScriptManager.addScript(ScriptManager.java:94) + +.... + +[#XInclude] +== XInclude + +XML configuration files can include other files with +http://www.xml.com/lpt/a/1009[XInclude]. Here is an example log4j2.xml +file that includes two other files: + +.log4j2.xml +[source,xml] +---- + + + + xinclude-demo.log + + + + + +---- + +.log4j-xinclude-appenders.xml +[source,xml] +---- + + + + + + + + %d %p %C{1.} [%t] %m%n + + + +---- + +.log4j-xinclude-loggers.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + +---- + +[#CompositeConfiguration] +== Composite Configuration + +Log4j allows multiple configuration files to be used by specifying them +as a list of comma separated file paths on log4j.configurationFile or, +when using URLs, by adding secondary configuration locations as query +parameters named "override". The merge logic can be controlled by specifying +a class that implements the MergeStrategy interface on the log4j.mergeStrategy +property. The default merge strategy will merge the files using the following rules: + +1. The global configuration attributes are aggregated with those in +later configurations replacing those in previous configurations, with +the exception that the highest status level and the lowest +monitorInterval greater than 0 will be used. +2. Properties from all configurations are aggregated. Duplicate +properties replace those in previous configurations. +3. Filters are aggregated under a CompositeFilter if more than one +Filter is defined. Since Filters are not named duplicates may be +present. +4. Scripts and ScriptFile references are aggregated. Duplicate +definitions replace those in previous configurations. +5. Appenders are aggregated. Appenders with the same name are replaced +by those in later configurations, including all of the Appender's +subcomponents. +6. Loggers are all aggregated. Logger attributes are individually +merged with duplicates being replaced by those in later configurations. +Appender references on a Logger are aggregated with duplicates being +replaced by those in later configurations. Filters on a Logger are +aggregated under a CompositeFilter if more than one Filter is defined. +Since Filters are not named duplicates may be present. Filters under +Appender references included or discarded depending on whether their +parent Appender reference is kept or discarded. + +[#StatusMessages] +== Status Messages + +**** +*Troubleshooting tip for the impatient:* + +From log4j-2.9 onward, log4j2 will print all internal logging to the +console if system property `log4j2.debug` is either defined empty or its value +equals to `true` (ignoring case). + +Prior to log4j-2.9, there are two places where internal logging can be +controlled: + +* Before a configuration is found, status logger level can be controlled +with system property +`org.apache.logging.log4j.simplelog.StatusLogger.level`. +* After a configuration is found, status logger level can be controlled +in the configuration file with the "status" attribute, for example: +``. +**** + +Just as it is desirable to be able to diagnose problems in applications, +it is frequently necessary to be able to diagnose problems in the +logging configuration or in the configured components. Since logging has +not been configured, "normal" logging cannot be used during +initialization. In addition, normal logging within appenders could +create infinite recursion which Log4j will detect and cause the +recursive events to be ignored. To accomodate this need, the Log4j 2 API +includes a +link:../log4j-api/apidocs/org/apache/logging/log4j/status/StatusLogger.html[`StatusLogger`]. +Components declare an instance of the StatusLogger similar to: + +[source,java] +---- +protected final static Logger logger = StatusLogger.getLogger(); +---- + +Since StatusLogger implements the Log4j 2 API's Logger interface, all +the normal Logger methods may be used. + +When configuring Log4j it is sometimes necessary to view the generated +status events. This can be accomplished by adding the status attribute +to the configuration element or a default value can be provided by +setting the "Log4jDefaultStatusLevel" system property. Valid values of +the status attribute are "trace", "debug", "info", "warn", "error" and +"fatal". The following configuration has the status attribute set to +debug. + +[source,xml] +---- + + + + target/rolling1/rollingtest-$${sd:type}.log + + + + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + + + + + +---- + +During startup this configuration produces: + +.... +2011-11-23 17:08:00,769 DEBUG Generated plugins in 0.003374000 seconds +2011-11-23 17:08:00,789 DEBUG Calling createProperty on class org.apache.logging.log4j.core.config.Property for element property with params(name="filename", value="target/rolling1/rollingtest-${sd:type}.log") +2011-11-23 17:08:00,792 DEBUG Calling configureSubstitutor on class org.apache.logging.log4j.core.config.PropertiesPlugin for element properties with params(properties={filename=target/rolling1/rollingtest-${sd:type}.log}) +2011-11-23 17:08:00,794 DEBUG Generated plugins in 0.001362000 seconds +2011-11-23 17:08:00,797 DEBUG Calling createFilter on class org.apache.logging.log4j.core.filter.ThresholdFilter for element ThresholdFilter with params(level="debug", onMatch="null", onMismatch="null") +2011-11-23 17:08:00,800 DEBUG Calling createLayout on class org.apache.logging.log4j.core.layout.PatternLayout for element PatternLayout with params(pattern="%m%n", Configuration(RoutingTest), null, charset="null") +2011-11-23 17:08:00,802 DEBUG Generated plugins in 0.001349000 seconds +2011-11-23 17:08:00,804 DEBUG Calling createAppender on class org.apache.logging.log4j.core.appender.ConsoleAppender for element Console with params(PatternLayout(%m%n), null, target="null", name="STDOUT", ignoreExceptions="null") +2011-11-23 17:08:00,804 DEBUG Calling createFilter on class org.apache.logging.log4j.core.filter.ThresholdFilter for element ThresholdFilter with params(level="debug", onMatch="null", onMismatch="null") +2011-11-23 17:08:00,813 DEBUG Calling createRoute on class org.apache.logging.log4j.core.appender.routing.Route for element Route with params(AppenderRef="null", key="null", Node=Route) +2011-11-23 17:08:00,823 DEBUG Calling createRoute on class org.apache.logging.log4j.core.appender.routing.Route for element Route with params(AppenderRef="STDOUT", key="Audit", Node=Route) +2011-11-23 17:08:00,825 DEBUG Calling createRoutes on class org.apache.logging.log4j.core.appender.routing.Routes for element Routes with params(pattern="${sd:type}", routes={Route(type=dynamic default), Route(type=static Reference=STDOUT key='Audit')}) +2011-11-23 17:08:00,827 DEBUG Calling createAppender on class org.apache.logging.log4j.core.appender.routing.RoutingAppender for element Routing with params(name="Routing", ignoreExceptions="null", Routes({Route(type=dynamic default),Route(type=static Reference=STDOUT key='Audit')}), Configuration(RoutingTest), null, null) +2011-11-23 17:08:00,827 DEBUG Calling createAppenders on class org.apache.logging.log4j.core.config.AppendersPlugin for element appenders with params(appenders={STDOUT, Routing}) +2011-11-23 17:08:00,828 DEBUG Calling createAppenderRef on class org.apache.logging.log4j.core.config.plugins.AppenderRefPlugin for element AppenderRef with params(ref="Routing") +2011-11-23 17:08:00,829 DEBUG Calling createLogger on class org.apache.logging.log4j.core.config.LoggerConfig for element logger with params(additivity="false", level="info", name="EventLogger", AppenderRef={Routing}, null) +2011-11-23 17:08:00,830 DEBUG Calling createAppenderRef on class org.apache.logging.log4j.core.config.plugins.AppenderRefPlugin for element AppenderRef with params(ref="STDOUT") +2011-11-23 17:08:00,831 DEBUG Calling createLogger on class org.apache.logging.log4j.core.config.LoggerConfig$RootLogger for element root with params(additivity="null", level="error", AppenderRef={STDOUT}, null) +2011-11-23 17:08:00,833 DEBUG Calling createLoggers on class org.apache.logging.log4j.core.config.LoggersPlugin for element loggers with params(loggers={EventLogger, root}) +2011-11-23 17:08:00,834 DEBUG Reconfiguration completed +2011-11-23 17:08:00,846 DEBUG Calling createLayout on class org.apache.logging.log4j.core.layout.PatternLayout for element PatternLayout with params(pattern="%d %p %c{1.} [%t] %m%n", Configuration(RoutingTest), null, charset="null") +2011-11-23 17:08:00,849 DEBUG Calling createPolicy on class org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy for element SizeBasedTriggeringPolicy with params(size="500") +2011-11-23 17:08:00,851 DEBUG Calling createAppender on class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile with params(fileName="target/rolling1/rollingtest-Unknown.log", filePattern="target/rolling1/test1-Unknown.%i.log.gz", append="null", name="Rolling-Unknown", bufferedIO="null", immediateFlush="null", SizeBasedTriggeringPolicy(SizeBasedTriggeringPolicy(size=500)), null, PatternLayout(%d %p %c{1.} [%t] %m%n), null, ignoreExceptions="null") +2011-11-23 17:08:00,858 DEBUG Generated plugins in 0.002014000 seconds +2011-11-23 17:08:00,889 DEBUG Reconfiguration started for context sun.misc.Launcher$AppClassLoader@37b90b39 +2011-11-23 17:08:00,890 DEBUG Generated plugins in 0.001355000 seconds +2011-11-23 17:08:00,959 DEBUG Generated plugins in 0.001239000 seconds +2011-11-23 17:08:00,961 DEBUG Generated plugins in 0.001197000 seconds +2011-11-23 17:08:00,965 WARN No Loggers were configured, using default +2011-11-23 17:08:00,976 DEBUG Reconfiguration completed +.... + +If the status attribute is set to error then only error messages will be +written to the console. This makes troubleshooting configuration errors +possible. As an example, if the configuration above is changed to have +the status set to error and the logger declaration is: + +[source,xml] +---- + + + +---- + +the following error message will be produced. + +.... +2011-11-24 23:21:25,517 ERROR Unable to locate appender Routng for logger EventLogger +.... + +Applications may wish to direct the status output to some other +destination. This can be accomplished by setting the dest attribute to +either "err" to send the output to stderr or to a file location or URL. +This can also be done by insuring the configured status is set to OFF +and then configuring the application programmatically such as: + +[source,java] +---- +StatusConsoleListener listener = new StatusConsoleListener(Level.ERROR); +StatusLogger.getLogger().registerListener(listener); +---- + +[#UnitTestingInMaven] +== Testing in Maven + +Maven can run unit and functional tests during the build cycle. By +default, any files placed in `src/test/resources` are automatically +copied to target/test-classes and are included in the classpath during +execution of any tests. As such, placing a log4j2-test.xml into this +directory will cause it to be used instead of a log4j2.xml or +log4j2.json that might be present. Thus a different log configuration +can be used during testing than what is used in production. + +A second approach, which is extensively used by Log4j 2, is to set the +log4j.configurationFile property in the method annotated with +@BeforeClass in the junit test class. This will allow an arbitrarily +named file to be used during the test. + +A third approach, also used extensively by Log4j 2, is to use the +`LoggerContextRule` JUnit test rule which provides additional +convenience methods for testing. This requires adding the `log4j-core` +`test-jar` dependency to your test scope dependencies. For example: + +[source,java] +---- +public class AwesomeTest { + @Rule + public LoggerContextRule init = new LoggerContextRule("MyTestConfig.xml"); + + @Test + public void testSomeAwesomeFeature() { + final LoggerContext ctx = init.getLoggerContext(); + final Logger logger = init.getLogger("org.apache.logging.log4j.my.awesome.test.logger"); + final Configuration cfg = init.getConfiguration(); + final ListAppender app = init.getListAppender("List"); + logger.warn("Test message"); + final List events = app.getEvents(); + // etc. + } +} +---- + +[#SystemProperties] +== System Properties + +The Log4j documentation references a number of System Properties that +can be used to control various aspects of Log4j 2 behavior. The table +below lists these properties along with their default value and a +description of what they control. Any spaces present in the property +name are for visual flow and should be removed. + +Note that beginning in Log4j 2.10, all system property names have been +normalized to follow a consistent naming scheme. While the old property +names are still supported for backwards compatibility, it is recommended +to update configurations to use the new style. This system is extensible +and is enabled through the +link:../log4j-api/apidocs/org/apache/logging/log4j/util/PropertySource.html[`PropertySource`] +interface. Additional property source classes can be added through the +standard `ServiceLoader` mechanism in Java SE. + +Properties can be overridden by sources with a lower number priority +(e.g.., -100 comes before 100). The following sources are all available +by default: + +.PropertySource priorities and descriptions +[cols="2,1,5"] +|=== +|Source |Priority |Description + +|Spring Boot Properties +|-100 +|This property source is enabled only if the Java application uses Spring Boot and the +`log4j-spring` module is present. It resolves properties using a Spring +link:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/env/Environment.html[Environment]. + +|System Properties +|0 +|All properties can be set using normal system +property patterns. These have the lowest numerical priority among commonly available property sources +and can override included `log4j2.component.properties` files or environment variables. If a `log4j2.system.properties` file is available on the classpath its contents are +sourced into Java system properties at Log4j startup. + +|Environment Variables +|100 +|Environment variables are all prefixed +with `LOG4J_`, are in all caps, and words are all separated by +underscores. Only this naming scheme is support for environment +variables as there were no old naming schemes to maintain compatibility +with. + +|`log4j2.component.properties` file +|200 +|Including this file on the classpath can be used as an alternative to providing properties as system +properties. This is the property source with highest numerical priority and can be used to provide +default values that can be overridden by the system administrator. +|=== + +The following is a list of available global configuration properties. +Note that these can only be set once per JVM process unlike +configuration settings available in configuration files. The _Property +Name_ column contains the name used in properties files and system +properties; _Environment Variable_ for the equivalent environment +variable; and _Legacy Property Name_ for the pre-2.10 name. + +.Log4j 2 global configuration properties +[cols="3*,a"] +|=== +|Property Name (Legacy Property Name) |Environment Variable |Default Value |Description + +|[[configurationFile]]log4j2.configurationFile + +([[log4j.configurationFile]]log4j.configurationFile) +|LOG4J_CONFIGURATION_FILE +|  +|Path to an Log4j 2 configuration file. May +also contain a comma separated list of configuration file names. May contain a URL. +When specified as a URL the "override" query parameter may be used to specify additional +configuration file locations. + +|[[debug]]log4j2.debug + +([[log4j2.debug]]log4j2.debug) +|LOG4J_DEBUG +|  +|Log4j2 will print all +internal logging to the console if system property `log4j2.debug` is either +defined empty or its value equals to `true` (ignoring case). + +|[[mergeStrategy]]log4j2.mergeStrategy + +([[log4j.mergeStrategy]]log4j.mergeStrategy) +|LOG4J_MERGE_STRATEGY +|  +|The name of the class that implements the MergeStrategy interface. If not +specified `DefaultMergeStrategy` will be used when creating a CompositeConfiguration. + +|[[contextSelector]]log4j2.contextSelector + +([[Log4jContextSelector]]Log4jContextSelector) +|LOG4J_CONTEXT_SELECTOR +|ClassLoaderContextSelector +|Creates the `LoggerContext`s. An +application can have one or more active LoggerContexts depending on the +circumstances. See link:logsep.html[Log Separation] for more details. +Available context selector implementation classes: + +`org.apache.logging.log4j.core.async .AsyncLoggerContextSelector` - +makes link:async.html[all loggers asynchronous]. + +`org.apache.logging.log4j.core.async .BasicAsyncLoggerContextSelector` - +makes link:async.html[all loggers asynchronous] using a single shared +AsyncLoggerContext. + +`org.apache.logging.log4j.core.selector .BasicContextSelector` - creates +a single shared LoggerContext. + +`org.apache.logging.log4j.core.selector .ClassLoaderContextSelector` - +separate LoggerContexts for each web application. + +`org.apache.logging.log4j.core.selector .JndiContextSelector` - use JNDI +to locate each web application's LoggerContext. + +`org.apache.logging.log4j.core.osgi .BundleContextSelector` - separate +LoggerContexts for each OSGi bundle. + +|[[logEventFactory]]log4j2.logEventFactory + +([[Log4jLogEventFactory]]Log4jLogEventFactory) +|LOG4J_LOG_EVENT_FACTORY +|org.apache.logging.log4j.core.impl .DefaultLogEventFactory +|Factory class used by LoggerConfig to create `LogEvent` instances. (Ignored when +the `AsyncLoggerContextSelector` is used.) + +|[[loggerContextFactory]]log4j2.loggerContextFactory + +([[log4j2.loggerContextFactory]]log4j2.loggerContextFactory) +|LOG4J_LOGGER_CONTEXT_FACTORY +|org.apache.logging.log4j.simple +.SimpleLoggerContextFactory +|Factory class used by LogManager to +bootstrap the logging implementation. The core jar provides +`org.apache.logging.log4j.core .impl.Log4jContextFactory`. + +|[[configurationFactory]]log4j2.configurationFactory + +([[log4j.configurationFactory]]log4j.configurationFactory) +|LOG4J_CONFIGURATION_FACTORY +|  +|Fully specified class name of a class +extending +`org.apache.logging.log4j.core .config.ConfigurationFactory`. +If specified, an instance of this class is added to the list of +configuration factories. + +|[[configurationAllowedProtocols]]log4j2.Configuration.allowedProtocols + +([[log4j.configurationAllowedProtocols]]log4j.configurationAllowedProtocols) +|LOG4J_CONFIGURATION_ALLOWED_PROTOCOLS +|  +|A comma separated list of the protocols that may be used to load a configuration file. The default is https. +To completely prevent accessing the configuration via a URL specify a value of "_none". + +|[[configurationAuthorizationProvider]]log4j2.Configuration.authorizationProvider + +([[log4j.configurationAuthorizationProvider]]log4j.configurationAuthorizationProvider) +|LOG4J_CONFIGURATION_AUTHORIZATION_PROVIDER +|org.apache.logging.log4j.core.util.BasicAuthorizationProvider +|The fully qualified class name of the AuthorizationProvider. + +|[[configurationPassword]]log4j2.Configuration.password + +([[log4j.configurationPassword]]log4j.configurationPassword) +|LOG4J_CONFIGURATION_PASSWORD +|  +|The password required to access the remote logging configuration file. + +|[[configurationPasswordDecryptor]]log4j2.Configuration.passwordDecryptor + +([[log4j.configurationPasswordDecryptor]]log4j.configurationPasswordDecryptor) +|LOG4J_CONFIGURATION_DECRYPTOR +|  +|If the password is encrypted this class will be used to decrypt it. + +|[[configurationUsername]]log4j2.Configuration.username+ +([[log4j.configurationUsername]]log4j.configurationUsername) +|LOG4J_CONFIGURATION_USERNAME +|  +|The user name required to access the remote logging configuration file. + +|[[shutdownHookEnabled]]log4j2.shutdownHookEnabled + +([[log4j.shutdownHookEnabled]]log4j.shutdownHookEnabled) +|LOG4J_SHUTDOWN_HOOK_ENABLED +|true +|Overrides the global flag for +whether or not a shutdown hook should be used to stop a +`LoggerContext`. By default, this is enabled and can be +disabled on a per-configuration basis. When running with the `log4j-web` +module, this is automatically disabled. + +|[[shutdownCallbackRegistry]]log4j2.shutdownCallbackRegistry + +([[log4j.shutdownCallbackRegistry]]log4j.shutdownCallbackRegistry) +|LOG4J_SHUTDOWN_CALLBACK_REGISTRY +|org.apache.logging.log4j.core.util +.DefaultShutdownCallbackRegistry +|Fully specified class name of a class implementing +link:../log4j-core/apidocs/org/apache/logging/log4j/core/util/ShutdownCallbackRegistry.html[ShutdownCallbackRegistry]. +If specified, an instance of this class is used instead of +`DefaultShutdownCallbackRegistry`. The specified class must have a +default constructor. + +|[[clock]]log4j2.clock + +([[log4j.Clock]]log4j.Clock) +|LOG4J_CLOCK +|SystemClock +|Implementation +of the `org.apache.logging.log4j .core.time.Clock` interface that is +used for timestamping the log events. + +By default, `System.currentTimeMillis` is called on every log event. + +You can also specify a fully qualified class name of a custom class that +implements the `Clock` interface. + +|[[usePreciseClock]]log4j2.usePreciseClock +|LOG4J_USE_PRECISE_CLOCK +|false +|When false the clock resolution will be in milliseconds. When true it will use the smallest granularity supported by +the JVM. The precise clock is not garbage free. This setting only applies when Log4j's default SystemClock is used. + +|[[level]]log4j2.level + +([[org.apache.logging.log4j.level]]org.apache.logging.log4j.level) +|LOG4J_LEVEL +|ERROR +|Log level of the default configuration. The default +configuration is used if the ConfigurationFactory could not successfully +create a configuration (e.g. no log4j2.xml file was found). + +|[[disableThreadContext]]log4j2.disableThreadContext + +(disableThreadContext) +|LOG4J_DISABLE_THREAD_CONTEXT +|false +|If `true`, +the ThreadContext stack and map are disabled. (May be ignored if a +custom ThreadContext map is specified.) + +|[[disableThreadContextStack]]log4j2.disableThreadContextStack + +(disableThreadContextStack) +|LOG4J_DISABLE_THREAD_CONTEXT_STACK +|false +|If `true`, the ThreadContext stack is disabled. + +|[[disableThreadContextMap]]log4j2.disableThreadContextMap + +(disableThreadContextMap) +|LOG4J_DISABLE_THREAD_CONTEXT_MAP +|false +|If +`true`, the ThreadContext map is disabled. (May be ignored if a custom +ThreadContext map is specified.) + +|[[log4j2.threadContextMap]]log4j2.threadContextMap + +(log4j2.threadContextMap) +|LOG4J_THREAD_CONTEXT_MAP +|  +|Fully specified +class name of a custom `ThreadContextMap` implementation class. + +|[[isThreadContextMapInheritable]]log4j2.isThreadContextMapInheritable + +(isThreadContextMapInheritable) +|LOG4J_IS_THREAD_CONTEXT_MAP_INHERITABLE +|false +|If `true` use a `InheritableThreadLocal` to implement the +ThreadContext map. Otherwise, use a plain `ThreadLocal`. (May be ignored +if a custom ThreadContext map is specified.) + +|[[contextDataInjector]]log4j2.contextDataInjector + +([[log4j2.ContextDataInjector]]log4j2.ContextDataInjector) +|LOG4J_CONTEXT_DATA_INJECTOR +|  +|Fully specified class name of a custom +`ContextDataInjector` implementation class. + +|[[garbagefreeThreadContextMap]]log4j2.garbagefreeThreadContextMap + +([[log4j2.garbagefree.threadContextMap]]log4j2.garbagefree.threadContextMap) +|LOG4J_GARBAGEFREE_THREAD_CONTEXT_MAP +|false +|Specify "true" to make the +ThreadContext map garbage-free. + +|[[disableJmx]]log4j2.disableJmx + +([[log4j2.disable.jmx]]log4j2.disable.jmx) +|LOG4J_DISABLE_JMX +|false +|If +`true`, Log4j configuration objects like LoggerContexts, Appenders, +Loggers, etc. will not be instrumented with MBeans and cannot be +remotely monitored and managed. + +|[[jmxNotifyAsync]]log4j2.jmxNotifyAsync + +([[log4j2.jmx.notify.async]]log4j2.jmx.notify.async) +|LOG4J_JMX_NOTIFY_ASYNC +|false for web apps, true otherwise +|If `true`, +log4j's JMX notifications are sent from a separate background thread, +otherwise they are sent from the caller thread. If system property +`log4j2.is.webapp` is `true` or the `javax.servlet.Servlet` class is on +the classpath, the default behaviour is to use the caller thread to send +JMX notifications. + +|[[skipJansi]]log4j2.skipJansi + +([[log4j.skipJansi]]log4j.skipJansi) +|LOG4J_SKIP_JANSI +|true +|If `true`, +the ConsoleAppender will not try to use the Jansi output stream on +Windows. + +|[[ignoreTCL]]log4j2.ignoreTCL + +([[log4j.ignoreTCL]]log4j.ignoreTCL) +|LOG4J_IGNORE_TCL +|false +|If +`true`, classes are only loaded with the default class loader. +Otherwise, an attempt is made to load classes with the current thread's +context class loader before falling back to the default class loader. + + +|[[forceTCLOnly]]log4j2.forceTCLOnly + +([[log4j.forceTCLOnly]]log4j.forceTCLOnly) +|LOG4J_FORCE_TCL_ONLY +|false +|If `true`, classes and configuration are only loaded with the default context class loader. +Otherwise, log4j also uses the log4j classloader, parent classloaders and the system classloader. + +|[[enableJndi]]log4j2.enableJndi + +([[log4j.enableJndi]]log4j.enableJndi) +|LOG4J_ENABLE_JNDI +|false +| When true, Log4j components that use JNDI are enabled. When false, the default, they are disabled. + +|[[allowedLdapClasses]]log4j2.allowedLdapClasses + +([[log4j.allowedLdapClasses]]log4j.allowedLdapClasses) +|LOG4J_ALLOWED_LDAP_CLASSES +|  +| System property that specifies fully qualified class names that may be accessed by LDAP. The classes +must implement Serializable. By default only Java primative classes are allowed. + +|[[allowedLdapHosts]]log4j2.allowedLdapHosts + +([[log4j.allowedLdapHosts]]log4j.allowedLdapHosts) +|LOG4J_ALLOWED_LDAP_HOSTS +|  +| System property that adds host names or ip addresses that may be access by LDAP. By default it only allows +the local host names and ip addresses. + +|[[allowedJndiProtocols]]log4j2.allowedJndiProtocols + +([[log4j.allowedJndiProtocols]]log4j.allowedJndiProtocols) +|LOG4J_ALLOWED_JNDI_PROTOCOLS +|  +| System property that adds protocol names that JNDI will allow. By default it only allows java, ldap, and ldaps. + +|[[uuidSequence]]log4j2.uuidSequence + +([[org.apache.logging.log4j.uuidSequence]]org.apache.logging.log4j.uuidSequence) +|LOG4J_UUID_SEQUENCE +|0 +|System property that may be used to seed the +UUID generation with an integer value. + +|[[simplelogShowContextMap]]log4j2.simplelogShowContextMap + +([[org.apache.logging.log4j.simplelog.showContextMap]]org.apache.logging.log4j +.simplelog.showContextMap) +|LOG4J_SIMPLELOG_SHOW_CONTEXT_MAP +|false +|If +`true`, the full ThreadContext map is included in each SimpleLogger log +message. + +|[[simplelogShowlogname]]log4j2.simplelogShowlogname + +([[org.apache.logging.log4j.simplelog.showlogname]]org.apache.logging.log4j +.simplelog.showlogname) +|LOG4J_SIMPLELOG_SHOWLOGNAME +|false +|If `true`, +the logger name is included in each SimpleLogger log message. + +|[[simplelogShowShortLogname]]log4j2.simplelogShowShortLogname + +([[org.apache.logging.log4j.simplelog.showShortLogname]]org.apache.logging.log4j +.simplelog.showShortLogname) +|LOG4J_SIMPLELOG_SHOW_SHORT_LOGNAME +|true +|If `true`, only the last component of a logger name is included in +SimpleLogger log messages. (E.g., if the logger name is +"mycompany.myproject.mycomponent", only "mycomponent" is logged. + +|[[simplelogShowdatetime]]log4j2.simplelogShowdatetime + +([[org.apache.logging.log4j.simplelog.showdatetime]]org.apache.logging.log4j +.simplelog.showdatetime) +|LOG4J_SIMPLELOG_SHOWDATETIME +|false +|If +`true`, SimpleLogger log messages contain timestamp information. + +|[[simplelogDateTimeFormat]]log4j2.simplelogDateTimeFormat + +([[org.apache.logging.log4j.simplelog.dateTimeFormat]]org.apache.logging.log4j +.simplelog.dateTimeFormat) +|LOG4J_SIMPLELOG_DATE_TIME_FORMAT +|"yyyy/MM/dd HH:mm:ss:SSS zzz" +|Date-time format to use. Ignored if +`org.apache.logging.log4j .simplelog.showdatetime` is `false`. + +|[[simplelogLogFile]]log4j2.simplelogLogFile + +([[org.apache.logging.log4j.simplelog.logFile]]org.apache.logging.log4j +.simplelog.logFile) +|LOG4J_SIMPLELOG_LOG_FILE +|system.err +|"system.err" +(case-insensitive) logs to System.err, "system.out" (case-insensitive) +logs to System.out, any other value is interpreted as a file name to +save SimpleLogger messages to. + +|[[simplelogLevel]]log4j2.simplelogLevel + +([[org.apache.logging.log4j.simplelog.level]]org.apache.logging.log4j +.simplelog.level) +|LOG4J_SIMPLELOG_LEVEL +|ERROR +|Default level for new +SimpleLogger instances. + +|log4j2.simplelog..level + +([[org.apache.logging.log4j.simplelog.[loggerName]level]]org.apache.logging.log4j +.simplelog..level) +|LOG4J_SIMPLELOG__LEVEL +|SimpleLogger default log level +|Log level for a the SimpleLogger +instance with the specified name. + +|[[simplelogStatusLoggerLevel]]log4j2.simplelogStatusLoggerLevel + +([[org.apache.logging.log4j.simplelog.StatusLogger.level]]org.apache.logging.log4j.simplelog +.StatusLogger.level) +|LOG4J_SIMPLELOG_STATUS_LOGGER_LEVEL +|ERROR +|This +property is used to control the initial StatusLogger level, and can be +overridden in code by calling +`StatusLogger.getLogger() .setLevel(someLevel)`. Note that the +StatusLogger level is only used to determine the status log output level +until a listener is registered. In practice, a listener is registered +when a configuration is found, and from that point onwards, status +messages are only sent to the listeners (depending on their +statusLevel). + +|[[defaultStatusLevel]]log4j2.defaultStatusLevel + +([[Log4jDefaultStatusLevel]]Log4jDefaultStatusLevel) +|LOG4J_DEFAULT_STATUS_LEVEL +|ERROR +| +The StatusLogger logs events that occur in the logging system to the +console. During configuration, AbstractConfiguration registers a +StatusConsoleListener with the StatusLogger that may redirect status log +events from the default console output to a file. The listener also +supports fine-grained filtering. This system property specifies the +default status log level for the listener to use if the configuration +does not specify a status level. + +Note: this property is used by the log4j-core implementation only after +a configuration file has been found. + +|[[statusLoggerLevel]]log4j2.statusLoggerLevel + +([[log4j2.StatusLogger.level]]log4j2.StatusLogger.level) +|LOG4J_STATUS_LOGGER_LEVEL +|WARN +| +The initial "listenersLevel" of the StatusLogger. If StatusLogger +listeners are added, the "listenerLevel" is changed to that of the most +verbose listener. If any listeners are registered, the listenerLevel is +used to quickly determine if an interested listener exists. + +By default, StatusLogger listeners are added when a configuration is +found and by the JMX StatusLoggerAdmin MBean. For example, if a +configuration contains ``, a listener with +statusLevel TRACE is registered and the StatusLogger listenerLevel is +set to TRACE, resulting in verbose status messages displayed on the +console. + +If no listeners are registered, the listenersLevel is not used, and the +StatusLogger output level is determined by +`StatusLogger.getLogger().getLevel()` (see property +`org.apache.logging.log4j.simplelog .StatusLogger.level`). + +|[[statusEntries]]log4j2.statusEntries + +([[log4j2.status.entries]]log4j2.status.entries) +|LOG4J_STATUS_ENTRIES +|200 +|Number of StatusLogger events that are kept in a buffer and can be +retrieved with `StatusLogger.getStatusData()`. + +|[[statusLoggerDateformat]]log4j2.statusLoggerDateformat + +([[log4j2.StatusLogger.DateFormat]]log4j2.StatusLogger.DateFormat) +|LOG4J_STATUS_LOGGER_DATEFORMAT +|  +|Date-time format string to use as +the format for timestamps in the status logger output. See +`java.text.SimpleDateFormat` for supported formats. + +|[[asyncLoggerExceptionHandler]]log4j2.asyncLoggerExceptionHandler + +([[AsyncLogger.ExceptionHandler]]AsyncLogger.ExceptionHandler) +|LOG4J_ASYNC_LOGGER_EXCEPTION_HANDLER +|default handler +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[asyncLoggerRingBufferSize]]log4j2.asyncLoggerRingBufferSize + +([[AsyncLogger.RingBufferSize]]AsyncLogger.RingBufferSize) +|LOG4J_ASYNC_LOGGER_RING_BUFFER_SIZE +|256 * 1024 or 4 * 1024 in garbage-free mode +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[asyncLoggerWaitStrategy]]log4j2.asyncLoggerWaitStrategy + +([[AsyncLogger.WaitStrategy]]AsyncLogger.WaitStrategy) +|LOG4J_ASYNC_LOGGER_WAIT_STRATEGY +|Timeout +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[asyncLoggerTimeout]]log4j2.asyncLoggerTimeout + +([[AsyncLogger.Timeout]]AsyncLogger.Timeout) +|LOG4J_ASYNC_LOGGER_TIMEOUT +|10 +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[asyncLoggerSleepTimeNs]]log4j2.asyncLoggerSleepTimeNs + +([[AsyncLogger.SleepTimeNs]]AsyncLogger.SleepTimeNs) +|LOG4J_ASYNC_LOGGER_SLEEP_TIME_NS +|100 +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[asyncLoggerRetries]]log4j2.asyncLoggerRetries + +([[AsyncLogger.Retries]]AsyncLogger.Retries) +|LOG4J_ASYNC_LOGGER_RETRIES +|200 +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[AsyncLogger.SynchronizeEnqueueWhenQueueFull]]AsyncLogger.SynchronizeEnqueueWhenQueueFull +|ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL +|true +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[asyncLoggerThreadNameStrategy]]log4j2.asyncLoggerThreadNameStrategy + +([[AsyncLogger.ThreadNameStrategy]]AsyncLogger.ThreadNameStrategy) +|LOG4J_ASYNC_LOGGER_THREAD_NAME_STRATEGY +|CACHED +|See +link:async.html#SysPropsAllAsync[Async Logger System Properties] for +details. + +|[[asyncLoggerConfigExceptionHandler]]log4j2.asyncLoggerConfigExceptionHandler + +([[AsyncLoggerConfig.ExceptionHandler]]AsyncLoggerConfig.ExceptionHandler) +|LOG4J_ASYNC_LOGGER_CONFIG_EXCEPTION_HANDLER +|default handler +|See +link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger +System Properties] for details. + +|[[asyncLoggerConfigRingBufferSize]]log4j2.asyncLoggerConfigRingBufferSize + +([[AsyncLoggerConfig.RingBufferSize]]AsyncLoggerConfig.RingBufferSize) +|LOG4J_ASYNC_LOGGER_CONFIG_RING_BUFFER_SIZE +|256 * 1024 or 4 * 1024 in garbage-free mode +|See +link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger +System Properties] for details. + +|[[asyncLoggerConfigWaitStrategy]]log4j2.asyncLoggerConfigWaitStrategy + +([[AsyncLoggerConfig.WaitStrategy]]AsyncLoggerConfig.WaitStrategy) +|LOG4J_ASYNC_LOGGER_CONFIG_WAIT_STRATEGY +|Timeout +|See +link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger +System Properties] for details. + +|[[asyncLoggerConfigTimeout]]log4j2.asyncLoggerConfigTimeout + +([[AsyncLoggerConfig.Timeout]]AsyncLoggerConfig.Timeout) +|LOG4J_ASYNC_LOGGER_CONFIG_TIMEOUT +|10 +|See +link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger +System Properties] for details. + +|[[asyncLoggerConfigSleepTimeNs]]log4j2.asyncLoggerConfigSleepTimeNs + +([[AsyncLoggerConfig.SleepTimeNs]]AsyncLoggerConfig.SleepTimeNs) +|LOG4J_ASYNC_LOGGER_CONFIG_SLEEP_TIME_NS +|100 +|See +link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger +System Properties] for details. + +|[[asyncLoggerConfigRetries]]log4j2.asyncLoggerConfigRetries + +([[AsyncLoggerConfig.Retries]]AsyncLoggerConfig.Retries) +|LOG4J_ASYNC_LOGGER_CONFIG_RETRIES +|200 +|See +link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger +System Properties] for details. + + +|[[AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull]]AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull +|ASYNC_LOGGER_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL +|true +|See +link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger +System Properties] for details. + +|[[julLoggerAdapter]]log4j2.julLoggerAdapter + +([[log4j.jul.LoggerAdapter]]log4j.jul.LoggerAdapter) +|LOG4J_JUL_LOGGER_ADAPTER +|org.apache.logging.log4j +.jul.ApiLoggerAdapter +|Default LoggerAdapter to use in the JUL adapter. +By default, if log4j-core is available, then the class +`org.apache.logging.log4j.jul .CoreLoggerAdapter` will be used. +Otherwise, the `ApiLoggerAdapter` will be used. Custom implementations +must provide a public default constructor. + +|[[formatMsgAsync]]log4j2.formatMsgAsync + +([[log4j.format.msg.async]]log4j.format.msg.async) +|LOG4J_FORMAT_MSG_ASYNC +|false +|If `false` (the default), Log4j will +make sure the message is formatted in the caller thread, to ensure the +value at the time of the call to the logger is the value that is logged. + +|[[asyncQueueFullPolicy]]log4j2.asyncQueueFullPolicy + +([[log4j2.AsyncQueueFullPolicy]]log4j2.AsyncQueueFullPolicy) +|LOG4J_ASYNC_QUEUE_FULL_POLICY +|  +| +Used by Async Loggers and the AsyncAppender to maintain application +throughput even when the underlying appender cannot keep up with the +logging rate and the queue is filling up. + +If no value is specified (the default) events are never discarded. If +the queue is full, the logger call blocks until the event can be added +to the queue. + +Specify `Discard` to drop events whose level is equal or less than the +threshold level (INFO by default) when the queue is full. + +|[[discardThreshold]]log4j2.discardThreshold + +([[log4j2.DiscardThreshold]]log4j2.DiscardThreshold) +|LOG4J_DISCARD_THRESHOLD +|INFO +|Used by the +DiscardingAsyncQueueFullPolicy to determine which events to drop when +the queue becomes full. By default, `INFO`, `DEBUG` and `TRACE` level +events are discarded when the queue is full. This property only has +effect if `Discard` is specified as the `log4j2.AsyncQueueFullPolicy`. + +|[[messageFactory]]log4j2.messageFactory + +([[log4j2.messageFactory]]log4j2.messageFactory) +|LOG4J_MESSAGE_FACTORY +|org.apache.logging.log4j.message. ParameterizedMessageFactory or +org.apache.logging.log4j.message. ReusableMessageFactory in garbage-free +mode +|Default message factory used by Loggers if no factory was +specified. + +|[[flowMessageFactory]]log4j2.flowMessageFactory + +([[log4j2.flowMessageFactory]]log4j2.flowMessageFactory) +|LOG4J_FLOW_MESSAGE_FACTORY +|org.apache.logging.log4j.message. +DefaultFlowMessageFactory +|Default flow message factory used by Loggers. + +|[[isWebapp]]log4j2.isWebapp + +([[log4j2.is.webapp]]log4j2.is.webapp) +|LOG4J_IS_WEBAPP +|true if +`Servlet` class on class path +|This system property can be used to force +Log4j 2 to behave as if it is part of a web application (when true) or +as if it is not part of a web application (when false). + +|[[enableThreadlocals]]log4j2.enableThreadlocals + +([[log4j2.enable.threadlocals]]log4j2.enable.threadlocals) +|LOG4J_ENABLE_THREADLOCALS +|true +|This system property can be used to +switch off the use of threadlocals, which will partly disable Log4j's +garbage-free behaviour: to be fully garbage-free, Log4j stores objects +in ThreadLocal fields to reuse them, otherwise new objects are created +for each log event. Note that this property is not effective when Log4j +detects it is running in a web application. + +|[[enableDirectEncoders]]log4j2.enableDirectEncoders + +([[log4j2.enable.direct.encoders]]log4j2.enable.direct.encoders) +|LOG4J_ENABLE_DIRECT_ENCODERS +|true +|This property can be used to force +garbage-aware Layouts and Appenders to revert to the pre-2.6 behaviour +where converting log events to text generates temporary objects like +Strings and char[] arrays, and converting this text to bytes generates +temporary byte[] arrays. By default, this property is `true` and +garbage-aware Layouts and Appenders that convert log events to text will +convert this text to bytes without creating temporary objects. + +|[[initialReusableMsgSize]]log4j2.initialReusableMsgSize + +([[log4j.initialReusableMsgSize]]log4j.initialReusableMsgSize) +|LOG4J_INITIAL_REUSABLE_MSG_SIZE +|128 +|In GC-free mode, this property +determines the initial size of the reusable StringBuilders where the +message text is formatted and potentially passed to background threads. + +|[[maxReusableMsgSize]]log4j2.maxReusableMsgSize + +([[log4j.maxReusableMsgSize]]log4j.maxReusableMsgSize) +|LOG4J_MAX_REUSABLE_MSG_SIZE +|518 +|In GC-free mode, this property +determines the maximum size of the reusable StringBuilders where the +message text is formatted and potentially passed to background threads. + +|[[layoutStringBuilderMaxSize]]log4j2.layoutStringBuilderMaxSize + +([[log4j.layoutStringBuilder.maxSize]]log4j.layoutStringBuilder.maxSize) +|LOG4J_LAYOUT_STRING_BUILDER_MAX_SIZE +|2048 +|This property determines +the maximum size of the thread-local reusable StringBuilders used to +format the log event to text by Layouts that extend +AbstractStringLayout. + +|[[unboxRingbufferSize]]log4j2.unboxRingbufferSize + +([[log4j.unbox.ringbuffer.size]]log4j.unbox.ringbuffer.size) +|LOG4J_UNBOX_RINGBUFFER_SIZE +|32 +| +The `org.apache.logging.log4j.util.Unbox` utility manages a small +thread-local ring buffer of StringBuilders. Each time one of the `box()` +methods is called, the next slot in the ring buffer is used, until the +ring buffer is full and the first slot is reused. By default the Unbox +ring buffer has 32 slots, so user code can have up to 32 boxed +primitives in a single logger call. + +If more slots are required, set system property +`log4j.unbox.ringbuffer.size` to the desired ring buffer size. Note that +the specified number will be rounded up to the nearest power of 2. + +|[[loggerContextStacktraceOnStart]]log4j2.loggerContextStacktraceOnStart + +([[log4j.LoggerContext.stacktrace.on.start]]log4j.LoggerContext.stacktrace.on.start) +|LOG4J_LOGGER_CONTEXT_STACKTRACE_ON_START +|false +|Prints a stacktrace to +the link:#StatusMessages[status logger] at DEBUG level when the +LoggerContext is started. For debug purposes. + +|[[formatMsgNoLookups]]log4j2.formatMsgNoLookups + +([[log4j2.formatMsgNoLookups]]log4j2.formatMsgNoLookups) +|LOG4J_FORMAT_MSG_NO_LOOKUPS +|true +|Disables message +pattern lookups globally when set to `true`. This is equivalent to +defining all message patterns using `%m{nolookups}`. + +|[[trustStoreLocation]]log4j2.trustStoreLocation + +([[log4j2.trustStoreLocation]]log4j2.trustStoreLocation) +|LOG4J_TRUST_STORE_LOCATION +| +|The location of the trust store. If not provided the default trust store will be used. + +|[[trustStorePassword]]log4j2.trustStorePassword + +([[log4j2.trustStorePassword]]log4j2.trustStorePassword) +|LOG4J_TRUST_STORE_PASSWORD +| +|Password needed to access the trust store. + +|[[trustStorePasswordFile]]log4j2.trustStorePasswordFile + +([[log4j2.trustStorePasswordFile]]log4j2.trustStorePasswordFile) +|LOG4J_TRUST_STORE_PASSWORD_FILE +| +|The location of a file that contains the password for the trust store. + +|[[trustStorePasswordEnvironmentVariable]]log4j2.trustStorePasswordEnvironmentVariable + +([[log4j2.trustStorePasswordEnvironmentVariable]]log4j2.trustStorePasswordEnvironmentVariable) +|LOG4J_TRUST_STORE_PASSWORD_ENVIRONMENT_VARIABLE +| +|The name of the environment variable that contains the trust store password. + +|[[trustStoreType]]log4j2.trustStoreType + +([[log4j2.trustStoreType]]log4j2.trustStoreType) +|LOG4J_TRUST_STORE_TYPE +| +|The type of key store used for the trust store. + +|[[trustStoreKeyManagerFactoryAlgorithm]]log4j2.trustStoreKeyManagerFactoryAlgorithm + +([[log4j2.trustStoreKeyManagerFactoryAlgorithm]]log4j2.trustStoreKeyStoreFactoryAlgorithm) +|LOG4J_TRUST_STORE_KEY_MANAGER_FACTORY_ALGORITHM +| +|Java cryptographic algorithm. + +|[[keyStoreLocation]]log4j2.keyStoreLocation + +([[log4j2.trustStoreLocation]]log4j2.trustStoreLocation) +|LOG4J_KEY_STORE_LOCATION +| +|The location of the key store. If not provided the default key store will be used. + +|[[keyStorePassword]]log4j2.keyStorePassword + +([[log4j2.keyStorePassword]]log4j2.keyStorePassword) +|LOG4J_KEY_STORE_PASSWORD +| +|Password needed to access the trust store. + +|[[keyStorePasswordFile]]log4j2.keyStorePasswordFile + +([[log4j2.keyStorePasswordFile]]log4j2.keyStorePasswordFile) +|LOG4J_KEY_STORE_PASSWORD_FILE +| +|The location of a file that contains the password for the key store. + +|[[keyStorePasswordEnvironmentVariable]]log4j2.keyStorePasswordEnvironmentVariable + +([[log4j2.keyStorePasswordEnvironmentVariable]]log4j2.keyStorePasswordEnvironmentVariable) +|LOG4J_KEY_STORE_PASSWORD_ENVIRONMENT_VARIABLE +| +|The name of the environment variable that contains the key store password. + +|[[keyStoreType]]log4j2.keyStoreType + +([[log4j2.keyStoreType]]log4j2.keyStoreType) +|LOG4J_KEY_STORE_TYPE +| +|The type of key store used for the trust store. + +|[[keyStoreKeyManagerFactoryAlgorithm]]log4j2.keyStoreKeyManagerFactoryAlgorithm + +([[log4j2.keyStoreKeyManagerFactoryAlgorithm]]log4j2.keyStoreKeyStoreFactoryAlgorithm) +|LOG4J_KEY_STORE_KEY_MANAGER_FACTORY_ALGORITHM +| +|Java cryptographic algorithm. + + +|[[sslVerifyHostName]]log4j2.sslVerifyHostName + +([[log4j2.sslVerifyHostName]]log4j2.sslVerifyHostName) +|LOG4J_SSL_VERIFY_HOSTNAME +|false +|true or false if the host name should be verified + +|[[log4j2.Script.enableLanguages]]log4j2.Script.enableLanguages +|LOG4J_SCRIPT_ENABLE_LANGUAGES +| +| The list of script languages that are allowed to execute. The names specified must have a ScriptEngine installed +that advertises the same language(s) in order for scripting to be enabled. If no languages are specified, which is +the default, the ScriptManager will not be installed. + +|=== diff --git a/src/site/asciidoc/manual/customconfig.adoc b/src/site/asciidoc/manual/customconfig.adoc new file mode 100644 index 00000000000..bddf4197eeb --- /dev/null +++ b/src/site/asciidoc/manual/customconfig.adoc @@ -0,0 +1,386 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Programmatic Configuration +Ralph Goers + +Log4j 2 provides a few ways for applications to create their own +programmatic configuration: + +* Specify a custom `ConfigurationFactory` to start Log4j with a +programmatic configuration +* Use the `Configurator` to replace the configuration after Log4j started +* Initialize Log4j with a combination of a configuration file and +programmatic configuration +* Modify the current `Configuration` after initialization + +[#ConfigurationBuilder] +== The ConfigurationBuilder API + +Starting with release 2.4, Log4j provides a `ConfigurationBuilder` and a +set of component builders that allow a `Configuration` to be created +fairly easily. Actual configuration objects like `LoggerConfig` or +`Appender` can be unwieldy; they require a lot of knowledge about Log4j +internals which makes them difficult to work with if all you want is to +create a `Configuration`. + +The new `ConfigurationBuilder` API (in the +`org.apache.logging.log4j.core.config.builder.api` package) allows users +to create Configurations in code by constructing component +_definitions_. There is no need to work directly with actual +configuration objects. Component definitions are added to the +`ConfigurationBuilder`, and once all the definitions have been collected +all the actual configuration objects (like Loggers and Appenders) are +constructed. + +`ConfigurationBuilder` has convenience methods for the base components +that can be configured such as Loggers, Appenders, Filter, Properties, +etc. However, Log4j 2's plugin mechanism means that users can create any +number of custom components. As a trade-off, the `ConfigurationBuilder` +API provides only a limited number of "strongly typed" convenience +methods like `newLogger()`, `newLayout()` etc. The generic +`builder.newComponent()` method can be used if no convenience method +exists for the component you want to configure. + +For example, the builder does not know what sub-components can be +configured on specific components such as the RollingFileAppender vs. +the RoutingAppender. To specify a triggering policy on a +RollingFileAppender you would use builder.newComponent(). + +Examples of using the `ConfigurationBuilder` API are in the sections that +follow. + +[#ConfigurationFactory] +== Understanding ConfigurationFactory + +During initialization, Log4j 2 will search for available +link:extending.html#ConfigurationFactory[ConfigurationFactories] and +then select the one to use. The selected `ConfigurationFactory` creates +the `Configuration` that Log4j will use. Here is how Log4j finds the +available ConfigurationFactories: + +1. A system property named `log4j2.configurationFactory` can be set +with the name of the ConfigurationFactory to be used. +2. `ConfigurationFactory.setConfigurationFactory(ConfigurationFactory)` +can be called with the instance of the `ConfigurationFactory` to be used. +This must be called before any other calls to Log4j. +3. A `ConfigurationFactory` implementation can be added to the classpath +and configured as a plugin in the "ConfigurationFactory" category. The +`@Order` annotation can be used to specify the relative priority when +multiple applicable ConfigurationFactories are found. + +ConfigurationFactories have the concept of "supported types", which +basically maps to the file extension of the configuration file that the +ConfigurationFactory can handle. If a configuration file location is +specified, ConfigurationFactories whose supported type does not include +"*" or the matching file extension will not be used. + +[#Example] +== Initialize Log4j Using ConfigurationBuilder with a Custom ConfigurationFactory + +One way to programmatically configure Log4j 2 is to create a custom +`ConfigurationFactory` that uses the +link:#ConfigurationBuilder[`ConfigurationBuilder`] to create a +Configuration. The below example overrides the `getConfiguration()` +method to return a `Configuration` created by the `ConfigurationBuilder`. +This will cause the `Configuration` to automatically be hooked into Log4j +when the `LoggerContext` is created. In the example below, because it +specifies a supported type of "*" it will override any configuration +files provided. + +[source,java] +---- +@Namespace(ConfigurationFactory.NAMESPACE) +@Plugin +@Order(50) +public class CustomConfigurationFactory extends ConfigurationFactory { + + static Configuration createConfiguration(final String name, ConfigurationBuilder builder) { + builder.setConfigurationName(name); + builder.setStatusLevel(Level.ERROR); + builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL). + addAttribute("level", Level.DEBUG)); + AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE"). + addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT); + appenderBuilder.add(builder.newLayout("PatternLayout"). + addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); + appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, + Filter.Result.NEUTRAL).addAttribute("marker", "FLOW")); + builder.add(appenderBuilder); + builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG). + add(builder.newAppenderRef("Stdout")). + addAttribute("additivity", false)); + builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout"))); + return builder.build(); + } + + @Override + public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) { + return getConfiguration(loggerContext, source.toString(), null); + } + + @Override + public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) { + ConfigurationBuilder builder = newConfigurationBuilder(); + return createConfiguration(name, builder); + } + + @Override + protected String[] getSupportedTypes() { + return new String[] {"*"}; + } +} +---- + +As of version 2.7, the `ConfigurationFactory.getConfiguration()` methods +take an additional `LoggerContext` parameter. + +[#Configurator] +== Reconfigure Log4j Using ConfigurationBuilder with the Configurator + +An alternative to a custom `ConfigurationFactory` is to configure with the +`Configurator`. Once a `Configuration` object has been constructed, it can +be passed to one of the `Configurator.initialize` methods to set up the +Log4j configuration. + +Using the `Configurator` in this manner allows the application control +over when Log4j is initialized. However, should any logging be attempted +before `Configurator.initialize()` is called then the default +configuration will be used for those log events. + +[source,java] +---- +ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); +builder.setStatusLevel(Level.ERROR); +builder.setConfigurationName("BuilderTest"); +builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL) + .addAttribute("level", Level.DEBUG)); +AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").addAttribute("target", + ConsoleAppender.Target.SYSTEM_OUT); +appenderBuilder.add(builder.newLayout("PatternLayout") + .addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); +appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL) + .addAttribute("marker", "FLOW")); +builder.add(appenderBuilder); +builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG) + .add(builder.newAppenderRef("Stdout")).addAttribute("additivity", false)); +builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout"))); +ctx = Configurator.initialize(builder.build()); +---- + +This example shows how to create a configuration that includes a +RollingFileAppender. + +[source,java] +---- +ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder(); + +builder.setStatusLevel(Level.ERROR); +builder.setConfigurationName("RollingBuilder"); +// create a console appender +AppenderComponentBuilder appenderBuilder = builder.newAppender("Stdout", "CONSOLE").addAttribute("target", + ConsoleAppender.Target.SYSTEM_OUT); +appenderBuilder.add(builder.newLayout("PatternLayout") + .addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable")); +builder.add(appenderBuilder); +// create a rolling file appender +LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout") + .addAttribute("pattern", "%d [%t] %-5level: %msg%n"); +ComponentBuilder triggeringPolicy = builder.newComponent("Policies") + .addComponent(builder.newComponent("CronTriggeringPolicy").addAttribute("schedule", "0 0 0 * * ?")) + .addComponent(builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", "100M")); +appenderBuilder = builder.newAppender("rolling", "RollingFile") + .addAttribute("fileName", "target/rolling.log") + .addAttribute("filePattern", "target/archive/rolling-%d{MM-dd-yy}.log.gz") + .add(layoutBuilder) + .addComponent(triggeringPolicy); +builder.add(appenderBuilder); + +// create the new logger +builder.add(builder.newLogger("TestLogger", Level.DEBUG) + .add(builder.newAppenderRef("rolling")) + .addAttribute("additivity", false)); + +builder.add(builder.newRootLogger(Level.DEBUG) + .add(builder.newAppenderRef("rolling"))); +LoggerContext ctx = Configurator.initialize(builder.build()); +---- + +[#Hybrid] +== Initialize Log4j by Combining Configuration File with Programmatic Configuration + +Sometimes you want to configure with a configuration file but do some +additional programmatic configuration. A possible use case might be that +you want to allow for a flexible configuration using XML but at the same +time make sure there are a few configuration elements that are always +present that can't be removed. + +The easiest way to achieve this is to extend one of the standard +`Configuration` classes (`XMLConfiguration`, `JSONConfiguration`) and then +create a new `ConfigurationFactory` for the extended class. After the +standard configuration completes the custom configuration can be added +to it. + +The example below shows how to extend `XMLConfiguration` to manually add +an `Appender` and a `LoggerConfig` to the configuration. + +[source,java] +---- +@Namespace("ConfigurationFactory") +@Plugin("MyXMLConfigurationFactory") +@Order(10) +public class MyXMLConfigurationFactory extends ConfigurationFactory { + + /** + * Valid file extensions for XML files. + */ + public static final String[] SUFFIXES = new String[] {".xml", "*"}; + + /** + * Return the Configuration. + * @param source The InputSource. + * @return The Configuration. + */ + public Configuration getConfiguration(InputSource source) { + return new MyXMLConfiguration(source, configFile); + } + + /** + * Returns the file suffixes for XML files. + * @return An array of File extensions. + */ + public String[] getSupportedTypes() { + return SUFFIXES; + } +} + +public class MyXMLConfiguration extends XMLConfiguration { + public MyXMLConfiguration(final ConfigurationFactory.ConfigurationSource configSource) { + super(configSource); + } + + @Override + protected void doConfigure() { + super.doConfigure(); + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Layout layout = PatternLayout.createDefaultLayout(config); + final Appender appender = FileAppender.createAppender("target/test.log", "false", "false", "File", "true", + "false", "false", "4000", layout, null, "false", null, config); + appender.start(); + addAppender(appender); + LoggerConfig loggerConfig = LoggerConfig.createLogger("false", "info", "org.apache.logging.log4j", + "true", refs, null, config, null ); + loggerConfig.addAppender(appender, null, null); + addLogger("org.apache.logging.log4j", loggerConfig); + } +} +---- + +[#AddingToCurrent] +== Programmatically Modifying the Current Configuration after Initialization + +Applications sometimes have the need to customize logging separate from +the actual configuration. Log4j allows this although it suffers from a +few limitations: + +1. If the configuration file is changed the configuration will be +reloaded and the manual changes will be lost. +2. Modification to the running configuration requires that all the +methods being called (addAppender and addLogger) be synchronized. + +As such, the recommended approach for customizing a configuration is to +extend one of the standard Configuration classes, override the setup +method to first do super.setup() and then add the custom Appenders, +Filters and LoggerConfigs to the configuration before it is registered +for use. + +The following example adds an Appender and a new LoggerConfig using that +Appender to the current configuration. + +//TODO: update code example below with new plugin API +[source,java] +---- + final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + final Configuration config = ctx.getConfiguration(); + Layout layout = PatternLayout.createDefaultLayout(config); + Appender appender = FileAppender.createAppender("target/test.log", "false", "false", "File", "true", + "false", "false", "4000", layout, null, "false", null, config); + appender.start(); + config.addAppender(appender); + AppenderRef ref = AppenderRef.createAppenderRef("File", null, null); + AppenderRef[] refs = new AppenderRef[] {ref}; + LoggerConfig loggerConfig = LoggerConfig.createLogger("false", "info", "org.apache.logging.log4j", + "true", refs, null, config, null ); + loggerConfig.addAppender(appender, null, null); + config.addLogger("org.apache.logging.log4j", loggerConfig); + ctx.updateLoggers(); +} +---- + +[#AppendingToWritersAndOutputStreams] +== Appending Log Events to Writers and OutputStreams Programmatically + +Log4j 2.5 provides facilities to append log events to Writers and +OutputStreams. For example, this provides simple integration for JDBC +Driver implementors that use Log4j internally and still want to support +the JDBC APIs `CommonDataSource.setLogWriter(PrintWriter)`, +`java.sql.DriverManager.setLogWriter(PrintWriter)`, and +`java.sql.DriverManager.setLogStream(PrintStream)`. + +Given any `Writer`, like a `PrintWriter`, you tell Log4j to append +events to that writer by creating a `WriterAppender` and updating the +Log4j configuration: + +[source,java] +---- +void addAppender(final Writer writer, final String writerName) { + final LoggerContext context = LoggerContext.getContext(false); + final Configuration config = context.getConfiguration(); + final PatternLayout layout = PatternLayout.createDefaultLayout(config); + final Appender appender = WriterAppender.createAppender(layout, null, writer, writerName, false, true); + appender.start(); + config.addAppender(appender); + updateLoggers(appender, config); +} + +private void updateLoggers(final Appender appender, final Configuration config) { + final Level level = null; + final Filter filter = null; + for (final LoggerConfig loggerConfig : config.getLoggers().values()) { + loggerConfig.addAppender(appender, level, filter); + } + config.getRootLogger().addAppender(appender, level, filter); +} +---- + +You can achieve the same effect with an `OutputStream`, like a +`PrintStream`: + +[source,java] +---- +void addAppender(final OutputStream outputStream, final String outputStreamName) { + final LoggerContext context = LoggerContext.getContext(false); + final Configuration config = context.getConfiguration(); + final PatternLayout layout = PatternLayout.createDefaultLayout(config); + final Appender appender = OutputStreamAppender.createAppender(layout, null, outputStream, outputStreamName, false, true); + appender.start(); + config.addAppender(appender); + updateLoggers(appender, config); +} +---- + +The difference is the use of `OutputStreamAppender` instead of +`WriterAppender`. diff --git a/src/site/asciidoc/manual/customloglevels.adoc b/src/site/asciidoc/manual/customloglevels.adoc new file mode 100644 index 00000000000..c8c5ad11d26 --- /dev/null +++ b/src/site/asciidoc/manual/customloglevels.adoc @@ -0,0 +1,323 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Custom Log Levels +Remko Popma + +[[top]] + +[#DefiningLevelsInCode] +== Defining Custom Log Levels in Code + +Log4j 2 supports custom log levels. Custom log levels can be defined in +code or in configuration. To define a custom log level in code, use the +`Level.forName()` method. This method creates a new level for the +specified name. After a log level is defined you can log messages at +this level by calling the `Logger.log()` method and passing the custom log +level: + +[source,java] +---- +// This creates the "VERBOSE" level if it does not exist yet. +final Level VERBOSE = Level.forName("VERBOSE", 550); + +final Logger logger = LogManager.getLogger(); +logger.log(VERBOSE, "a verbose message"); // use the custom VERBOSE level + +// Create and use a new custom level "DIAG". +logger.log(Level.forName("DIAG", 350), "a diagnostic message"); + +// Use (don't create) the "DIAG" custom level. +// Only do this *after* the custom level is created! +logger.log(Level.getLevel("DIAG"), "another diagnostic message"); + +// Using an undefined level results in an error: Level.getLevel() returns null, +// and logger.log(null, "message") throws an exception. +logger.log(Level.getLevel("FORGOT_TO_DEFINE"), "some message"); // throws exception! +---- + +When defining a custom log level, the `intLevel` parameter (550 and 350 +in the example above) determines where the custom level exists in +relation to the standard levels built-in to Log4j 2. For reference, the +table below shows the `intLevel` of the built-in log levels. + +.Standard log levels built-in to Log4j +[cols=",>",options="header"] +|======================== +|Standard Level |intLevel +|OFF |0 +|FATAL |100 +|ERROR |200 +|WARN |300 +|INFO |400 +|DEBUG |500 +|TRACE |600 +|ALL |`Integer.MAX_VALUE` +|======================== + +[#DefiningLevelsInConfiguration] +== Defining Custom Log Levels in Configuration + +Custom log levels can also be defined in configuration. This is +convenient for using a custom level in a logger filter or an appender +filter. Similar to defining log levels in code, a custom level must be +defined first, before it can be used. If a logger or appender is +configured with an undefined level, that logger or appender will be +invalid and will not process any log events. + +The *CustomLevel* configuration element creates a custom level. +Internally it calls the same `Level.forName()` method discussed above. + +.CustomLevel Parameters +[cols="m,,4",options="header"] +|=== +|Parameter Name |Type |Description + +|name +|String +|The name of the custom level. Note that level names are +case sensitive. The convention is to use all upper-case names. + +|intLevel +|integer +|Determines where the custom level exists in relation +to the standard levels built-in to Log4j 2 (see the table above). +|=== + +The following example shows a configuration that defines some custom log +levels and uses a custom log level to filter log events sent to the +console. + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +[#StandardLoggerInterface] +== Convenience Methods for the Built-in Log Levels + +The built-in log levels have a set of convenience methods on the Logger +interface that makes them easier to use. For example, the Logger +interface has 24 `debug()` methods that support the DEBUG level: + +[source,java] +---- +// convenience methods for the built-in DEBUG level +debug(Marker, Message) +debug(Marker, Message, Throwable) +debug(Marker, Object) +debug(Marker, Object, Throwable) +debug(Marker, String) +debug(Marker, String, Object...) +debug(Marker, String, Throwable) +debug(Message) +debug(Message, Throwable) +debug(Object) +debug(Object, Throwable) +debug(String) +debug(String, Object...) +debug(String, Throwable) +// lambda support methods added in 2.4 +debug(Marker, MessageSupplier) +debug(Marker, MessageSupplier, Throwable) +debug(Marker, String, Supplier...) +debug(Marker, Supplier) +debug(Marker, Supplier, Throwable) +debug(MessageSupplier) +debug(MessageSupplier, Throwable) +debug(String, Supplier...) +debug(Supplier) +debug(Supplier, Throwable) +---- + +Similar methods exist for the other built-in levels. Custom levels, in +contrast, need to pass in the log level as an extra parameter. + +[source,java] +---- +// need to pass the custom level as a parameter +logger.log(VERBOSE, "a verbose message"); +logger.log(Level.forName("DIAG", 350), "another message"); +---- + +It would be nice to have the same ease of use with custom levels, so +that after declaring the custom VERBOSE/DIAG levels, we could use code +like this: + +[source,java] +---- +// nice to have: descriptive methods and no need to pass the level as a parameter +logger.verbose("a verbose message"); +logger.diag("another message"); +logger.diag("java 8 lambda expression: {}", () -> someMethod()); +---- + +The standard Logger interface cannot provide convenience methods for +custom levels, but the next few sections introduce a code generation +tool to create loggers that aim to make custom levels as easy to use as +built-in levels. + +[#AddingOrReplacingLevels] +== Adding or Replacing Log Levels + +We assume that most users want to _add_ custom level methods to the +Logger interface, in addition to the existing `trace()`, `debug()`, `info()`, +... methods for the built-in log levels. + +There is another use case, Domain Specific Language loggers, where we +want to _replace_ the existing `trace()`, `debug()`, `info()`, ... methods +with all-custom methods. + +For example, for medical devices we could have only `critical()`, +`warning()`, and `advisory()` methods. Another example could be a game +that has only `defcon1()`, `defcon2()`, and `defcon3()` levels. + +If it were possible to hide existing log levels, users could customize +the Logger interface to match their requirements. Some people may not +want to have a FATAL or a TRACE level, for example. They would like to +be able to create a custom Logger that only has `debug()`, `info()`, `warn()` +and `error()` methods. + +[#CustomLoggers] +== Generating Source Code for a Custom Logger Wrapper + +Common Log4j usage is to get an instance of the `Logger` interface from +the `LogManager` and call the methods on this interface. However, the +custom log Levels are not known in advance, so Log4j cannot provide an +interface with convenience methods for these custom log Levels. + +To solve this, Log4j ships with a tool that generates source code for a +Logger wrapper. The generated wrapper class has convenience methods for +each custom log level, making custom levels just as easy to use as the +built-in levels. + +There are two flavors of wrappers: ones that _*extend*_ the Logger API +(adding methods to the built-in levels) and ones that _*customize*_ the +Logger API (replacing the built-in methods). + +When generating the source code for a wrapper class, you need to +specify: + +* the fully qualified name of the class to generate +* the list of custom levels to support and their `intLevel` relative +strength +* whether to extend `Logger` (and keep the existing built-in methods) or +have only methods for the custom log levels + +You would then include the generated source code in the project where +you want to use custom log levels. + +[#ExampleUsage] +== Example Usage of a Generated Logger Wrapper + +Here is an example of how one would use a generated logger wrapper with +custom levels DIAG, NOTICE and VERBOSE: + +[source,java] +---- +// ExtLogger is a generated logger wrapper +import com.mycompany.myproject.ExtLogger; + +public class MyService { + // instead of Logger logger = LogManager.getLogger(MyService.class): + private static final ExtLogger logger = ExtLogger.create(MyService.class); + + public void demoExtendedLogger() { + // ... + logger.trace("the built-in TRACE level"); + logger.verbose("a custom level: a VERBOSE message"); + logger.debug("the built-in DEBUG level"); + logger.notice("a custom level: a NOTICE message"); + logger.info("the built-in INFO level"); + logger.diag("a custom level: a DIAG message"); + logger.warn("the built-in WARN level"); + logger.error("the built-in ERROR level"); + logger.fatal("the built-in FATAL level"); + logger.notice("java 8 lambda expression only executed if NOTICE is enabled: {}", () -> someMethod()); + // ... + } + ... +} +---- + +[#CodeGen] +== Generating Extended Loggers + +Use the following command to generate a logger wrapper that adds methods +to the built-in ones: + +[source,sh,subs="attributes"] +---- +java -cp log4j-core-{Log4jReleaseVersion}.jar org.apache.logging.log4j.core.tools.ExtendedLoggerGenerator \ + com.mycomp.ExtLogger DIAG=350 NOTICE=450 VERBOSE=550 > com/mycomp/ExtLogger.java +---- + +This will generate source code for a logger wrapper that has the +convenience methods for the built-in levels _as well as_ the specified +custom levels. The tool prints the generated source code to the console. +By appending " > _filename_" the output can be redirected to a file. + +NOTE: Prior to log4j-2.9, this tool was an inner class +`Generate$ExtendedLogger`. + +Under the bash shell on Unix/Mac/Linux the dollar character $ needs to +be escaped, so the class name should be between single quotes +'org.apache.logging.log4j.core.tools.Generate$ExtendedLogger’. + +== Generating Custom Loggers + +Use the following command to generate a logger wrapper that hides the +built-in levels and has only custom levels: + +[source,sh,subs="attributes"] +---- +java -cp log4j-core-{Log4jReleaseVersion}.jar org.apache.logging.log4j.core.tools.CustomLoggerGenerator \ + com.mycomp.MyLogger DEFCON1=350 DEFCON2=450 DEFCON3=550 > com/mycomp/MyLogger.java +---- + +This will generate source code for a logger wrapper that _only_ has +convenience methods for the specified custom levels, _not_ for the +built-in levels. The tool prints the generated source code to the +console. By appending " > _filename_" the output can be redirected to a +file. + +NOTE: Prior to log4j-2.9, this tool was an inner class `Generate$ExtendedLogger`. +Under the bash shell on Unix/Mac/Linux the dollar character $ needs to +be escaped, so the class name should be between single quotes +'org.apache.logging.log4j.core.tools.Generate$CustomLogger’. diff --git a/src/site/asciidoc/manual/dependencyinjection.adoc b/src/site/asciidoc/manual/dependencyinjection.adoc new file mode 100644 index 00000000000..9d922d93269 --- /dev/null +++ b/src/site/asciidoc/manual/dependencyinjection.adoc @@ -0,0 +1,154 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Dependency Injection +Matt Sicker + +Log4j 3.x uses a more sophisticated dependency injection API inspired by https://javaee.github.io/javaee-spec/javadocs/javax/inject/package-summary.html[`javax.inject`] and its open source implementations such as https://github.com/zsoltherpai/feather[Feather] and https://github.com/google/guice[Guice]. +Dependency injection can be thought of as a replacement for calling `new` or custom factory methods in Java code; alternatively, it can be thought of as a factory for factory instances. +Classes define _injection points_ which may be fields, method parameters, and constructor parameters, whose values are injected at runtime. +Classes may define _factory methods_ which provide a way to create instances of their return type while allowing for dependency injection of the factory method's arguments. +Injector callback services may be registered for customizing the dependency injection system at initialization. + +== Overview + +The `org.apache.logging.log4j.plugins.di.Injector` runtime keeps a registry of bindings between `Key` and `Supplier`, where `org.apache.logging.log4j.plugins.di.Key` describes an instance type `T`, optional qualifier annotation type, and optional name plus namespace, and `java.util.function.Supplier` defines a factory for obtaining instances of `T` matching the corresponding `Key`. +To obtain a factory for a given key, if a binding is not already registered for said key, then the raw class of the key (i.e., the `Class` instance from `Key`) is checked for a single constructor annotated with `@org.apache.logging.log4j.plugins.Inject` or a no-arg constructor if no `@Inject` constructor is present. +The injectable constructor is invoked with arguments which are obtained by looking up factories matching the `Key` of each constructor parameter. +Then, fields that are annotated with `@Inject` or annotated with an annotation that is itself annotated with `@QualifierType` (i.e., a _qualifier annotation_) are injected from factories matching the `Key` of the field. +Next, methods that are annotated with `@Inject` or have a parameter annotated with a qualifier annotation are invoked using factories matching the `Key` of each parameter. +Finally, no-arg methods annotated with `@Inject` are invoked which can be useful for post-injection initialization logic. + +Bindings may be registered using `Injector::registerBinding` or `Injector::registerBindingIfAbsent` using explicit `Key` instances, or they may be registered using `Injector::registerBundle` as _bundles_ which are instances or injectable `Class` instances that contain one or more annotated factory methods. +Annotated factory methods are non-abstract methods annotated with an annotation that is itself annotated with `@org.apache.logging.log4j.plugins.FactoryType`. +Built-in factory annotations include `@org.apache.logging.log4j.plugins.Factory` and `@org.apache.logging.log4j.plugins.PluginFactory`. + +`Injector` can create `@Configurable` namespace plugins from a tree of `org.apache.logging.log4j.plugins.Node` instances using `Injector::configure`. +This allows for node attributes and child nodes to be injected into a plugin class along with any other registered bindings. +To configure a `Node`, its plugin class is checked for a static method annotated with a factory annotation (such as `@PluginFactory` or `@Factory`) first before falling back to checking for an `@Inject` constructor or a no-args constructor. +This static factory method or constructor has its parameters injected. +If the return value of a static plugin factory method implements `java.util.function.Supplier`, then member injection is performed on the instance and the return value of `Supplier::get` is returned instead. +See link:./plugins.html[Plugins] for more information. + +`Injector` can inject members into an arbitrary object instance by using `Injector::injectMembers`. +This can be useful for injecting Log4j-managed instances into external or application code. +An `Injector` can be obtained via `LoggerContext::getInjector`. + +=== Injection Points + +Injection points are injectable fields or parameters where a dependency should be injected. +_Injectable fields_ are fields annotated with `@Inject` or a qualifier annotation. +_Injectable methods_ are methods annotated with `@Inject` or are not annotated with a factory annotation and have at least one parameter annotated with a qualifier annotation. +_Injectable constructors_ are constructors annotated with `@Inject`; only one such constructor should exist per class. +When a field or parameter is annotated with a name-providing annotation (i.e., an annotation annotated with `@org.apache.logging.log4j.plugins.name.NameProvider`), then the provided name or name of the field or parameter are included in the `Key` for the injection point. +When these elements are annotated with a `@Namespace` annotation or meta-annotation, then that namespace name is included in the `Key` for the injection point. +Similarly, when a field or parameter is annotated with a qualifier annotation, then that qualifier annotation type is included in the `Key` for the injection point. + +An example of each injection point with a no-arg `@Inject` method to show the post-injected state: + +[source,java] +---- +class ExampleBean { + @Override + public String toString() { + return "ExampleBean"; + } +} + +class FieldInjection { + @Inject + ExampleBean exampleBean; + + @Inject + void init() { + System.out.println(exampleBean); + } +} + +class ConstructorInjection { + final ExampleBean bean; + + @Inject + ConstructorInjection(final ExampleBean exampleBean) { + bean = exampleBean; + } + + @Inject + void init() { + System.out.println(exampleBean); + } +} + +class MethodInjection { + ExampleBean bean; + + @Inject + void setBean(final ExampleBean exampleBean) { + bean = exampleBean; + } + + @Inject + void init() { + System.out.println(exampleBean); + } +} +---- + +=== Names and Qualifiers + +Qualifiers are annotations that are annotated with `@org.apache.logging.log4j.plugins.QualifierType`. +Qualifiers provide a way to match dependencies and factories based on more than just their type. +For example, the `@org.apache.logging.log4j.plugins.Named` qualifier allows for creating different bindings of the same type with different names (along with support for aliases). +Qualifiers on an injection point request a binding with that qualifier type and name. +Qualifiers on a factory method register a binding with that qualifier type and name. +The name for a qualifier is provided via an `org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider` strategy class given in the `@NameProvider` annotation declared on the qualifier annotation. +Aliases are likewise provided via an `org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider` strategy class given in the `@AliasesProvider` annotated declared on the qualifier annotation. + +=== Scopes + +Scopes control the lifecycle of instances returned from a factory. +Scopes correspond to an annotation that is annotated with `@org.apache.logging.log4j.plugins.ScopeType`. +By default, `@org.apache.logging.log4j.plugins.Singleton` and the default scope are supported. +The `@Singleton` scope ensures that only a single instance is returned from factories bound in that scope. +The default scope returns a new instance every time. +Additional scopes may be registered via `Injector::registerScope`. + +== Injector Callbacks + +An `Injector` may be initialized with `org.apache.logging.log4j.plugins.di.InjectorCallback` service classes. +These service classes must be declared in their respective `module-info.java` files containing `provides org.apache.logging.log4j.plugins.di.InjectorCallback with my.fully.qualified.ClassName;` and should also be declared in a file named `META-INF/services/org.apache.logging.log4j.plugins.di.InjectorCallback` containing the line `my.fully.qualified.ClassName` for traditional classpath usage. +Callback services are invoked in the order defined by each `InjectorCallback::getOrder` value in the natural integer order (from `Integer.MIN_VALUE` to `Integer.MAX_VALUE`). +Each callback is given the `Injector` that invoked `Injector::init` where it can be introspected and modified. +Log4j includes one such callback with an order value of 0 which supports registering callbacks that invoke before or after this default. +This default callback registers default bindings for various keys if none are already registered. + +=== Configurable Bindings + +The default callback sets up bindings for the following keys if none have been registered. +Some of these bindings were previously configured through various system properties which are supported via the default callback and its default bindings, though they can be directly registered via custom callbacks with a negative order value. + +* `org.apache.logging.log4j.core.ContextDataInjector` +* `org.apache.logging.log4j.core.config.ConfigurationFactory` +* `org.apache.logging.log4j.core.config.composite.MergeStrategy` +* `org.apache.logging.log4j.core.impl.LogEventFactory` +* `org.apache.logging.log4j.core.lookup.InterpolatorFactory` +* `org.apache.logging.log4j.core.lookup.StrSubstitutor` +* `org.apache.logging.log4j.core.selector.ContextSelector` +* `org.apache.logging.log4j.core.time.Clock` +* `org.apache.logging.log4j.core.time.NanoClock` +* `org.apache.logging.log4j.core.util.ShutdownCallbackRegistry` +* `org.apache.logging.log4j.core.util.WatchManager` +* `org.apache.logging.log4j.core.config.ConfigurationScheduler` diff --git a/src/site/asciidoc/manual/eventlogging.adoc b/src/site/asciidoc/manual/eventlogging.adoc new file mode 100644 index 00000000000..d45fe7158bf --- /dev/null +++ b/src/site/asciidoc/manual/eventlogging.adoc @@ -0,0 +1,142 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Log4j 2 API +Ralph Goers + +[#EventLogging] +== Event Logging + +The `EventLogger` class provides a simple mechanism for logging events +that occur in an application. While the `EventLogger` is useful as a way +of initiating events that should be processed by an audit Logging +system, by itself it does not implement any of the features an audit +logging system would require such as guaranteed delivery. + +The recommended way of using the `EventLogger` in a typical web +application is to populate the `ThreadContext` Map with data that is +related to the entire lifespan of the request such as the user's id, the +user's IP address, the product name, etc. This can easily be done in a +servlet filter where the `ThreadContext` Map can also be cleared at the +end of the request. When an event that needs to be recorded occurs a +`StructuredDataMessage` should be created and populated. Then call +`EventLogger.logEvent(msg)` where msg is a reference to the +`StructuredDataMessage`. + +[source,java] +---- +import org.apache.logging.log4j.ThreadContext; +import org.apache.commons.lang.time.DateUtils; + +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.FilterChain; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.TimeZone; + +public class RequestFilter implements Filter { + private FilterConfig filterConfig; + private static String TZ_NAME = "timezoneOffset"; + + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + } + + /** + * Sample filter that populates the MDC on every request. + */ + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest)servletRequest; + HttpServletResponse response = (HttpServletResponse)servletResponse; + ThreadContext.put("ipAddress", request.getRemoteAddr()); + HttpSession session = request.getSession(false); + TimeZone timeZone = null; + if (session != null) { + // Something should set this after authentication completes + String loginId = (String)session.getAttribute("LoginId"); + if (loginId != null) { + ThreadContext.put("loginId", loginId); + } + // This assumes there is some javascript on the user's page to create the cookie. + if (session.getAttribute(TZ_NAME) == null) { + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if (TZ_NAME.equals(cookie.getName())) { + int tzOffsetMinutes = Integer.parseInt(cookie.getValue()); + timeZone = TimeZone.getTimeZone("GMT"); + timeZone.setRawOffset((int)(tzOffsetMinutes * DateUtils.MILLIS_PER_MINUTE)); + request.getSession().setAttribute(TZ_NAME, tzOffsetMinutes); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + } + } + } + } + ThreadContext.put("hostname", servletRequest.getServerName()); + ThreadContext.put("productName", filterConfig.getInitParameter("ProductName")); + ThreadContext.put("locale", servletRequest.getLocale().getDisplayName()); + if (timeZone == null) { + timeZone = TimeZone.getDefault(); + } + ThreadContext.put("timezone", timeZone.getDisplayName()); + filterChain.doFilter(servletRequest, servletResponse); + ThreadContext.clear(); + } + + public void destroy() { + } +} +---- + +Sample class that uses `EventLogger`. + +[source,java] +---- +import org.apache.logging.log4j.StructuredDataMessage; +import org.apache.logging.log4j.EventLogger; + +import java.util.Date; +import java.util.UUID; + +public class MyApp { + + public String doFundsTransfer(Account toAccount, Account fromAccount, long amount) { + toAccount.deposit(amount); + fromAccount.withdraw(amount); + String confirm = UUID.randomUUID().toString(); + StructuredDataMessage msg = new StructuredDataMessage(confirm, null, "transfer"); + msg.put("toAccount", toAccount); + msg.put("fromAccount", fromAccount); + msg.put("amount", amount); + EventLogger.logEvent(msg); + return confirm; + } +} +---- + +The `EventLogger` class uses a `Logger` named "EventLogger". `EventLogger` +uses a logging level of OFF as the default to indicate that it cannot be +filtered. These events can be formatted for printing using the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/layout/StructuredDataLayout.html[`StructuredDataLayout`]. diff --git a/src/site/asciidoc/manual/extending.adoc b/src/site/asciidoc/manual/extending.adoc new file mode 100644 index 00000000000..70704130b3f --- /dev/null +++ b/src/site/asciidoc/manual/extending.adoc @@ -0,0 +1,623 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Extending Log4j 3 +Ralph Goers + +Log4j 3 provides numerous ways that it can be manipulated and extended. +This section includes an overview of the various ways that are directly +supported by the Log4j 3 implementation. + +[#LoggerContextFactory] +== LoggerContextFactory + +The `LoggerContextFactory` binds the Log4j API to its implementation. +The Log4j `LogManager` locates a `LoggerContextFactory` by using +`java.util.ServiceLoader` to locate all instances of +`org.apache.logging.log4j.spi.Provider`. Each implementation must +provide a class that extends `org.apache.logging.log4j.spi.Provider` and +should have a no-arg constructor that delegates to Provider's +constructor passing the Priority, the API versions it is compatible +with, and the class that implements +`org.apache.logging.log4j.spi.LoggerContextFactory`. Log4j will compare +the current API version and if it is compatible the implementation +will be added to the list of providers. The API version in +`org.apache.logging.log4j.LogManager` is only changed when a feature is +added to the API that implementations need to be aware of. If more than +one valid implementation is located the value for the Priority will be +used to identify the factory with the highest priority. Finally, the +class that implements +`org.apache.logging.log4j.spi.LoggerContextFactory` will be instantiated +and bound to the LogManager. In Log4j 2 this is provided by +`Log4jContextFactory`. + +Applications may change the LoggerContextFactory that will be used by + +1. Create a binding to the logging implementation. +.. Implement a new link:../log4j-core/apidocs/org/apache/logging/log4j/core/impl/Log4jContextFactory.html[`LoggerContextFactory`]. +.. Implement a class that extends link:../log4j-core/apidocs/org/apache/logging/spi/Provider.html[`org.apache.logging.spi.Provider`] +with a no-arg constructor that calls super-class's constructor with the +Priority, the API version(s), `LoggerContextFactory` class, and +optionally, a link:../log4j-core/apidocs/org/apache/logging/log4j/spi/ThreadContextMap.html[`ThreadContextMap`] implementation class. +.. Create a `META-INF/services/org.apache.logging.spi.Provider` file +that contains the name of the class that implements +`org.apache.logging.spi.Provider`. +2. Setting the system property "log4j2.loggerContextFactory" to the name +of the `LoggerContextFactory` class to use. +3. Setting the property "log4j2.loggerContextFactory" in a properties +file named "log4j2.LogManager.properties" to the name of the +LoggerContextFactory class to use. The properties file must be on the +classpath. + +[#InjectorCallback] +== InjectorCallback +InjectorCallback services are called by an link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/di/Injector.html[`Injector`] when its `init()` method is invoked (see link:./dependencyinjection.html[Dependency Injection] for further information about `Injector`). +These callback services are invoked in order using `InjectorCallback::getOrder` and the natural integer comparator. +Callbacks may examine the current state of the invoking `Injector` as well as make changes to bindings or provide default bindings. +Backwards compatibility with system property based methods for configuring various extensions are handled through a default callback with order 0 which allows for making customizations before or after the defaults are set up. +Callback classes must have a no-args constructor and need to be defined in two places: + +1. Add the fully qualified class name of the callback class to a file named `META-INF/services/org.apache.logging.log4j.plugins.di.InjectorCallback`. +2. For compatibility with Java modules, add a line to `module-info.java` containing `provides org.apache.logging.log4j.plugins.di.InjectorCallback with my.fully.qualified.ClassName;`. + +[#ContextSelector] +== ContextSelector + +ContextSelectors are called by the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/impl/Log4jContextFactory.html[Log4j +LoggerContext factory]. They perform the actual work of locating or +creating a LoggerContext, which is the anchor for Loggers and their +configuration. ContextSelectors are free to implement any mechanism they +desire to manage LoggerContexts. The default Log4jContextFactory checks +for the presence of an `Injector` binding for `ContextSelector`. +If none are defined, the System Property named "Log4jContextSelector" is checked. +If found, the property is expected to contain the name of the Class that implements the ContextSelector to be used. +This class is then used for creating `ContextSelector` instances. + +Log4j provides five ContextSelectors: + +link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/BasicContextSelector.html[`BasicContextSelector`]:: + Uses either a LoggerContext that has been stored in a ThreadLocal or a + common LoggerContext. +link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[`ClassLoaderContextSelector`]:: + Associates LoggerContexts with the ClassLoader that created the caller + of the getLogger(...) call. This is the default ContextSelector. +link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/JndiContextSelector.html[`JndiContextSelector`]:: + Locates the LoggerContext by querying JNDI. Please see link:../manual/configuration.html#allowedJndiProtocols[log4j2.allowedJndiProtocols], +link:../manual/configuration.html#allowedLdapClasses[log4j2.allowedLdapClasses], and +link:../manual/configuration.html#allowedLdapHosts[log4j2.allowedLdapHosts] for restrictions on using JNDI +with Log4j. +link:../log4j-core/apidocs/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.html[`AsyncLoggerContextSelector`]:: + Creates a LoggerContext that ensures that all loggers are + AsyncLoggers. +link:../log4j-core/apidocs/org/apache/logging/log4j/core/osgi/BundleContextSelector.html[`BundleContextSelector`]:: + Associates LoggerContexts with the ClassLoader of the bundle that + created the caller of the getLogger call. This is enabled by default + in OSGi environments. + +[#ConfigurationFactory] +== ConfigurationFactory + +Modifying the way in which logging can be configured is usually one of +the areas with the most interest. The primary method for doing that is +by implementing or extending a +link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/ConfigurationFactory.html[`ConfigurationFactory`]. +Log4j provides two ways of adding new ConfigurationFactories. The first +is by defining the system property named "log4j.configurationFactory" to +the name of the class that should be searched first for a configuration. +The second method is by defining the `ConfigurationFactory` as a `Plugin`. + +All the ConfigurationFactories are then processed in order. Each factory +is called on its `getSupportedTypes()` method to determine the file +extensions it supports. If a configuration file is located with one of +the specified file extensions then control is passed to that +`ConfigurationFactory` to load the configuration and create the link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/Configuration.html[`Configuration`] object. + +Most `Configuration` extend the link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/AbstractConfiguration.html[`AbstractConfiguration`] class. This class expects that the subclass will process the configuration file and create +a hierarchy of `Node` objects. Each `Node` is fairly simple in that it +consists of the name of the node, the name/value pairs associated with +the node, The `PluginType` of the node and a List of all of its child +Nodes. `Configuration` will then be passed the `Node` tree and +instantiate the configuration objects from that. + +[source,java] +---- +@Namespace("ConfigurationFactory") +@Plugin("XMLConfigurationFactory") +@Order(5) +public class XMLConfigurationFactory extends ConfigurationFactory { + + /** + * Valid file extensions for XML files. + */ + public static final String[] SUFFIXES = new String[] {".xml", "*"}; + + /** + * Returns the Configuration. + * @param loggerContext The logger context. + * @param source The InputSource. + * @return The Configuration. + */ + @Override + public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) { + return new XmlConfiguration(loggerContext, source); + } + + /** + * Returns the file suffixes for XML files. + * @return An array of File extensions. + */ + public String[] getSupportedTypes() { + return SUFFIXES; + } +} +---- + +[#LoggerConfig] +== LoggerConfig + +`LoggerConfig` objects are where Loggers created by applications tie into +the configuration. The Log4j implementation requires that all +LoggerConfigs are based on the LoggerConfig class, so applications +wishing to make changes must do so by extending the `LoggerConfig` class. +To declare the new `LoggerConfig`, declare it as a Plugin of type "Core" +and providing the name that applications should specify as the element +name in the configuration. The `LoggerConfig` should also define a +PluginFactory that will create an instance of the `LoggerConfig`. + +The following example shows how the root `LoggerConfig` simply extends a +generic `LoggerConfig`. + +[source,java] +---- +@Configurable(printObject = true) +@Plugin("root") +public static class RootLogger extends LoggerConfig { + + @PluginFactory + public static LoggerConfig createLogger(@PluginAttribute(defaultBooleanValue = true) boolean additivity, + @PluginAttribute(defaultStringValue = "ERROR") Level level, + @PluginElement AppenderRef[] refs, + @PluginElement Filter filter) { + List appenderRefs = Arrays.asList(refs); + return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, level, additivity); + } +} +---- + +[#LogEventFactory] +== LogEventFactory + +A `LogEventFactory` is used to generate LogEvents. +Applications may replace the standard `LogEventFactory` by binding a factory for `LogEventFactory` in `Injector`. +If no binding is already present, the value of the system property `log4j2.logEventFactory` is checked for the name of the custom `LogEventFactory` class. + +Note: When log4j is configured to have link:async.html#AllAsync[all +loggers asynchronous], log events are pre-allocated in a ring buffer and +the `LogEventFactory` is not used. + +[#MessageFactory] +== MessageFactory + +A `MessageFactory` is used to generate `Message` objects. Applications may +replace the standard `ParameterizedMessageFactory` (or +`ReusableMessageFactory` in garbage-free mode) by setting the value of the +system property `log4j2.messageFactory` to the name of the custom +`MessageFactory` class. + +Flow messages for the `Logger.entry()` and `Logger.exit()` methods have +a separate `FlowMessageFactory`. Applications may replace the +`DefaultFlowMessageFactory` by setting the value of the system property +`log4j2.flowMessageFactory` to the name of the custom `FlowMessageFactory` +class. + +[#Lookups] +== Lookups + +Lookups are the means in which parameter substitution is performed. +During Configuration initialization an "Interpolator" is created that +locates all the Lookups and registers them for use when a variable needs +to be resolved. The interpolator matches the "prefix" portion of the +variable name to a registered Lookup and passes control to it to resolve +the variable. + +A Lookup must be declared using a `@Plugin @Lookup` annotation. The `value` specified on the `@Plugin` annotation will be used to +match the prefix. The example below shows a Lookup that will return +the value of a System Property. + +The provided Lookups are documented here: link:./lookups.html[Lookups] + +[source,java] +---- +@Lookup +@Plugin("sys") +public class SystemPropertiesLookup implements StrLookup { + + /** + * Lookup the value for the key. + * @param key the key to be looked up, may be null + * @return The value for the key. + */ + public String lookup(String key) { + return System.getProperty(key); + } + + /** + * Lookup the value for the key using the data in the LogEvent. + * @param event The current LogEvent. + * @param key the key to be looked up, may be null + * @return The value associated with the key. + */ + public String lookup(LogEvent event, String key) { + return System.getProperty(key); + } +} +---- + +[#Filters] +== Filters + +As might be expected, Filters are used to reject or accept log +events as they pass through the logging system. A Filter is declared +using a `@Configurable` annotation with an `elementType` of "filter". +The `value` attribute on the `@Plugin` annotation is used to specify the name +of the element users should use to enable the Filter. Specifying the +`printObject` attribute with a value of "true" indicates that a call to +`toString` will format the arguments to the filter as the configuration is +being processed. The Filter must also specify a `@PluginFactory` method +or `@PluginFactoryBuilder` builder class and method +that will be called to create the Filter. + +The example below shows a Filter used to reject LogEvents based upon +their logging level. Notice the typical pattern where all the filter +methods resolve to a single filter method. + +[source,java] +---- +@Configurable(elementType = Filter.ELEMENT_TYPE, printObject = true) +@Plugin +public final class ThresholdFilter extends AbstractFilter { + + private final Level level; + + private ThresholdFilter(Level level, Result onMatch, Result onMismatch) { + super(onMatch, onMismatch); + this.level = level; + } + + public Result filter(Logger logger, Level level, Marker marker, String msg, Object[] params) { + return filter(level); + } + + public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) { + return filter(level); + } + + public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) { + return filter(level); + } + + @Override + public Result filter(LogEvent event) { + return filter(event.getLevel()); + } + + private Result filter(Level level) { + return level.isAtLeastAsSpecificAs(this.level) ? onMatch : onMismatch; + } + + @Override + public String toString() { + return level.toString(); + } + + /** + * Create a ThresholdFilter. + * @param level The log Level. + * @param onMatch The action to take on a match. + * @param onMismatch The action to take on a mismatch. + * @return The created ThresholdFilter. + */ + @PluginFactory + public static ThresholdFilter createFilter(@PluginAttribute(defaultStringValue = "ERROR") Level level, + @PluginAttribute(defaultStringValue = "NEUTRAL") Result onMatch, + @PluginAttribute(defaultStringValue = "DENY") Result onMismatch) { + return new ThresholdFilter(level, onMatch, onMismatch); + } +} +---- + +[#Appenders] +== Appenders + +Appenders are passed an event, (usually) invoke a Layout to format the +event, and then "publish" the event in whatever manner is desired. +Appenders are declared as `@Configurable` with an +`elementType` of "appender". The `value` attribute on the `@Plugin` annotation +specifies the name of the element users must provide in their +configuration to use the Appender. Appenders should specify `printObject` +as "true" if the toString method renders the values of the attributes +passed to the Appender. + +Appenders must also declare a `@PluginFactory` method that returns an instance +of the appender or a builder class used to create the appender. The example below shows +an Appender named "Stub" that can be used as an initial template. + +Most Appenders use Managers. A manager actually "owns" the resources, +such as an `OutputStream` or socket. When a reconfiguration occurs a new +Appender will be created. However, if nothing significant in the +previous Manager has changed, the new Appender will simply reference it +instead of creating a new one. This insures that events are not lost +while a reconfiguration is taking place without requiring that logging +pause while the reconfiguration takes place. + +[source,java] +---- +@Configurable(elementType = Appender.ELEMENT_TYPE, printObject = true) +@Plugin("Stub") +public final class StubAppender extends AbstractOutputStreamAppender { + + private StubAppender(String name, + Layout layout, + Filter filter, + boolean ignoreExceptions, + StubManager manager) { + super(name, layout, filter, ignoreExceptions, true, manager); + } + + @PluginFactory + public static StubAppender createAppender(@PluginAttribute @Required(message = "No name provided for StubAppender") String name, + @PluginAttribute boolean ignoreExceptions, + @PluginElement Layout layout, + @PluginElement Filter filter) { + + StubManager manager = StubManager.getStubManager(name); + if (manager == null) { + return null; + } + if (layout == null) { + layout = PatternLayout.createDefaultLayout(); + } + return new StubAppender(name, layout, filter, ignoreExceptions, manager); + } +} +---- + +[#Layouts] +== Layouts + +Layouts perform the formatting of events into the printable text that is +written by Appenders to some destination. All Layouts must implement the +`Layout` interface. Layouts that format the event into a `String` should +extend `AbstractStringLayout`, which will take care of converting the +`String` into the required byte array. + +Every Layout must declare itself as a plugin using the `@Plugin` +annotation and a `@Configurable` annotation with an `elementType` of "layout". `printObject` should be set to "true" if the plugin's `toString` +method will provide a representation of the object and its parameters. +The name of the plugin must match the value users should use to specify +it as an element in their Appender configuration. The plugin also must +provide a static method annotated as a `@PluginFactory` and with each of +the methods parameters annotated with `@PluginAttribute` or `@PluginElement` as +appropriate. The plugin can alternatively use the plugin builder notation. + +[source,java] +---- +@Configurable(elementType = Layout.ELEMENT_TYPE, printObject = true) +@Plugin +public class SampleLayout extends AbstractStringLayout { + + protected SampleLayout(boolean locationInfo, boolean properties, boolean complete, + Charset charset) { + super(charset); + // handle the boolean parameters + } + + @PluginFactory + public static SampleLayout createLayout(@PluginAttribute boolean locationInfo, + @PluginAttribute boolean properties, + @PluginAttribute boolean complete, + @PluginAttribute(defaultStringValue = "UTF-8") Charset charset) { + return new SampleLayout(locationInfo, properties, complete, charset); + } +} +---- + +[#PatternConverters] +== PatternConverters + +PatternConverters are used by the PatternLayout to format the log event +into a printable `String`. Each Converter is responsible for a single kind +of manipulation, however Converters are free to format the event in +complex ways. For example, there are several converters that manipulate +Throwables and format them in various ways. + +A PatternConverter must first declare itself as a Plugin using the +standard `@Plugin` annotation and the `@Namespace` annotation with the value "Converter". Furthermore, the Converter must also specify the +`@ConverterKeys` annotation to define the tokens that can be specified in +the pattern (preceded by a '%' character) to identify the Converter. + +Unlike most other Plugins, Converters do not use a `@PluginFactory`. +Instead, each Converter is required to provide a static `newInstance` +method that accepts an array of `String` as the only parameter. The +`String[]` is the values that are specified within the curly braces +that can follow the converter key. + +The following shows the skeleton of a Converter plugin. + +[source,java] +---- +@Namespace("Converter") +@Plugin("query") +@ConverterKeys({"q", "query"}) +public final class QueryConverter extends LogEventPatternConverter { + + public QueryConverter(String[] options) { + } + + public static QueryConverter newInstance(final String[] options) { + return new QueryConverter(options); + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + // get the data from 'event', to the work and append the result to 'toAppendTo'. + } +} +---- + +A pattern to use this converter could be specified as `... %q ...` or `... %q{argument} ...`. +The "argument" will be passed as first (and only) value to the `options` parameter of the +`newInstance(...)` method. + +[#Plugin_Builders] +== Plugin Builders + +Some plugins take a lot of optional configuration options. When a plugin +takes many options, it is more maintainable to use a builder class +rather than a factory method (see _Item 2: Consider a builder when faced +with many constructor parameters_ in _Effective Java_ by Joshua Bloch). +There are some other advantages to using an annotated builder class over +an annotated factory method: + +* Attribute names don't need to be specified if they match the field name or the parameter name. +* Default values can be specified in code rather than through an +annotation (also allowing a runtime-calculated default value which isn't +allowed in annotations). +* Adding new optional parameters doesn't require existing programmatic +configuration to be refactored. +* Easier to write unit tests using builders rather than factory methods +with optional parameters. +* Default values are specified via code rather than relying on +reflection and injection, so they work programmatically as well as in a +configuration file. + +Here is an example of a plugin factory from `ListAppender`: + +[source,java] +---- +@PluginFactory +public static ListAppender createAppender( + @PluginAttribute @Required(message = "No name provided for ListAppender") final String name, + @PluginAttribute final boolean entryPerNewLine, + @PluginAttribute final boolean raw, + @PluginElement final Layout layout, + @PluginElement final Filter filter) { + return new ListAppender(name, filter, layout, newLine, raw); +} +---- + +Here is that same factory using a builder pattern instead: + +[source,java] +---- +@PluginFactory +public static Builder newBuilder() { + return new Builder(); +} + +public static class Builder implements org.apache.logging.log4j.plugins.util.Builder { + + private String name; + private boolean entryPerNewLine; + private boolean raw; + private Layout layout; + private Filter filter; + + + public Builder setName( + @PluginAttribute + @Required(message = "No name provided for ListAppender") + final String name) { + this.name = name; + return this; + } + + public Builder setEntryPerNewLine(@PluginAttribute final boolean entryPerNewLine) { + this.entryPerNewLine = entryPerNewLine; + return this; + } + + public Builder setRaw(@PluginAttribute final boolean raw) { + this.raw = raw; + return this; + } + + public Builder setLayout(@PluginElement final Layout layout) { + this.layout = layout; + return this; + } + + public Builder setFilter(@PluginElement final Filter filter) { + this.filter = filter; + return this; + } + + @Override + public ListAppender build() { + return new ListAppender(name, filter, layout, entryPerNewLine, raw); + } +} +---- + +When plugins are being constructed after a configuration has been +parsed, a plugin builder will be used if available, otherwise a plugin +factory method will be used as a fallback. If a plugin contains neither +factory, then it cannot be used from a configuration file (it can still +be used programmatically of course). + +Here is an example of using a plugin factory versus a plugin builder +programmatically: + +[source,java] +---- +ListAppender list1 = ListAppender.createAppender("List1", true, false, null, null); +ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLine(true).build(); +---- + +[#Custom_ContextDataProvider] +== Custom ContextDataProvider + +The link:../log4j-core/apidocs/org/apache/logging/log4j/core/util/ContextDataProvider.html[`ContextDataProvider`] +(introduced in Log4j 2.13.2) is an interface applications and libraries can use to inject +additional key-value pairs into the LogEvent's context data. Log4j's +link:../log4j-core/apidocs/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.html[`ThreadContextDataInjector`] +uses `java.util.ServiceLoader` to locate and load `ContextDataProvider` instances. +Log4j itself adds the ThreadContext data to the LogEvent using +`org.apache.logging.log4j.core.impl.ThreadContextDataProvider`. Custom implementations +should implement the `org.apache.logging.log4j.core.util.ContextDataProvider` interface and +declare it as a service by defining the implmentation class in a file named +`META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider`. + +== Custom ThreadContextMap implementations + +A garbage-free `StringMap`-based context map can be installed by setting +system property `log4j2.garbagefreeThreadContextMap` to true. (Log4j +must be link:garbagefree.html#Config[enabled] to use ThreadLocals.) + +Any custom link:../log4j-core/apidocs/org/apache/logging/log4j/spi/ThreadContextMap.html[`ThreadContextMap`] +implementation can be installed by setting system property `log4j2.threadContextMap` +to the fully qualified class name of the class implementing the `ThreadContextMap` +interface. By also implementing the `ReadOnlyThreadContextMap` interface, your custom +`ThreadContextMap` implementation will be accessible to applications via the +link:../log4j-api/apidocs/org/apache/logging/log4j/ThreadContext.html#getThreadContextMap()[`ThreadContext::getThreadContextMap`] +method. + +[#Custom_Plugins] +== Custom Plugins + +// TODO +See the link:plugins.html[Plugins] section of the manual. diff --git a/src/site/asciidoc/manual/filters.adoc b/src/site/asciidoc/manual/filters.adoc new file mode 100644 index 00000000000..89a3b569cae --- /dev/null +++ b/src/site/asciidoc/manual/filters.adoc @@ -0,0 +1,966 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Filters +Ralph Goers + +Filters allow Log Events to be evaluated to determine if or how they +should be published. A Filter will be called on one of its `filter` +methods and will return a `Result`, which is an Enum that has one of 3 +values - `ACCEPT`, `DENY` or `NEUTRAL`. + +Filters may be configured in one of four locations: + +1. Context-wide Filters are configured directly in the configuration. +Events that are rejected by these filters will not be passed to loggers +for further processing. Once an event has been accepted by a +Context-wide filter it will not be evaluated by any other Context-wide +Filters nor will the Logger's Level be used to filter the event. The +event will be evaluated by Logger and Appender Filters however. +2. Logger Filters are configured on a specified Logger. These are +evaluated after the Context-wide Filters and the Log Level for the +Logger. Events that are rejected by these filters will be discarded and +the event will not be passed to a parent Logger regardless of the +additivity setting. +3. Appender Filters are used to determine if a specific Appender should +handle the formatting and publication of the event. +4. Appender Reference Filters are used to determine if a Logger should +route the event to an appender. + +[#BurstFilter] +== BurstFilter + +The BurstFilter provides a mechanism to control the rate at which +LogEvents are processed by silently discarding events after the maximum +limit has been reached. + +.Burst Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|level +|String +|Level of messages to be filtered. Anything at or below +this level will be filtered out if `maxBurst` has been exceeded. The +default is WARN meaning any messages that are higher than warn will be +logged regardless of the size of a burst. + +|rate +|float +|The average number of events per second to allow. + +|maxBurst +|integer +|The maximum number of events that can occur before +events are filtered for exceeding the average rate. The default is 10 +times the rate. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A configuration containing the BurstFilter might look like: + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +[#CompositeFilter] +== CompositeFilter + +The CompositeFilter provides a way to specify more than one filter. It +is added to the configuration as a filters element and contains other +filters to be evaluated. The filters element accepts no parameters. + +A configuration containing the CompositeFilter might look like: + +[source,xml] +---- + + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + +---- + +[#DynamicThresholdFilter] +== DynamicThresholdFilter + +The DynamicThresholdFilter allows filtering by log level based on +specific attributes. For example, if the user's loginId is being +captured in the ThreadContext Map then it is possible to enable debug +logging for only that user. If the log event does not contain the +specified ThreadContext item NEUTRAL will be returned. + +.Dynamic Threshold Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|key +|String +|The name of the item in the ThreadContext Map to compare. + +|defaultThreshold +|String +|Level of messages to be filtered. The default +threshold only applies if the log event contains the specified +ThreadContext Map item and its value does not match any key in the +key/value pairs. + +|keyValuePair +|KeyValuePair[] +|One or more KeyValuePair elements that +define the matching value for the key and the Level to evaluate when the +key matches. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +Here is a sample configuration containing the DynamicThresholdFilter: + +[source,xml] +---- + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +[#MapFilter] +== MapFilter + +The MapFilter allows filtering against data elements that are in a +MapMessage. + +.Map Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|keyValuePair +|KeyValuePair[] +|One or more KeyValuePair elements that +define the key in the map and the value to match on. If the same key is +specified more than once then the check for that key will automatically +be an "or" since a Map can only contain a single value. + +|operator +|String +|If the operator is "or" then a match by any one of +the key/value pairs will be considered to be a match, otherwise all the +key/value pairs must match. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +As in this configuration, the MapFilter can be used to log particular +events: + +[source,xml] +---- + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +This sample configuration will exhibit the same behavior as the +preceding example since the only logger configured is the root. + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + +---- + +This third sample configuration will exhibit the same behavior as the +preceding examples since the only logger configured is the root and the +root is only configured with a single appender reference. + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + +---- + +[#MarkerFilter] +== MarkerFilter + +The MarkerFilter compares the configured Marker value against the Marker +that is included in the LogEvent. A match occurs when the Marker name +matches either the Log Event's Marker or one of its parents. + +.Marker Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|marker +|String +|The name of the Marker to compare. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A sample configuration that only allows the event to be written by the +appender if the Marker matches: + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +[#MutableThreadContextMapFilter] +== MutableThreadContextMapFilter + +The MutableThreadContextMapFilter or MutableContextMapFilter allows filtering against data elements that are in the current context. By default this is the ThreadContext Map. The values to compare are defined externally and can be periodically polled for changes. + +.Mutable Context Map Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|configLocation +|String +|A file path or URI that points to the configuration. See below for a sample configuration. + +|operator +|String +|If the operator is "or" then a match by any one of +the key/value pairs will be considered to be a match, otherwise all the +key/value pairs must match. + +|pollInterval +|int +|The number of seconds to wait before checking to see if the configuration has been modified. When using HTTP or HTTPS the server must support the If-Modified-Since header and return a Last-Modified header containing the date and time the file was last modified. Note that by default only the https, file, and jar protocols are allowed. Support for other protocols can be enabled by specifying them in the log4j2.Configuration.allowedProtocols system property + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A sample configuration that only allows the event to be written by the +appender if the Marker matches: + +[source,xml] +---- + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + +---- +The configuration file supplied to the filter should look similar to: +[source,json] +---- +{ + "configs": { + "loginId": ["rgoers@apache.org", "logging@apache.org"], + "accountNumber": ["30510263"] + } +} +---- + +[#NoMarkerFilter] +== NoMarkerFilter + +The NoMarkerFilter checks that there is no marker included in the LogEvent. A match occurs when there is no +marker in the Log Event. + +.No Marker Filter Parameters +[cols="1m,1,3"] +|=== +|Parameter Name |Type |Description + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A sample configuration that only allows the event to be written by the +appender if no marker is there: + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +[#RegexFilter] +== RegexFilter + +The RegexFilter allows the formatted or unformatted message to be +compared against a regular expression. + +.Regex Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|regex +|String +|The regular expression. + +|useRawMsg +|boolean +|If true the unformatted message will be used, +otherwise the formatted message will be used. The default value is +false. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A sample configuration that only allows the event to be written by the +appender if it contains the word "test": + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +[[Script]] + +The ScriptFilter executes a script that returns true or false. + +.Script Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|script +|Script, ScriptFile or ScriptRef +|The Script element that specifies the logic to be executed. + +|onMatch +|String +|Action to take when the script returns true. May be +ACCEPT, DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter returns false. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +.Script Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|configuration +|Configuration +|The Configuration that owns this +ScriptFilter. + +|level +|Level +|The logging Level associated with the event. Only present +when configured as a global filter. + +|loggerName +|String +|The name of the logger. Only present when +configured as a global filter. + +|logEvent +|LogEvent +|The LogEvent being processed. Not present when +configured as a global filter. + +|marker +|Marker +|The Marker passed on the logging call, if any. Only +present when configured as a global filter. + +|message +|Message +|The Message associated with the logging call. Only +present when configured as a global filter. + +|parameters +|Object[] +|The parameters passed to the logging call. Only +present when configured as a global filter. Some Messages include the +parameters as part of the Message. + +|throwable +|Throwable +|The Throwable passed to the logging call, if any. +Only present when configured as a global filter. Som Messages include +Throwable as part of the Message. + +|substitutor +|StrSubstitutor +|The StrSubstitutor used to replace lookup variables. +|=== + +The sample below shows how to declare script fields and then reference +them in specific components. See +link:appenders.html#ScriptCondition[ScriptCondition] for an example of +how the `ScriptPlugin` element can be used to embed script code directly in +the configuration. + +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +[#StructuredDataFilter] +== StructuredDataFilter + +The StructuredDataFilter is a MapFilter that also allows filtering on +the event id, type and message. + +.StructuredData Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|keyValuePair +|KeyValuePair[] +|One or more KeyValuePair elements that +define the key in the map and the value to match on. "id", "id.name", +"type", and "message" should be used to match on the StructuredDataId, +the name portion of the StructuredDataId, the type, and the formatted +message respectively. If the same key is specified more than once then +the check for that key will automatically be an "or" since a Map can +only contain a single value. + +|operator +|String +|If the operator is "or" then a match by any one of +the key/value pairs will be considered to be a match, otherwise all the +key/value pairs must match. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +As in this configuration, the StructuredDataFilter can be used to log +particular events: + +[source,xml] +---- + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +[#ThreadContextMapFilter] +== ThreadContextMapFilter + +The ThreadContextMapFilter or ContextMapFilter allows filtering against +data elements that are in the current context. By default this is the +ThreadContext Map. + +.Context Map Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|keyValuePair +|KeyValuePair[] +|One or more KeyValuePair elements that +define the key in the map and the value to match on. If the same key is +specified more than once then the check for that key will automatically +be an "or" since a Map can only contain a single value. + +|operator +|String +|If the operator is "or" then a match by any one of +the key/value pairs will be considered to be a match, otherwise all the +key/value pairs must match. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A configuration containing the ContextMapFilter might look like: + +[source,xml] +---- + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +The ContextMapFilter can also be applied to a logger for filtering: + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + +---- + +[#ThresholdFilter] +== ThresholdFilter + +This filter returns the onMatch result if the level in the LogEvent is +the same or more specific than the configured level and the onMismatch +value otherwise. For example, if the ThresholdFilter is configured with +Level ERROR and the LogEvent contains Level DEBUG then the onMismatch +value will be returned since ERROR events are more specific than DEBUG. + +.Threshold Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|level +|String +|A valid Level name to match on. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A sample configuration that only allows the event to be written by the +appender if the level matches: + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- + +[#TimeFilter] +== TimeFilter + +The time filter can be used to restrict filter to only a certain portion +of the day. + +.Time Filter Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|start +|String +|A time in HH:mm:ss format. + +|end +|String +|A time in HH:mm:ss format. Specifying an end time less +than the start time will result in no log entries being written. + +|timezone +|String +|The timezone to use when comparing to the event +timestamp. + +|onMatch +|String +|Action to take when the filter matches. May be ACCEPT, +DENY or NEUTRAL. The default value is NEUTRAL. + +|onMismatch +|String +|Action to take when the filter does not match. May +be ACCEPT, DENY or NEUTRAL. The default value is DENY. +|=== + +A sample configuration that only allows the event to be written by the +appender from 5:00 to 5:30 am each day using the default timezone: + +[source,xml] +---- + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + +---- diff --git a/src/site/asciidoc/manual/flowtracing.adoc b/src/site/asciidoc/manual/flowtracing.adoc new file mode 100644 index 00000000000..c4026612c08 --- /dev/null +++ b/src/site/asciidoc/manual/flowtracing.adoc @@ -0,0 +1,275 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Log4j 2 API +Ralph Goers + +[#FlowTracing] +== Flow Tracing + +The `Logger` class provides logging methods that are quite useful for +following the execution path of applications. These methods generate +logging events that can be filtered separately from other debug logging. +Liberal use of these methods is encouraged as the output has been found +to + +* aid in problem diagnosis in development without requiring a debug +session +* aid in problem diagnosis in production where no debugging is possible +* help educate new developers in learning the application. + +The most used methods are the entry() or traceEntry() and exit() or +traceExit() methods. entry() or traceEntry() should be placed at the +beginning of methods, except perhaps for simple getters and setters. +entry() can be called passing from 0 to 4 parameters. Typically these +will be parameters passed to the method. traceEntry() can be passed a +format String and a variable list of parameters, or a Message. The +entry() and traceEntry() methods log with a level of TRACE and uses a +Marker with a name of "ENTER" which is also a "FLOW" Marker and all +message strings will begin with "event", even if a format String or +Message is used. + +The main difference between the entry and traceEntry methods is that the +entry method accepts a variable list of objects where presumably each is +a method parameter. The traceEntry method accepts a format string +followed by a variable list of objects, presumably included in the +format String. It is not possible to have a single method that includes +both of these as it would be ambiguous whether the first String is a +parameter or a format String. + +An exit() or traceExit() method should be placed before any return +statement or as the last statement of methods without a return. exit() +and traceExit() can be called with or without a parameter. Typically, +methods that return void will use exit() or traceExit() while methods +that return an Object will use exit(Object obj) or traceExit(object, new +SomeMessage(object)). The exit() and traceExit() methods log with a +level of TRACE and uses a Marker with a name of "EXIT" which is also a +"FLOW" Marker and all message strings will begin with "exit", even if a +format String or Message is used. + +The throwing() method can be used by an application when it is throwing +an exception that is unlikely to be handled, such as a RuntimeException. +This will insure that proper diagnostics are available if needed. The +logging event generated will have a level of ERROR and will have an +associated Marker with a name of "THROWING" which is also an "EXCEPTION" +Marker. + +The catching() method can be used by an application when it catches an +Exception that it is not going to rethrow, either explicitly or attached +to another Exception. The logging event generated will have a level of +ERROR and will have an associated Marker with a name of "CATCHING" which +is also an "EXCEPTION" Marker. + +The following example shows a simple application using these methods in +a fairly typical manner. The throwing() is not present since no +Exceptions are explicitly thrown and not handled. + +[source,java] +---- +package com.test; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + +import java.util.Random; + +public class TestService { + private Logger logger = LogManager.getLogger(TestService.class.getName()); + + private String[] messages = new String[] { + "Hello, World", + "Goodbye Cruel World", + "You had me at hello" + }; + private Random rand = new Random(1); + + public void setMessages(String[] messages) { + logger.traceEntry(new JsonMessage(messages)); + this.messages = messages; + logger.traceExit(); + } + + public String[] getMessages() { + logger.traceEntry(); + return logger.traceExit(messages, new JsonMessage(messages)); + } + + public String retrieveMessage() { + logger.entry(); + + String testMsg = getMessage(getKey()); + + return logger.exit(testMsg); + } + + public void exampleException() { + logger.entry(); + try { + String msg = messages[messages.length]; + logger.error("An exception should have been thrown"); + } catch (Exception ex) { + logger.catching(ex); + } + logger.exit(); + } + + public String getMessage(int key) { + logger.entry(key); + + String value = messages[key]; + + return logger.exit(value); + } + + private int getKey() { + logger.entry(); + int key = rand.nextInt(messages.length); + return logger.exit(key); + } +} +---- + +This test application uses the preceding service to generate logging +events. + +[source,java] +---- +package com.test; + +public class App { + + public static void main( String[] args ) { + TestService service = new TestService(); + service.retrieveMessage(); + service.retrieveMessage(); + service.exampleException(); + } +} +---- + +The configuration below will cause all output to be routed to +target/test.log. The pattern for the FileAppender includes the class +name, line number and method name. Including these in the pattern are +critical for the log to be of value. + +[source,xml] +---- + + + + + + + + + + + + + + + + + + +---- + +Here is the output that results from the Java classes and configuration +above. + +.... +19:08:07.056 TRACE com.test.TestService 19 retrieveMessage - entry +19:08:07.060 TRACE com.test.TestService 46 getKey - entry +19:08:07.060 TRACE com.test.TestService 48 getKey - exit with (0) +19:08:07.060 TRACE com.test.TestService 38 getMessage - entry parms(0) +19:08:07.060 TRACE com.test.TestService 42 getMessage - exit with (Hello, World) +19:08:07.060 TRACE com.test.TestService 23 retrieveMessage - exit with (Hello, World) +19:08:07.061 TRACE com.test.TestService 19 retrieveMessage - entry +19:08:07.061 TRACE com.test.TestService 46 getKey - entry +19:08:07.061 TRACE com.test.TestService 48 getKey - exit with (1) +19:08:07.061 TRACE com.test.TestService 38 getMessage - entry parms(1) +19:08:07.061 TRACE com.test.TestService 42 getMessage - exit with (Goodbye Cruel World) +19:08:07.061 TRACE com.test.TestService 23 retrieveMessage - exit with (Goodbye Cruel World) +19:08:07.062 TRACE com.test.TestService 27 exampleException - entry +19:08:07.077 DEBUG com.test.TestService 32 exampleException - catching java.lang.ArrayIndexOutOfBoundsException: 3 + at com.test.TestService.exampleException(TestService.java:29) [classes/:?] + at com.test.App.main(App.java:9) [classes/:?] + at com.test.AppTest.testApp(AppTest.java:15) [test-classes/:?] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29] + at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29] + at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81) [junit-4.3.1.jar:?] + at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42) [junit-4.3.1.jar:?] + at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52) [junit-4.3.1.jar:?] + at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:35) [surefire-junit4-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115) [surefire-junit4-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97) [surefire-junit4-2.7.2.jar:2.7.2] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29] + at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29] + at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103) [surefire-booter-2.7.2.jar:2.7.2] + at $Proxy0.invoke(Unknown Source) [?:?] + at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150) [surefire-booter-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91) [surefire-booter-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69) [surefire-booter-2.7.2.jar:2.7.2] +19:08:07.087 TRACE com.test.TestService 34 exampleException - exit +.... + +Simply changing the root logger level to DEBUG in the example above will +reduce the output considerably. + +.... +19:13:24.963 DEBUG com.test.TestService 32 exampleException - catching java.lang.ArrayIndexOutOfBoundsException: 3 + at com.test.TestService.exampleException(TestService.java:29) [classes/:?] + at com.test.App.main(App.java:9) [classes/:?] + at com.test.AppTest.testApp(AppTest.java:15) [test-classes/:?] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29] + at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29] + at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81) [junit-4.3.1.jar:?] + at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42) [junit-4.3.1.jar:?] + at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?] + at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52) [junit-4.3.1.jar:?] + at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:35) [surefire-junit4-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115) [surefire-junit4-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97) [surefire-junit4-2.7.2.jar:2.7.2] + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29] + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29] + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29] + at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29] + at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103) [surefire-booter-2.7.2.jar:2.7.2] + at $Proxy0.invoke(Unknown Source) [?:?] + at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150) [surefire-booter-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91) [surefire-booter-2.7.2.jar:2.7.2] + at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69) [surefire-booter-2.7.2.jar:2.7.2] +.... diff --git a/src/site/asciidoc/manual/garbagefree.adoc b/src/site/asciidoc/manual/garbagefree.adoc new file mode 100644 index 00000000000..d08a9564a4a --- /dev/null +++ b/src/site/asciidoc/manual/garbagefree.adoc @@ -0,0 +1,589 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Garbage-free Steady State Logging +Remko Popma + +//// +Different applications have different performance requirements. +Some only need to worry about throughput, but for many +the most important performance consideration is latency (response time). +Users of such applications would consider it a serious problem +if the system becomes unresponsive for more than a few seconds, or even milliseconds in some cases. +In financial trading for example predictable low latency is so important that it is often considered +worthwhile to trade off some throughput in return for a consistent response time. +//// + +Garbage collection pauses are a common cause of latency spikes and for +many systems significant effort is spent on controlling these pauses. + +Many logging libraries, including previous versions of Log4j, allocate +temporary objects like log event objects, Strings, char arrays, byte +arrays and more during steady state logging. This contributes to +pressure on the garbage collector and increases the frequency with which +GC pauses occur. + +From version 2.6, Log4j runs in "garbage free" mode by default where +objects and buffers are reused and no temporary objects are allocated as +much as possible. There is also a "low garbage" mode which is not +completely garbage free but does not use ThreadLocal fields. This is the +default mode when Log4j link:#Config[detects] it is running in a web +application. Finally, it is possible to switch off all garbage-free +logic and run in "classic mode" instead. For details, see the +link:#Config[Configuration] section below. + +[#jfr] +== A Contrived Example + +To highlight the difference that garbage-free logging can make, we used +Java Flight Recorder to measure a simple application that does nothing +but log a simple string as often as possible for about 12 seconds. + +The application was configured to use Async Loggers, a RandomAccessFile +appender and a "%d %p %c{1.} [%t] %m %ex%n" pattern layout. (Async +Loggers used the Yield WaitStrategy.) + +Mission Control shows that with Log4j 2.5 this application allocates +memory at a rate of about 809 MB/sec, resulting in 141 minor +collections. Log4j 2.6 does not allocate temporary objects in this +configuration, and as a result the same application with Log4j 2.6 has a +memory allocation rate of 1.6 MB/sec and was GC-free with 0 (zero) +garbage collections. + +[cols="2*"] +|=== +|link:../images/log4j-2.5-FlightRecording.png[image:../images/log4j-2.5-FlightRecording-thumbnail40pct.png[image]] + +With Log4j 2.5: memory allocation rate 809 MB/sec, 141 minor +collections. +|link:../images/log4j-2.6-FlightRecording.png[image:../images/log4j-2.6-FlightRecording-thumbnail40pct.png[image]] + +Log4j 2.6 did not allocate temporary objects: 0 (zero) garbage +collections. +|=== + +[#Config] +== Configuration + +Garbage-free logging in Log4j 2.6 is partially implemented by reusing +objects in ThreadLocal fields, and partially by reusing buffers when +converting text to bytes. + +ThreadLocal fields holding non-JDK classes can cause memory leaks in web +applications when the application server's thread pool continues to +reference these fields after the web application is undeployed. To avoid +causing memory leaks, Log4j will not use these ThreadLocals when it +detects that it is used in a web application (when the +`javax.servlet.Servlet` class is in the classpath, or when system +property `log4j2.isWebapp` is set to "true"). + +Some garbage-reducing functionality does not rely on ThreadLocals and is +enabled by default for all applications: in Log4j 2.6, converting log +events to text and text to bytes can be done by directly encoding text +into a reused ByteBuffer without creating intermediary Strings, char +arrays and byte arrays. So while logging is not completely garbage-free +for web applications yet, the pressure on the garbage collector can +still be significantly reduced. + +NOTE: As of version 2.6, a Log4j configuration containing a +`` section will result in temporary objects being created +during steady-state logging. + +NOTE: as of version 2.18.0, the default Async Logger wait strategy used by Log4j +(Timeout) is garbage-free. Some of the wait strategies included in LMAX disruptor 3.4.4, +especially `TimeoutBlockingWaitStrategy` and `BlockingWaitStrategy` (Block) +are not garbage-free since they +cause `java.util.concurrent.locks.AbstractQueuedSynchronizer$Node` objects to be created. +The default wait strategy used by Log4j uses a synchronized block instead of a ReentrantLock to avoid this problem. +The Yield and Sleep wait strategies are garbage-free. (For configuring predefined wait strategies, see +link:async.html#SysPropsAllAsync[here] and +link:async.html#SysPropsMixedSync-Async[here], +you may also configure a link:async.html#WaitStrategy[custom wait strategy].) + +=== Disabling Garbage-free Logging + +There are two separate system properties for manually controlling the +mechanisms Log4j uses to avoid creating temporary objects: + +* `log4j2.enableThreadlocals` - if "true" (the default for non-web +applications) objects are stored in ThreadLocal fields and reused, +otherwise new objects are created for each log event. +* `log4j2.enableDirectEncoders` - if "true" (the default) log events are +converted to text and this text is converted to bytes without creating +temporary objects. Note: _synchronous_ logging performance may be worse +for multi-threaded applications in this mode due to synchronization on +the shared buffer. If your application is multi-threaded and logging +performance is important, consider using Async Loggers. +* The ThreadContext map is _not_ garbage-free by default, but from Log4j +2.7 it can be configured to be garbage-free by setting system property +`log4j2.garbagefreeThreadContextMap` to "true". + +Instead of system properties, the above properties can also be specified +in a file named `log4j2.component.properties` by including this file in +the classpath of the application. See the +link:configuration.html#SystemProperties[manual regarding system +properties] for more info. + +[#Appenders] +=== Supported Appenders + +The following link:appenders.html[appenders] are garbage-free during +steady-state logging: + +* Console +* File +* RollingFile (some temporary objects are created during file rollover) +* RandomAccessFile +* RollingRandomAccessFile (some temporary objects are created during +file rollover) +* MemoryMappedFile + +Any other appenders not in the above list (including AsyncAppender) +create temporary objects during steady-state logging. Instead of +AsyncAppender, use link:async.html[Async Loggers] to log asynchronously +in a garbage-free manner. + +[#Filters] +=== Supported Filters + +The following link:filters.html[filters] are garbage-free during +steady-state logging: + +* CompositeFilter (adding and removing element filters creates temporary +objects for thread safety) +* DynamicThresholdFilter +* LevelRangeFilter (garbage free since 2.8) +* MapFilter (garbage free since 2.8) +* MarkerFilter (garbage free since 2.8) +* StructuredDataFilter (garbage free since 2.8) +* ThreadContextMapFilter (garbage free since 2.8) +* ThresholdFilter (garbage free since 2.8) +* TimeFilter (garbage free since 2.8 except when range must be recalculated once per day) + +Other filters like BurstFilter, RegexFilter and ScriptFilter are not +trivial to make garbage free, and there is currently no plan to change +them. + +[#Layouts] +=== Supported Layouts + +==== GelfLayout + +GelfLayout is garbage-free when used with compressionType="OFF", as long +as no additional field contains '${' (variable substitution). + +==== JsonTemplateLayout + +`JsonTemplateLayout` is garbage-free with +link:json-template-layout.html#faq-garbage-free[a few exceptions]. + +==== PatternLayout + +PatternLayout with the following limited set of conversion patterns is +garbage-free. Format modifiers to control such things as field width, +padding, left and right justification will not generate garbage. + +[cols="1,2"] +|=== +|Conversion Pattern |Description + +|%c{precision}, %logger{precision} +|Logger name + +|%d, %date +a| +Note: Only the predefined date formats are garbage-free: (millisecond +separator may be either a comma ',' or a period '.') + +!=== +!Pattern !Example + +!%d{DEFAULT} +!2012-11-02 14:34:02,781 + +!%d{ISO8601} +!2012-11-02T14:34:02,781 + +!%d{ISO8601_BASIC} +!20121102T143402,781 + +!%d{ABSOLUTE} +!14:34:02,781 + +!%d{DATE} +!02 Nov 2012 14:34:02,781 + +!%d{COMPACT} +!20121102143402781 + +!%d{HH:mm:ss,SSS} +!14:34:02,781 + +!%d{dd MMM yyyy HH:mm:ss,SSS} +!02 Nov 2012 14:34:02,781 + +!%d{HH:mm:ss}{GMT+0} +!18:34:02 + +!%d{UNIX} +!1351866842 + +!%d{UNIX_MILLIS} +!1351866842781 +!=== + +|%enc{pattern}, %encode{pattern} +|Encodes special characters such as +'\n' and HTML characters to help prevent log forging and some XSS +attacks that could occur when displaying logs in a web browser - +garbage-free since 2.8 + +|%equals{pattern}{test}{substitution}, +%equalsIgnoreCase{pattern}{test}{substitution} +|Replaces occurrences +of 'test', a string, with its replacement 'substitution' in the string +resulting from evaluation of the pattern - garbage-free since 2.8 + +|%highlight{pattern}{style} +|Adds ANSI colors - garbage-free since 2.7 +(unless nested pattern is not garbage free) + +|%K{key}, %map{key}, %MAP{key} +|Outputs the entries in a +link:../log4j-api/apidocs/org/apache/logging/log4j/message/MapMessage.html[MapMessage], +if one is present in the event - garbage-free since 2.8. + +|%m, %msg, %message +|Log message (garbage-free unless message text +contains '${') + +|%marker +|The full name of the marker (including parents) - garbage-free +since 2.8 + +|%markerSimpleName +|The simple name of the marker (not including +parents) + +|%maxLen, %maxLength +|Truncates another pattern to some max number of +characters - garbage-free since 2.8 + +|%n +|The platform dependent line separator + +|%N, %nano +|System.nanoTime() when the event was logged + +|%notEmpty{pattern}, %varsNotEmpty{pattern}, +%variablesNotEmpty{pattern} +|Outputs the result of evaluating the +pattern if and only if all variables in the pattern are not empty - +garbage-free since 2.8 + +|%p, %level +|The level of the logging event + +|%r, %relative +|The number of milliseconds elapsed since the JVM was +started until the creation of the logging event - garbage-free since 2.8 + +|%sn, %sequenceNumber +|A sequence number that will be incremented in +every event - garbage-free since 2.8 + +|%style{pattern}{ANSI style} +|Style the message - garbage-free since +2.7 (unless nested pattern is not garbage free) + +|%T, %tid, %threadId +|The ID of the thread that generated the logging +event + +|%t, %tn, %thread, %threadName +|The name of the thread that generated +the logging event + +|%tp +|The priority of the thread that generated the logging event + +|%X{key[,key2...]}, %mdc{key[,key2...]}, %MDC{key[,key2...]} +|Outputs +the Thread Context Map (also known as the Mapped Diagnostic Context or +MDC) associated with the thread that generated the logging event - +garbage-free since 2.8 + +|literal text +|Garbage-free unless literal contains '${' (variable +substitution) +|=== + +Other PatternLayout conversion patterns, and other Layouts may be +updated to avoid creating temporary objects in future releases. (Patches +welcome!) + +NOTE: Logging exceptions and stack traces will create temporary +objects with any layout. (However, Layouts will only create these +temporary objects when an exception actually occurs.) We haven't figured +out a way to log exceptions and stack traces without creating temporary +objects. That is unfortunate, but you probably still want to log them +when they happen. + +**** +NOTE: patterns containing regular expressions and lookups for property +substitution will result in temporary objects being created during +steady-state logging. + +Including location information is done by walking the stacktrace of an +exception, which creates temporary objects, so the following patterns +are not garbage-free: + +* %C, %class - Class Name +* %F, %file - File Location +* %l, %location - Location +* %L, %line - Line Location +* %M, %method - Method Location + +Also, the pattern converters for formatting Throwables are not +garbage-free: + +* %ex, %exception, %throwable - The Throwable trace bound to the +LoggingEvent +* %rEx, %rException %rThrowable - Same as %ex but with wrapping +exceptions +* %xEx, %xException, %xThrowable - Same as %ex but with class packaging +information +* %u, %uuid - Creates a new random or time-based UUID while formatting + +**** + +[#api] +=== API Changes + +Methods have been added to the `Logger` interface so that no vararg +array objects are created when logging messages with up to ten +parameters. + +Also, methods have been added to the `Logger` interface to log +`java.lang.CharSequence` messages. User-defined objects that implement +the `CharSequence` interface can be logged without creating temporary +objects: Log4j will try to turn CharSequence messages, Object messages +and message parameters into text by appending them to a StringBuilder as +a CharSequence. This avoids calling `toString()` on these objects. + +An alternative is to implement the +http://logging.apache.org/log4j/2.x/log4j-api/xref/org/apache/logging/log4j/util/StringBuilderFormattable.html[`org.apache.logging.log4j.util.StringBuilderFormattable`] +interface. If an object is logged that implements this interface, its +`formatTo` method is called instead of `toString()`. + +Log4j may call `toString()` on message and parameter objects when +garbage-free logging is disabled (when system property +`log4j2.enableThreadlocals` is set to "false".) + +[#codeImpact] +=== Impact on Application Code: Autoboxing + +We made an effort to make logging garbage-free without requiring code +changes in existing applications, but there is one area where this was +not possible. When logging primitive values (i.e. int, double, boolean, +etc.) the JVM autoboxes these primitive values to their Object wrapper +equivalents, creating garbage. + +Log4j provides an `Unbox` utility to prevent autoboxing of primitive +parameters. This utility contains a thread-local pool of reused +`StringBuilder`s. The `Unbox.box(primitive)` methods write directly into +a StringBuilder, and the resulting text will be copied into the final +log message text without creating temporary objects. + +[source,java] +---- +import static org.apache.logging.log4j.util.Unbox.box; + +// ... +public void garbageFree() { + logger.debug("Prevent primitive autoboxing {} {}", box(10L), box(2.6d)); +} +---- + +**** +NOTE: not all logging is garbage free. Specifically: + +* The ThreadContext map is not garbage-free by default, but can be +configured to be garbage-free by setting system property +`log4j2.garbagefreeThreadContextMap` to "true". +* The ThreadContext stack is not garbage-free. +* Logging more than 10 parameters creates vararg arrays. +* Logging very large messages (more than 518 characters) when all +loggers are Async Loggers will cause the internal StringBuilder in the +RingBuffer to be trimmed back to their max size. +* Logging messages containing '${': substituting a ${variable} creates +temporary objects. +* Logging a lambda _as a parameter_ +(`logger.info("lambda value is {}", () -> callExpensiveMethod())`) +creates a vararg array. Logging a lambda expression by itself is +garbage-free: `logger.debug(() -> callExpensiveMethod())`. +* The `Logger.traceEntry` and `Logger.traceExit` methods create +temporary objects. +* Time calculations are not garbage free when log4j2.usePreciseClock is set to true. +The default is false. +**** + +[#Performance] +== Performance + +[#Latency] +=== Response Time Latency + +Response time is how long it takes to log a message under a certain +load. What is often reported as latency is actually _service time_: how +long it took to perform the operation. This hides the fact that a single +spike in service time adds queueing delay for many of the subsequent +operations. Service time is easy to measure (and often looks good on +paper) but is irrelevant for users since it omits the time spent waiting +for service. For this reason we report response time: service time plus +wait time. See the link:../performance.html#responseTime[response time +section] of the performance page for more detail. + +The response time test results below were all derived from running the +ResponseTimeTest class which can be found in the Log4j 2 unit test +source directory. If you want to run these tests yourself, here are the +command line options we used: + +* -Xms1G -Xmx1G (prevent heap resizing during the test) +* -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector +-DAsyncLogger.WaitStrategy=busyspin (to use Async Loggers. The BusySpin +wait strategy reduces some jitter.) +* *classic mode:* -Dlog4j2.enable.threadlocals=false +-Dlog4j2.enable.direct.encoders=false + +*garbage-free mode:* -Dlog4j2.enable.threadlocals=true +-Dlog4j2.enable.direct.encoders=true +* -XX:CompileCommand=dontinline,org.apache.logging.log4j.core.async.perftest.NoOpIdleStrategy::idle +* -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps +-XX:+PrintTenuringDistribution -XX:+PrintGCApplicationConcurrentTime +-XX:+PrintGCApplicationStoppedTime (to eyeball GC and safepoint pauses) + +=== Async Loggers + +The graph below compares "classic" logging to garbage-free logging +response time behaviour for Log4j's Async Loggers. In the graph, "100k" +means logging at a sustained load of 100,000 messages/second, "800k" is +a sustained load of 800,000 messages/second. + +image:../images/ResponseTimeAsyncClassicVsGcFree-label.png[image] + +In *classic* mode we see numerous minor garbage collections which pause +the application threads for 3 milliseconds or more. This quickly adds up +to response time delays of almost 10 milliseconds. As you can see in the +graph, increasing the load shifts the curve to the left (there are more +spikes). This makes sense: logging more means more pressure on the +garbage collector resulting in more minor GC pauses. We experimented a +little with reducing the load to 50,000 or even 5000 messages/second, +but this did not eliminate the 3 millisecond pauses, it just made them +occur less frequently. Note that all GC pauses in this test are minor GC +pauses. We did not see any full garbage collections. + +In *garbage-free* mode, maximum response time remains well below 1 +millisecond under a wide range of loads. (Max 780 us at 800,000 +messages/sec, max 407 us at 600,000 messages/sec, with the 99% around 5 +us for all loads up to 800,000 messages/sec.) Increasing or decreasing +the load does not change the response time behaviour. We did not +investigate the cause of the 200-300 microsecond pauses we saw in these +tests. + +When we increased the load further we begin to see larger response time +pauses for both classic and garbage-free logging. At sustained loads of +1 million messages/second or more we start to approach the maximum +throughput of the underlying RandomAccessFile Appender (see the +synchronous logging throughput chart below). At these loads the +ringbuffer starts to fill up and backpressure kicks in: attempting to +add another message when the ringbuffer is full will block until a free +slot becomes available. We start to see response times of tens of +milliseconds or more; and attempting to increase the load even more +results in larger and larger response time spikes. + +=== Synchronous File Logging + +With synchronous file logging, garbage-free logging still performs +better than classic logging, but the difference is less pronounced. + +At a workload of 100,000 messages/second, classic logging max response +time was a little over 2 milliseconds where garbage-free logging was a +little over 1 millisecond. When the workload is increased to 300,000 +messages/second, classic logging shows response time pauses of 6 +milliseconds where the garbage-free response times were less than 3 +milliseconds. It may be possible to improve on this, we did not +investigate further yet. + +image:../images/ResponseTimeSyncClassicVsGcFree.png[image] + +The above results are obtained with the ResponseTimeTest class which can +be found in the Log4j 2 unit test source directory, running on JDK +1.8.0_45 on RHEL 6.5 (Linux 2.6.32-573.1.1.el6.x86_64) with 10-core Xeon +CPU E5-2660 v3 @2.60GHz with hyperthreading switched on (20 virtual +cores). + +[#Throughput] +=== Classic Logging has Slightly Higher Throughput + +Throughput is slightly worse for garbage-free logging, compared to +classic logging. This is true for both synchronous and asynchronous +logging. The graph below compares the sustained throughput of +synchronous logging to a file with Log4j 2.6 in garbage-free mode, +classic mode and Log4j 2.5. + +image:../images/garbage-free2.6-SyncThroughputLinux.png[Throughput of +Log4j 2.6 in garbage-free mode is slightly worse than in classic mode, +but on par with 2.5 and much better than alternatives logging libraries] + +The results above are obtained with the +http://openjdk.java.net/projects/code-tools/jmh/[JMH] Java benchmark +harness. See the FileAppenderBenchmark source code in the log4j-perf +module. + +[#UnderTheHood] +== Under the Hood + +Custom Message implementations that implement +`org.apache.logging.log4j.util.StringBuilderFormattable` can be +converted to text by garbage-free Layouts without creating temporary +objects. PatternLayout uses this mechanism and other layouts that +convert LogEvents to text will likely also look for this interface. + +Custom Layouts that want to be garbage-free should implement the +`Encoder` interface. For custom Layouts that convert a +LogEvent to a text representation, the +`org.apache.logging.log4j.core.layout.StringBuilderEncoder` class may be +useful to convert this text to bytes in a garbage-free manner. + +Custom Appenders that want to be garbage-free should provide their +Layout with a `ByteBufferDestination` implementation that the Layout can +directly write into. + +`AbstractOutputStreamAppender` has been modified to make the +ConsoleAppender, (Rolling)FileAppender, +(Rolling)RandomAccessFileAppender and MemoryMappedFileAppender +garbage-free. An effort has been made to minimize impact on custom +Appenders that extend `AbstractOutputStreamAppender`, but it is +impossible to guarantee that changing the superclass will not impact any +and all subclasses. Custom Appenders that extend +`AbstractOutputStreamAppender` should verify that they still function +correctly. In case there is a problem, system property +`log4j2.enable.direct.encoders` can be set to "false" to revert to the +pre-Log4j 2.6 behaviour. + +//// +TODO Applications that wish to reuse custom Message instances with Async Loggers should let +their Message classes implement the `org.apache.logging.log4j.message.ReusableMessage` interface. +TODO This is not sufficient: see LOG4J2-1342, would be nice if we could solve this in a generic way. +//// diff --git a/src/site/asciidoc/manual/index.adoc b/src/site/asciidoc/manual/index.adoc new file mode 100644 index 00000000000..57af2536547 --- /dev/null +++ b/src/site/asciidoc/manual/index.adoc @@ -0,0 +1,138 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Overview +Ralph Goers + +== Welcome to Log4j! + +Almost every large application includes its own logging or tracing API. +In conformance with this rule, the E.U. http://www.semper.org[SEMPER] +project decided to write its own tracing API. This was in early 1996. +After countless enhancements, several incarnations and much work that +API has evolved to become log4j, a popular logging package for Java. The +package is distributed under the link:../LICENSE[Apache Software +License], a fully-fledged open source license certified by the +http://www.opensource.org[open source] initiative. The latest log4j +version, including full-source code, class files and documentation can +be found at +https://logging.apache.org/log4j/2.x/index.html[*https://logging.apache.org/log4j/2.x/index.html*]. + +Inserting log statements into code is a low-tech method for debugging +it. It may also be the only way because debuggers are not always +available or applicable. This is usually the case for multithreaded +applications and distributed applications at large. + +Experience indicates that logging was an important component of the +development cycle. It offers several advantages. It provides precise +_context_ about a run of the application. Once inserted into the code, +the generation of logging output requires no human intervention. +Moreover, log output can be saved in persistent medium to be studied at +a later time. In addition to its use in the development cycle, a +sufficiently rich logging package can also be viewed as an auditing +tool. + +As Brian W. Kernighan and Rob Pike put it in their truly excellent book +_"The Practice of Programming":_ + +____ +As personal choice, we tend not to use debuggers beyond getting a stack +trace or the value of a variable or two. One reason is that it is easy +to get lost in details of complicated data structures and control flow; +we find stepping through a program less productive than thinking harder +and adding output statements and self-checking code at critical places. +Clicking over statements takes longer than scanning the output of +judiciously-placed displays. It takes less time to decide where to put +print statements than to single-step to the critical section of code, +even assuming we know where that is. More important, debugging +statements stay with the program; debugging sessions are transient. +____ + +Logging does have its drawbacks. It can slow down an application. If too +verbose, it can cause scrolling blindness. To alleviate these concerns, +log4j is designed to be reliable, fast and extensible. Since logging is +rarely the main focus of an application, the log4j API strives to be +simple to understand and to use. + +== Log4j 2 + +Log4j 1.x has been widely adopted and used in many applications. +However, through the years development on it has slowed down. It has +become more difficult to maintain due to its need to be compliant with +very old versions of Java and became +https://blogs.apache.org/foundation/entry/apache_logging_services_project_announces[End +of Life] in August 2015. Its alternative, SLF4J/Logback made many needed +improvements to the framework. So why bother with Log4j 2? Here are a +few of the reasons. + +1. Log4j 2 is designed to be usable as an audit logging framework. Both +Log4j 1.x and Logback will lose events while reconfiguring. Log4j 2 will +not. In Logback, exceptions in Appenders are never visible to the +application. In Log4j 2 Appenders can be configured to allow the +exception to percolate to the application. +2. Log4j 2 contains next-generation link:async.html[Asynchronous +Loggers] based on the https://lmax-exchange.github.io/disruptor/[LMAX +Disruptor library]. In multi-threaded scenarios Asynchronous Loggers +have 10 times higher throughput and orders of magnitude lower latency +than Log4j 1.x and Logback. +3. Log4j 2 is link:garbagefree.html[garbage free] for stand-alone +applications, and low garbage for web applications during steady state +logging. This reduces pressure on the garbage collector and can give +better response time performance. +4. Log4j 2 uses a link:plugins.html[Plugin system] that makes it +extremely easy to link:extending.html[extend the framework] by adding +new link:appenders.html[Appenders], link:filters.html[Filters], +link:layouts.html[Layouts], link:lookups.html[Lookups], and Pattern +Converters without requiring any changes to Log4j. +5. Due to the Plugin system configuration is simpler. Entries in the +configuration do not require a class name to be specified. +6. Support for link:customloglevels.html[custom log levels]. Custom log +levels can be defined in code or in configuration. +7. Support for link:api.html#LambdaSupport[lambda expressions]. Client +code running on Java 8 can use lambda expressions to lazily construct a +log message only if the requested log level is enabled. Explicit level +checks are not needed, resulting in cleaner code. +8. Support for link:messages.html[Message objects]. Messages allow +support for interesting and complex constructs to be passed through the +logging system and be efficiently manipulated. Users are free to create +their own `Message` types and write custom link:layouts.html[Layouts], +link:filters.html[Filters] and link:lookups.html[Lookups] to manipulate +them. +9. Log4j 1.x supports Filters on Appenders. Logback added TurboFilters +to allow filtering of events before they are processed by a Logger. +Log4j 2 supports Filters that can be configured to process events before +they are handled by a Logger, as they are processed by a Logger or on an +Appender. +10. Many Logback Appenders do not accept a Layout and will only send +data in a fixed format. Most Log4j 2 Appenders accept a Layout, allowing +the data to be transported in any format desired. +11. Layouts in Log4j 1.x and Logback return a String. This resulted in +the problems discussed at +http://logback.qos.ch/manual/encoders.html[Logback Encoders]. Log4j 2 +takes the simpler approach that link:layouts.html[Layouts] always return +a byte array. This has the advantage that it means they can be used in +virtually any Appender, not just the ones that write to an OutputStream. +12. The link:appenders.html#SyslogAppender[Syslog Appender] supports +both TCP and UDP as well as support for the BSD syslog and the +http://tools.ietf.org/html/rfc5424[RFC 5424] formats. +13. Log4j 2 takes advantage of Java 5 concurrency support and performs +locking at the lowest level possible. Log4j 1.x has known deadlock +issues. Many of these are fixed in Logback but many Logback classes +still require synchronization at a fairly high level. +14. It is an Apache Software Foundation project following the community +and support model used by all ASF projects. If you want to contribute or +gain the right to commit changes just follow the path outlined at +http://jakarta.apache.org/site/contributing.html[Contributing]. diff --git a/src/site/asciidoc/manual/jmx.adoc b/src/site/asciidoc/manual/jmx.adoc new file mode 100644 index 00000000000..e434b0cff82 --- /dev/null +++ b/src/site/asciidoc/manual/jmx.adoc @@ -0,0 +1,208 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += JMX +Remko Popma + +Log4j 2 has built-in support for JMX. The StatusLogger, ContextSelector, +and all LoggerContexts, LoggerConfigs and Appenders are instrumented +with MBeans and can be remotely monitored and controlled. + +Also included is a simple client GUI that can be used to monitor the +StatusLogger output, as well as to remotely reconfigure Log4j with a +different configuration file, or to edit the current configuration +directly. + +[#Enabling_JMX] +== Enabling JMX + +JMX support is enabled by default. When Log4j initializes, the +StatusLogger, ContextSelector, and all LoggerContexts, LoggerConfigs and +Appenders are instrumented with MBeans. To disable JMX completely, and +prevent these MBeans from being created, specify system property +`log4j2.disableJmx` to `true` when you start the Java VM. + +[#Local] +== Local Monitoring and Management + +To perform local monitoring you don't need to specify any system +properties. The JConsole tool that is included in the Java JDK can be +used to monitor your application. Start JConsole by typing +`$JAVA_HOME/bin/jconsole` in a command shell. For more details, +see Oracle's documentation on +https://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html[how +to use JConsole]. + +[#Remote] +== Remote Monitoring and Management + +To enable monitoring and management from remote systems, set the +following system property when starting the Java VM. + +`com.sun.management.jmxremote.port=portNum` + +In the property above, `portNum` is the port number through which you +want to enable JMX RMI connections. + +For more details, see Oracle's documentation on +https://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html#gdenl[Remote +Monitoring and Management]. + +[#RMI_GC] +== RMI impact on Garbage Collection + +Be aware that RMI by default triggers a full GC every hour. See the +https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/sunrmiproperties.html[Oracle +documentation] for the `sun.rmi.dgc.server.gcInterval` and +`sun.rmi.dgc.client.gcInterval` properties. The default value of both +properties is 3600000 milliseconds (one hour). Before Java 6, it was one +minute. + +The two sun.rmi arguments reflect whether your JVM is running in server +or client mode. If you want to modify the GC interval time it may be +best to specify both properties to ensure the argument is picked up by +the JVM. + +An alternative may be to disable explicit calls to `System.gc()` +altogether with `-XX:+DisableExplicitGC`, or (if you are using the CMS +or G1 collector) add `-XX:+ExplicitGCInvokesConcurrent` to ensure the +full GCs are done concurrently in parallel with your application instead +of forcing a stop-the-world collection. + +[#Log4j_MBeans] +== Log4j Instrumented Components + +The best way to find out which methods and attributes of the various +Log4j components are accessible via JMX is to look at the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/jmx/package-summary.html[Javadoc] +or by exploring directly in JConsole. + +The screenshot below shows the Log4j MBeans in JConsole. + +image:../images/jmx-jconsole-mbeans.png[JConsole screenshot of the +MBeans tab] + +[#ClientGUI] +== Client GUI + +Log4j includes a basic client GUI that can be used to monitor the +StatusLogger output and to remotely modify the Log4j configuration. The +client GUI can be run as a stand-alone application or as a JConsole +plug-in. + +=== Running the Client GUI as a JConsole Plug-in + +To run the Log4j JMX Client GUI as a JConsole Plug-in, start JConsole +with the following command: + +`$JAVA_HOME/bin/jconsole -pluginpath /path/to/log4j-api-{Log4jReleaseVersion}.jar:/path/to/log4j-core-{Log4jReleaseVersion}.jar:/path/to/log4j-jmx-gui-{Log4jReleaseVersion}.jar` + +or on Windows: + +`%JAVA_HOME%\bin\jconsole -pluginpath \path\to\log4j-api-{Log4jReleaseVersion}.jar;\path\to\log4j-core-{Log4jReleaseVersion}.jar;\path\to\log4j-jmx-gui-{Log4jReleaseVersion}.jar` + +If you execute the above command and connect to your application, you +will see an extra "Log4j 2" tab in the JConsole window. This tab +contains the client GUI, with the StatusLogger selected. The screenshot +below shows the StatusLogger panel in JConsole. + +image:../images/jmx-jconsole-statuslogger.png[JConsole screenshot of the +StatusLogger display] + +=== Remotely Editing the Log4j Configuration + +The client GUI also contains a simple editor that can be used to +remotely change the Log4j configuration. + +The screenshot below shows the configuration edit panel in JConsole. + +image:../images/jmx-jconsole-editconfig.png[JConsole screenshot of the +configuration file editor] + +The configuration edit panel provides two ways to modify the Log4j +configuration: specifying a different configuration location URI, or +modifying the configuration XML directly in the editor panel. + +If you specify a different configuration location URI and click the +"Reconfigure from Location" button, the specified file or resource must +exist and be readable by the application, or an error will occur and the +configuration will not change. If an error occurred while processing the +contents of the specified resource, Log4j will keep its original +configuration, but the editor panel will show the contents of the file +you specified. + +The text area showing the contents of the configuration file is +editable, and you can directly modify the configuration in this editor +panel. Clicking the "Reconfigure with XML below" button will send the +configuration text to the remote application where it will be used to +reconfigure Log4j on the fly. This will not overwrite any configuration +file. Reconfiguring with text from the editor happens in memory only and +the text is not permanently stored anywhere. + +[#ClientStandAlone] +=== Running the Client GUI as a Stand-alone Application + +To run the Log4j JMX Client GUI as a stand-alone application, run the +following command: + +`$JAVA_HOME/bin/java -cp /path/to/log4j-api-{Log4jReleaseVersion}.jar:/path/to/log4j-core-{Log4jReleaseVersion}.jar:/path/to/log4j-jmx-gui-{Log4jReleaseVersion}.jar org.apache.logging.log4j.jmx.gui.ClientGui ` + +or on Windows: + +`%JAVA_HOME%\bin\java -cp \path\to\log4j-api-{Log4jReleaseVersion}.jar;\path\to\log4j-core-{Log4jReleaseVersion}.jar;\path\to\log4j-jmx-gui-{Log4jReleaseVersion}.jar org.apache.logging.log4j.jmx.gui.ClientGui ` + +Where `options` are one of the following: + +* `:` +* `service:jmx:rmi:///jndi/rmi://:/jmxrmi` +* `service:jmx:rmi://:/jndi/rmi://:/jmxrmi` + +The port number must be the same as the portNum specified when you +started the application you want to monitor. + +For example, if you started your application with these options: + +.... +com.sun.management.jmxremote.port=33445 +com.sun.management.jmxremote.authenticate=false +com.sun.management.jmxremote.ssl=false +.... + +*(Note that this disables _all_ security so this is not recommended for +production environments. Oracle's documentation on +https://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html#gdenl[Remote +Monitoring and Management] provides details on how to configure JMX more +securely with password authentication and SSL.)* + +Then you can run the client with this command: + +`$JAVA_HOME/bin/java -cp /path/to/log4j-api-{Log4jReleaseVersion}.jar:/path/to/log4j-core-{Log4jReleaseVersion}.jar:/path/to/log4j-jmx-gui-{Log4jReleaseVersion}.jar org.apache.logging.log4j.jmx.gui.ClientGui localhost:33445` + +or on Windows: + +`%JAVA_HOME%\bin\java -cp \path\to\log4j-api-{Log4jReleaseVersion}.jar;\path\to\log4j-core-{Log4jReleaseVersion}.jar;\path\to\log4j-jmx-gui-{Log4jReleaseVersion}.jar org.apache.logging.log4j.jmx.gui.ClientGui localhost:33445` + +The screenshot below shows the StatusLogger panel of the client GUI when +running as a stand-alone application. + +image:../images/jmx-standalone-statuslogger.png[JMX GUI screenshot of +StatusLogger display] + +The screenshot below shows the configuration editor panel of the client +GUI when running as a stand-alone application. + +image:../images/jmx-standalone-editconfig.png[JMX GUI screenshot of +configuration editor] diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm new file mode 100644 index 00000000000..f1bb103a21e --- /dev/null +++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm @@ -0,0 +1,1959 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// +#set($dollar = '$') += JSON Template Layout +Volkan Yazıcı + +`JsonTemplateLayout` is a customizable, efficient, and garbage-free JSON +generating layout. It encodes ``LogEvent``s according to the structure described +by the JSON template provided. In a nutshell, it shines with its + +* Customizable JSON structure (see `eventTemplate[Uri]` and + `stackTraceElementTemplate[Uri]` xref:layout-config[layout configuration] parameters) + +* Customizable timestamp formatting (see xref:event-template-resolver-timestamp[] + event template resolver) + +* Feature rich exception formatting (see xref:event-template-resolver-exception[] + and xref:event-template-resolver-exceptionRootCause[] event template resolvers) + +* xref:extending[Extensible plugin support] + +* Customizable object xref:recycling-strategy[recycling strategy] + +[#usage] +== Usage + +Adding `log4j-layout-template-json` artifact to your list of dependencies is +enough to enable access to `JsonTemplateLayout` in your Log4j configuration: + +[source,xml] +---- + + org.apache.logging.log4j + log4j-layout-template-json + ${Log4jReleaseVersion} + +---- + +For instance, given the following JSON template modelling +https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[the Elastic Common Schema (ECS) specification] +(accessible via `classpath:EcsLayout.json`) + +[source,json] +---- +{ + "@timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC" + } + }, + "ecs.version": "1.2.0", + "log.level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message", + "stringified": true + }, + "process.thread.name": { + "$resolver": "thread", + "field": "name" + }, + "log.logger": { + "$resolver": "logger", + "field": "name" + }, + "labels": { + "$resolver": "mdc", + "flatten": true, + "stringified": true + }, + "tags": { + "$resolver": "ndc" + }, + "error.type": { + "$resolver": "exception", + "field": "className" + }, + "error.message": { + "$resolver": "exception", + "field": "message" + }, + "error.stack_trace": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } +} +---- + +in combination with the below `log4j2.xml` configuration: + +[source,xml] +---- + +---- + +or with the below `log4j2.properties` configuration: + +[source,ini] +---- +appender.console.json.type = JsonTemplateLayout +appender.console.json.eventTemplateUri = classpath:EcsLayout.json +---- + +`JsonTemplateLayout` generates JSON as follows: + +[source,json] +---- +{ + "@timestamp": "2017-05-25T19:56:23.370Z", + "ecs.version": "1.2.0", + "log.level": "ERROR", + "message": "Hello, error!", + "process.thread.name": "main", + "log.logger": "org.apache.logging.log4j.JsonTemplateLayoutDemo", + "error.type": "java.lang.RuntimeException", + "error.message": "test", + "error.stack_trace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n" +} +---- + +[#layout-config] +== Layout Configuration + +`JsonTemplateLayout` is configured with the following parameters: + +.`JsonTemplateLayout` parameters +[cols="1m,1m,4"] +|=== +| Parameter Name +| Type +| Description + +| charset +| Charset +| `Charset` used for `String` encoding + +| locationInfoEnabled +| boolean +| toggles access to the `LogEvent` source; file name, line number, etc. + (defaults to `false` set by `log4j.layout.jsonTemplate.locationInfoEnabled` + property) + +| stackTraceEnabled +| boolean +| toggles access to the stack traces (defaults to `true` set by + `log4j.layout.jsonTemplate.stackTraceEnabled` property) + +| eventTemplate +| String +| inline JSON template for rendering ``LogEvent``s (has priority over + `eventTemplateUri`, defaults to `null` set by + `log4j.layout.jsonTemplate.eventTemplate` property) + +| eventTemplateUri +| String +| URI pointing to the JSON template for rendering ``LogEvent``s (defaults to + `classpath:EcsLayout.json` set by `log4j.layout.jsonTemplate.eventTemplateUri` + property) + +| eventTemplateRootObjectKey +| String +| if present, the event template is put into a JSON object composed of a single + member with the provided key (defaults to `null` set by + `log4j.layout.jsonTemplate.eventTemplateRootObjectKey` + property) + +| eventTemplateAdditionalField +| EventTemplateAdditionalField[] +| additional key-value pairs appended to the root of the event template + +| stackTraceElementTemplate +| String +| inline JSON template for rendering ``StackTraceElement``s (has priority over + `stackTraceElementTemplateUri`, defaults to `null` set by + `log4j.layout.jsonTemplate.stackTraceElementTemplate` property) + +| stackTraceElementTemplateUri +| String +| URI pointing to the JSON template for rendering ``StackTraceElement``s + (defaults to `classpath:StackTraceElementLayout.json` set by + `log4j.layout.jsonTemplate.stackTraceElementTemplateUri` property) + +| eventDelimiter +| String +| delimiter used for separating rendered ``LogEvent``s (defaults to + `System.lineSeparator()` set by `log4j.layout.jsonTemplate.eventDelimiter` + property) + +| nullEventDelimiterEnabled +| boolean +| append `\0` (`null`) character to the end of every `eventDelimiter` + separating rendered ``LogEvent``s (defaults to `false` set by + `log4j.layout.jsonTemplate.nullEventDelimiterEnabled` property) + +| maxStringLength +| int +| truncate string values longer than the specified limit (defaults to 16384 set + by `log4j.layout.jsonTemplate.maxStringLength` property) + +| truncatedStringSuffix +| String +| suffix to append to strings truncated due to exceeding `maxStringLength` + (defaults to `…` set by `log4j.layout.jsonTemplate.truncatedStringSuffix` + property) + +| recyclerFactory +| RecyclerFactory +| recycling strategy that can either be `dummy`, `threadLocal`, or `queue` + (set by `log4j.layout.jsonTemplate.recyclerFactory` property) +|=== + +[#additional-event-template-fields] +=== Additional event template fields + +Additional event template fields are a convenient short-cut to add custom fields +to a template or override the existing ones. Following configuration overrides +the `host` field of the `GelfLayout.json` template and adds two new custom +fields: + +.XML configuration with additional fields +[source,xml] +---- + + + + + +---- + +The default `format` for the added new fields are `String`. +One can also provide JSON-formatted additional fields: + +.XML-formatted configuration with JSON-formatted additional fields +[source,xml] +---- + + + + + +---- + +Additional event template fields can very well be introduced using properties-, +YAML-, and JSON-formatted configurations: + +.Properties-formatted configuration with JSON-formatted additional fields +[source,properties] +---- +appender.console.layout.type = JsonTemplateLayout +appender.console.layout.eventTemplateUri = classpath:GelfLayout.json +appender.console.layout.eventTemplateAdditionalField[0].type = EventTemplateAdditionalField +appender.console.layout.eventTemplateAdditionalField[0].key = marker +appender.console.layout.eventTemplateAdditionalField[0].value = {"$resolver": "marker", "field": "name"} +appender.console.layout.eventTemplateAdditionalField[0].format = JSON +appender.console.layout.eventTemplateAdditionalField[1].type = EventTemplateAdditionalField +appender.console.layout.eventTemplateAdditionalField[1].key = aNumber +appender.console.layout.eventTemplateAdditionalField[1].value = 1 +appender.console.layout.eventTemplateAdditionalField[1].format = JSON +appender.console.layout.eventTemplateAdditionalField[2].type = EventTemplateAdditionalField +appender.console.layout.eventTemplateAdditionalField[2].key = aList +appender.console.layout.eventTemplateAdditionalField[2].value = [1, 2, "three"] +appender.console.layout.eventTemplateAdditionalField[2].format = JSON +---- + +.YAML-formatted configuration with JSON-formatted additional fields +[source,yaml] +---- +JsonTemplateLayout: + eventTemplateAdditionalField: + - key: "marker" + value: '{"$resolver": "marker", "field": "name"}' + format: "JSON" + - key: "aNumber" + value: "1" + format: "JSON" + - key: "aList" + value: '[1, 2, "three"]' + format: "JSON" +---- + +.JSON-formatted configuration with JSON-formatted additional fields +[source,json] +---- +{ + "JsonTemplateLayout": { + "eventTemplateAdditionalField": [ + { + "key": "marker", + "value": "{\"$resolver\": \"marker\", \"field\": \"name\"}", + "format": "JSON" + }, + { + "key": "aNumber", + "value": "1", + "format": "JSON" + }, + { + "key": "aList", + "value": "[1, 2, \"three\"]", + "format": "JSON" + } + ] + } +} +---- + +[#recycling-strategy] +=== Recycling strategy + +`RecyclerFactory` plays a crucial role for determining the memory footprint of +the layout. Template resolvers employ it to create recyclers for objects that +they can reuse. The behavior of each `RecyclerFactory` and when one should +prefer one over another is explained below: + +* `dummy` performs no recycling, hence each recycling attempt will result in a +new instance. This will obviously create a load on the garbage-collector. It +is a good choice for applications with low and medium log rate. + +* `threadLocal` performs the best, since every instance is stored in +``ThreadLocal``s and accessed without any synchronization cost. Though this +might not be a desirable option for applications running with hundreds of +threads or more, e.g., a web servlet. + +* `queue` is the best of both worlds. It allows recycling of objects up to a +certain number (`capacity`). When this limit is exceeded due to excessive +concurrent load (e.g., `capacity` is 50 but there are 51 threads concurrently +trying to log), it starts allocating. `queue` is a good strategy where +`threadLocal` is not desirable. ++ +`queue` also accepts optional `supplier` (of type `java.util.Queue`, defaults to + `org.jctools.queues.MpmcArrayQueue.new` if JCTools is in the classpath; +otherwise `java.util.concurrent.ArrayBlockingQueue.new`) and `capacity` (of +type `int`, defaults to `max(8,2*cpuCount+1)`) parameters: ++ +.Example configurations of `queue` recycling strategy +[source] +---- +queue:supplier=org.jctools.queues.MpmcArrayQueue.new +queue:capacity=10 +queue:supplier=java.util.concurrent.ArrayBlockingQueue.new,capacity=50 +---- + +The default `RecyclerFactory` is `threadLocal`, if +`log4j2.enable.threadlocals=true`; otherwise, `queue`. + +See <> for details on how to introduce custom +`RecyclerFactory` implementations. + +[#template-config] +== Template Configuration + +Templates are configured by means of the following `JsonTemplateLayout` +parameters: + +- `eventTemplate[Uri]` (for serializing ``LogEvent``s) +- `stackTraceElementTemplate[Uri]` (for serializing ``StackStraceElement``s) +- `eventTemplateAdditionalField` (for extending the used event template) + +[#event-templates] +=== Event Templates + +`eventTemplate[Uri]` describes the JSON structure `JsonTemplateLayout` uses to +serialize ``LogEvent``s. The default configuration (accessible by +`log4j.layout.jsonTemplate.eventTemplate[Uri]` property) is set to +`classpath:EcsLayout.json` provided by the `log4j-layout-template-json` +artifact, which contains the following predefined event templates: + +- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/EcsLayout.json[`EcsLayout.json`] + described by https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[the Elastic Common Schema (ECS) specification] + +- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/LogstashJsonEventLayoutV1.json[`LogstashJsonEventLayoutV1.json`] + described in https://github.com/logstash/log4j-jsonevent-layout[Logstash + `json_event` pattern for log4j] + +- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/GelfLayout.json[`GelfLayout.json`] + described by https://docs.graylog.org/en/3.1/pages/gelf.html#gelf-payload-specification[the + Graylog Extended Log Format (GELF) payload specification] with additional + `_thread` and `_logger` fields. (Here it is advised to override the obligatory + `host` field with a user provided constant via + xref:additional-event-template-fields[additional event template fields] + to avoid `hostName` property lookup at runtime, which incurs an extra cost.) + +- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/GcpLayout.json[`GcpLayout.json`] + described by https://cloud.google.com/logging/docs/structured-logging[Google + Cloud Platform structured logging] with additional + `_thread`, `_logger` and `_exception` fields. The exception trace, if any, + is written to the `_exception` field as well as the `message` field – + the former is useful for explicitly searching/analyzing structured exception + information, while the latter is Google's expected place for the exception, + and integrates with https://cloud.google.com/error-reporting[Google Error Reporting]. + +- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-template-json/src/main/resources/JsonLayout.json[`JsonLayout.json`] + providing the exact JSON structure generated by link:layouts.html#JSONLayout[`JsonLayout`] + with the exception of `thrown` field. (`JsonLayout` serializes the `Throwable` + as is via Jackson `ObjectMapper`, whereas `JsonLayout.json` template of + `JsonTemplateLayout` employs the `StackTraceElementLayout.json` template + for stack traces to generate a document-store-friendly flat structure.) + +[#event-template-resolvers] +==== Event Template Resolvers + +Event template resolvers consume a `LogEvent` and render a certain property of +it at the point of the JSON where they are declared. For instance, `marker` +resolver renders the marker of the event, `level` resolver renders the level, +and so on. An event template resolver is denoted with a special object +containing a`${dollar}resolver` key: + +.Example event template demonstrating the usage of `level` resolver +[source,json] +---- +{ + "version": "1.0", + "level": { + "$resolver": "level", + "field": "name" + } +} +---- + +Here `version` field will be rendered as is, while `level` field will be +populated by the `level` resolver. That is, this template will generate JSON +similar to the following: + +.Example JSON generated from the demonstrated event template +[source,json] +---- +{ + "version": "1.0", + "level": "INFO" +} +---- + +The complete list of available event template resolvers are provided below in +detail. + +[#event-template-resolver-counter] +===== `counter` + +[source] +---- +config = [ start ] , [ overflowing ] , [ stringified ] +start = "start" -> number +overflowing = "overflowing" -> boolean +stringified = "stringified" -> boolean +---- + +Resolves a number from an internal counter. + +Unless provided, `start` and `overflowing` are respectively set to zero and +`true` by default. + +When `stringified` is enabled, which is set to `false by default, the resolved +number will be converted to a string. + +[WARNING] +==== +When `overflowing` is set to `true`, the internal counter is created using a +`long`, which is subject to overflow while incrementing, though garbage-free. +Otherwise, a `BigInteger` is used, which does not overflow, but incurs +allocation costs. +==== + +====== Examples + +Resolves a sequence of numbers starting from 0. Once `Long.MAX_VALUE` is +reached, counter overflows to `Long.MIN_VALUE`. + +[source,json] +---- +{ + "$resolver": "counter" +} +---- + +Resolves a sequence of numbers starting from 1000. Once `Long.MAX_VALUE` is +reached, counter overflows to `Long.MIN_VALUE`. + +[source,json] +---- +{ + "$resolver": "counter", + "start": 1000 +} +---- + +Resolves a sequence of numbers starting from 0 and keeps on doing as long as +JVM heap allows. + +[source,json] +---- +{ + "$resolver": "counter", + "overflowing": false +} +---- + +[#event-template-resolver-caseConverter] +===== `caseConverter` + +[source] +---- +config = case , input , [ locale ] , [ errorHandlingStrategy ] +input = JSON +case = "case" -> ( "upper" | "lower" ) +locale = "locale" -> ( + language | + ( language , "_" , country ) | + ( language , "_" , country , "_" , variant ) + ) +errorHandlingStrategy = "errorHandlingStrategy" -> ( + "fail" | + "pass" | + "replace" + ) +replacement = "replacement" -> JSON +---- + +Converts the case of string values. + +`input` can be any available template value; e.g., a JSON literal, a lookup +string, an object pointing to another resolver. + +Unless provided, `locale` points to the one returned by +`JsonTemplateLayoutDefaults.getLocale()`, which is configured by +`log4j.layout.jsonTemplate.locale` system property and by default set to the +default system locale. + +`errorHandlingStrategy` determines the behavior when either the input doesn't +resolve to a string value or case conversion throws an exception: + +* `fail` propagates the failure +* `pass` causes the resolved value to be passed as is +* `replace` suppresses the failure and replaces it with the `replacement`, +which is set to `null` by default + +`errorHandlingStrategy` is set to `replace` by default. + +Most of the time JSON logs are persisted to a storage solution (e.g., +Elasticsearch) that keeps a statically-typed index on fields. Hence, if a field +is always expected to be of type string, using non-string ``replacement``s or +`pass` in `errorHandlingStrategy` might result in type incompatibility issues at +the storage level. + +[WARNING] +==== +Unless the input value is ``pass``ed intact or ``replace``d, case conversion is +not garbage-free. +==== + +====== Examples + +Convert the resolved log level strings to upper-case: + +[source,json] +---- +{ + "$resolver": "caseConverter", + "case": "upper", + "input": { + "$resolver": "level", + "field": "name" + } +} +---- + +Convert the resolved `USER` environment variable to lower-case using `nl_NL` +locale: + +[source,json] +---- +{ + "$resolver": "caseConverter", + "case": "lower", + "locale": "nl_NL", + "input": "${dollar}{env:USER}" +} +---- + +Convert the resolved `sessionId` thread context data (MDC) to lower-case: + +[source,json] +---- +{ + "$resolver": "caseConverter", + "case": "lower", + "input": { + "$resolver": "mdc", + "key": "sessionId" + } +} +---- + +Above, if `sessionId` MDC resolves to a, say, number, case conversion will fail. +Since `errorHandlingStrategy` is set to `replace` and replacement is set to +`null` by default, the resolved value will be `null`. One can suppress this +behavior and let the resolved `sessionId` number be left as is: + +[source,json] +---- +{ + "$resolver": "caseConverter", + "case": "lower", + "input": { + "$resolver": "mdc", + "key": "sessionId" + }, + "errorHandlingStrategy": "pass" +} +---- + +or replace it with a custom string: + +[source,json] +---- +{ + "$resolver": "caseConverter", + "case": "lower", + "input": { + "$resolver": "mdc", + "key": "sessionId" + }, + "errorHandlingStrategy": "replace", + "replacement": "unknown" +} +---- + +[#event-template-resolver-endOfBatch] +===== `endOfBatch` + +[source,json] +---- +{ + "$resolver": "endOfBatch" +} +---- + +Resolves `logEvent.isEndOfBatch()` boolean flag. + +[#event-template-resolver-exception] +===== `exception` + +[source] +---- +config = field , [ stringified ] , [ stackTrace ] +field = "field" -> ( "className" | "message" | "stackTrace" ) + +stackTrace = "stackTrace" -> ( + [ stringified ] + , [ elementTemplate ] + ) + +stringified = "stringified" -> ( boolean | truncation ) +truncation = "truncation" -> ( + [ suffix ] + , [ pointMatcherStrings ] + , [ pointMatcherRegexes ] + ) +suffix = "suffix" -> string +pointMatcherStrings = "pointMatcherStrings" -> string[] +pointMatcherRegexes = "pointMatcherRegexes" -> string[] + +elementTemplate = "elementTemplate" -> object +---- + +Resolves fields of the `Throwable` returned by `logEvent.getThrown()`. + +`stringified` is set to `false` by default. `stringified` at the root level is +*deprecated* in favor of `stackTrace.stringified`, which has precedence if both +are provided. + +`pointMatcherStrings` and `pointMatcherRegexes` enable the truncation of +stringified stack traces after the given matching point. If both parameters are +provided, `pointMatcherStrings` will be checked first. + +If a stringified stack trace truncation takes place, it will be indicated with a +`suffix`, which by default is set to the configured `truncatedStringSuffix` in +the layout, unless explicitly provided. Every truncation suffix is prefixed with +a newline. + +Stringified stack trace truncation operates in `Caused by:` and `Suppressed:` +label blocks. That is, matchers are executed against each label in isolation. + +`elementTemplate` is an object describing the template to be used while +resolving the `StackTraceElement` array. If `stringified` is set to `true`, +`elementTemplate` will be discarded. By default, `elementTemplate` is set to +`null` and rather populated from the layout configuration. That is, the stack +trace element template can also be provided using +`stackTraceElementTemplate[Uri]` layout configuration parameters. The template +to be employed is determined in the following order: + +. `elementTemplate` provided in the resolver configuration + +. `stackTraceElementTemplate` parameter from layout configuration +(the default is populated from `log4j.layout.jsonTemplate.stackTraceElementTemplate` +system property) + +. `stackTraceElementTemplateUri` parameter from layout configuration +(the default is populated from `log4j.layout.jsonTemplate.stackTraceElementTemplateUri` +system property) + +See <> +for the list of available resolvers in a stack trace element template. + +Note that this resolver is toggled by +`log4j.layout.jsonTemplate.stackTraceEnabled` property. + +[WARNING] +==== +Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`, +access to (and hence rendering of) stack traces are not garbage-free. + +Each `pointMatcherRegexes` item triggers a `Pattern#matcher()` call, which is +not garbage-free either. +==== + +Resolve `logEvent.getThrown().getClass().getCanonicalName()`: + +[source,json] +---- +{ + "$resolver": "exception", + "field": "className" +} +---- + +Resolve the stack trace into a list of `StackTraceElement` objects: + +[source,json] +---- +{ + "$resolver": "exception", + "field": "stackTrace" +} +---- + +Resolve the stack trace into a string field: + +[source,json] +---- +{ + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } +} +---- + +Resolve the stack trace into a string field such that the content will be +truncated after the given point matcher: + +[source,json] +---- +{ + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": { + "truncation": { + "suffix": "... [truncated]", + "pointMatcherStrings": ["at javax.servlet.http.HttpServlet.service"] + } + } + } +} +---- + +Resolve the stack trace into an object described by the provided stack trace +element template: + +[source,json] +---- +{ + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "elementTemplate": { + "class": { + "$resolver": "stackTraceElement", + "field": "className" + }, + "method": { + "$resolver": "stackTraceElement", + "field": "methodName" + }, + "file": { + "$resolver": "stackTraceElement", + "field": "fileName" + }, + "line": { + "$resolver": "stackTraceElement", + "field": "lineNumber" + } + } + } +} +---- + +See <> for further details on resolvers available +for ``StackTraceElement`` templates. + +[#event-template-resolver-exceptionRootCause] +===== `exceptionRootCause` + +Resolves the fields of the innermost `Throwable` returned by +`logEvent.getThrown()`. Its syntax and garbage-footprint are identical to the +xref:event-template-resolver-exception[] resolver. + +[#event-template-resolver-level] +===== `level` + +[source] +---- +config = field , [ severity ] +field = "field" -> ( "name" | "severity" ) +severity = severity-field +severity-field = "field" -> ( "keyword" | "code" ) +---- + +Resolves the fields of the `logEvent.getLevel()`. + +Resolve the level name: + +[source,json] +---- +{ + "$resolver": "level", + "field": "name" +} +---- + +Resolve the https://en.wikipedia.org/wiki/Syslog#Severity_levels[Syslog severity] +keyword: + +[source,json] +---- +{ + "$resolver": "level", + "field": "severity", + "severity": { + "field": "keyword" + } +} +---- + +Resolve the https://en.wikipedia.org/wiki/Syslog#Severity_levels[Syslog severity] +code: + +[source,json] +---- +{ + "$resolver": "level", + "field": "severity", + "severity": { + "field": "code" + } +} +---- + +[#event-template-resolver-logger] +===== `logger` + +[source] +---- +config = "field" -> ( "name" | "fqcn" ) +---- + +Resolves `logEvent.getLoggerFqcn()` and `logEvent.getLoggerName()`. + +Resolve the logger name: + +[source,json] +---- +{ + "$resolver": "logger", + "field": "name" +} +---- + +Resolve the logger's fully qualified class name: + +[source,json] +---- +{ + "$resolver": "logger", + "field": "fqcn" +} +---- + +[#event-template-resolver-main] +===== `main` + +[source] +---- +config = ( index | key ) +index = "index" -> number +key = "key" -> string +---- + +Performs link:lookups.html#AppMainArgsLookup[Main Argument Lookup] for the +given `index` or `key`. + +Resolve the 1st `main()` method argument: + +[source,json] +---- +{ + "$resolver": "main", + "index": 0 +} +---- + +Resolve the argument coming right after `--userId`: + +[source,json] +---- +{ + "$resolver": "main", + "key": "--userId" +} +---- + +[#event-template-resolver-map] +===== `map` + +Resolves ``MapMessage``s. See link:#map-resolver-template[Map Resolver Template] +for details. + +[#event-template-resolver-mdc] +===== `mdc` + +Resolves Mapped Diagnostic Context (MDC), aka. Thread Context Data. See +link:#map-resolver-template[Map Resolver Template] for details. + +[WARNING] +==== +`log4j2.garbagefreeThreadContextMap` flag needs to be turned on to iterate +the map without allocations. +==== + +[#event-template-resolver-message] +===== `message` + +[source] +---- +config = [ stringified ] , [ fallbackKey ] +stringified = "stringified" -> boolean +fallbackKey = "fallbackKey" -> string +---- + +Resolves `logEvent.getMessage()`. + +[WARNING] +==== +For simple string messages, the resolution is performed without allocations. +For ``ObjectMessage``s and ``MultiformatMessage``s, it depends. +==== + +Resolve the message into a string: + +[source,json] +---- +{ + "$resolver": "message", + "stringified": true +} +---- + +Resolve the message such that if it is an `ObjectMessage` or a +`MultiformatMessage` with JSON support, its type (string, list, object, etc.) +will be retained: + +[source,json] +---- +{ + "$resolver": "message" +} +---- + +Given the above configuration, a `SimpleMessage` will generate a `"sample log +message"`, whereas a `MapMessage` will generate a `{"action": "login", +"sessionId": "87asd97a"}`. Certain indexed log storage systems (e.g., +https://www.elastic.co/elasticsearch/[Elasticsearch]) will not allow both values +to coexist due to type mismatch: one is a `string` while the other is an `object`. +Here one can use a `fallbackKey` to work around the problem: + +[source,json] +---- +{ + "$resolver": "message", + "fallbackKey": "formattedMessage" +} +---- + +Using this configuration, a `SimpleMessage` will generate a +`{"formattedMessage": "sample log message"}` and a `MapMessage` will generate a +`{"action": "login", "sessionId": "87asd97a"}`. Note that both emitted JSONs are +of type `object` and have no type-conflicting fields. + +[#event-template-resolver-messageParameter] +===== `messageParameter` + +[source] +---- +config = [ stringified ] , [ index ] +stringified = "stringified" -> boolean +index = "index" -> number +---- + +Resolves `logEvent.getMessage().getParameters()`. + +[WARNING] +==== +Regarding garbage footprint, `stringified` flag translates to +`String.valueOf(value)`, hence mind not-`String`-typed values. Further, +`logEvent.getMessage()` is expected to implement `ParameterVisitable` interface, +which is the case if `log4j2.enableThreadLocals` property set to true. +==== + +Resolve the message parameters into an array: + +[source,json] +---- +{ + "$resolver": "messageParameter" +} +---- + +Resolve the string representation of all message parameters into an array: + +[source,json] +---- +{ + "$resolver": "messageParameter", + "stringified": true +} +---- + +Resolve the first message parameter: + +[source,json] +---- +{ + "$resolver": "messageParameter", + "index": 0 +} +---- + +Resolve the string representation of the first message parameter: + +[source,json] +---- +{ + "$resolver": "messageParameter", + "index": 0, + "stringified": true +} +---- + +[#event-template-resolver-ndc] +===== `ndc` + +[source] +---- +config = [ pattern ] +pattern = "pattern" -> string +---- + +Resolves the Nested Diagnostic Context (NDC), aka. Thread Context Stack, +`String[]` returned by `logEvent.getContextStack()`. + +Resolve all NDC values into a list: + +[source,json] +---- +{ + "$resolver": "ndc" +} +---- + +Resolve all NDC values matching with the `pattern` regex: + +[source,json] +---- +{ + "$resolver": "ndc", + "pattern": "user(Role|Rank):\\w+" +} +---- + +[#event-template-resolver-pattern] +===== `pattern` + +[source] +---- +config = pattern , [ stackTraceEnabled ] +pattern = "pattern" -> string +stackTraceEnabled = "stackTraceEnabled" -> boolean +---- + +Resolver delegating to link:layouts.html#PatternLayout[`PatternLayout`]. + +The default value of `stackTraceEnabled` is inherited from the parent +`JsonTemplateLayout`. + +Resolve the string produced by `%p %c{1.} [%t] %X{userId} %X %m%ex` pattern: + +[source,json] +---- +{ + "$resolver": "pattern", + "pattern": "%p %c{1.} [%t] %X{userId} %X %m%ex" +} +---- + +[#event-template-resolver-source] +===== `source` + +[source] +---- +config = "field" -> ( + "className" | + "fileName" | + "methodName" | + "lineNumber" ) +---- + +Resolves the fields of the `StackTraceElement` returned by +`logEvent.getSource()`. + +Note that this resolver is toggled by +`log4j.layout.jsonTemplate.locationInfoEnabled` property. + +Resolve the line number: + +[source,json] +---- +{ + "$resolver": "source", + "field": "lineNumber" +} +---- + +[#event-template-resolver-thread] +===== `thread` + +[source] +---- +config = "field" -> ( "name" | "id" | "priority" ) +---- + +Resolves `logEvent.getThreadId()`, `logEvent.getThreadName()`, +`logEvent.getThreadPriority()`. + +Resolve the thread name: + +[source,json] +---- +{ + "$resolver": "thread", + "field": "name" +} +---- + +[#event-template-resolver-timestamp] +===== `timestamp` + +[source] +---- +config = [ patternConfig | epochConfig ] + +patternConfig = "pattern" -> ( [ format ] , [ timeZone ] , [ locale ] ) +format = "format" -> string +timeZone = "timeZone" -> string +locale = "locale" -> ( + language | + ( language , "_" , country ) | + ( language , "_" , country , "_" , variant ) + ) + +epochConfig = "epoch" -> ( unit , [ rounded ] ) +unit = "unit" -> ( + "nanos" | + "millis" | + "secs" | + "millis.nanos" | + "secs.nanos" | + ) +rounded = "rounded" -> boolean +---- + +Resolves `logEvent.getInstant()` in various forms. + +.`timestamp` template resolver examples +[cols="5,2m"] +|=== +| Configuration +| Output + +a| +[source,json] +---- +{ + "$resolver": "timestamp" +} +---- +| 2020-02-07T13:38:47.098+02:00 + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC", + "locale": "en_US" + } +} +---- +| 2020-02-07T13:38:47.098Z + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "epoch": { + "unit": "secs" + } +} +---- +| 1581082727.982123456 + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "epoch": { + "unit": "secs", + "rounded": true + } +} +---- +| 1581082727 + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "epoch": { + "unit": "secs.nanos" + } +} +---- +| 982123456 + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "epoch": { + "unit": "millis" + } +} +---- +| 1581082727982.123456 + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "epoch": { + "unit": "millis", + "rounded": true + } +} +---- +| 1581082727982 + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "epoch": { + "unit": "millis.nanos" + } +} +---- +| 123456 + +a| +[source,json] +---- +{ + "$resolver": "timestamp", + "epoch": { + "unit": "nanos" + } +} +---- +| 1581082727982123456 +|=== + +[#map-resolver-template] +==== Map Resolver Template + +`ReadOnlyStringMap` is Log4j's `Map` equivalent with +garbage-free accessors and heavily employed throughout the code base. It is the +data structure backing both Mapped Diagnostic Context (MDC), aka. Thread Context +Data and `MapMessage` implementations. Hence template resolvers for both of +these are provided by a single backend: `ReadOnlyStringMapResolver`. Put another +way, both `mdc` and `map` resolvers support identical configuration, behaviour, +and garbage footprint, which are detailed below. + +[source] +---- +config = singleAccess | multiAccess + +singleAccess = key , [ stringified ] +key = "key" -> string +stringified = "stringified" -> boolean + +multiAccess = [ pattern ] , [ replacement ] , [ flatten ] , [ stringified ] +pattern = "pattern" -> string +replacement = "replacement" -> string +flatten = "flatten" -> ( boolean | flattenConfig ) +flattenConfig = [ flattenPrefix ] +flattenPrefix = "prefix" -> string +---- + +`singleAccess` resolves a single field, whilst `multiAccess` resolves a +multitude of fields. If `flatten` is provided, `multiAccess` merges the fields +with the parent, otherwise creates a new JSON object containing the values. + +Enabling `stringified` flag converts each value to its string representation. + +Regex provided in the `pattern` is used to match against the keys. If provided, +`replacement` will be used to replace the matched keys. These two are +effectively equivalent to `Pattern.compile(pattern).matcher(key).matches()` and +`Pattern.compile(pattern).matcher(key).replaceAll(replacement)` calls. + +[WARNING] +==== +Regarding garbage footprint, `stringified` flag translates to +`String.valueOf(value)`, hence mind not-`String`-typed values. + +`pattern` and `replacement` incur pattern matcher allocation costs. + +Writing certain non-primitive values (e.g., `BigDecimal`, `Set`, etc.) to JSON +generates garbage, though most (e.g., `int`, `long`, `String`, `List`, +`boolean[]`, etc.) don't. +==== + +`"${dollar}resolver"` is left out in the following examples, since it is to be +defined by the actual resolver, e.g., `map`, `mdc`. + +Resolve the value of the field keyed with `user:role`: + +[source,json] +---- +{ + "$resolver": "…", + "key": "user:role" +} +---- + +Resolve the string representation of the `user:rank` field value: + +[source,json] +---- +{ + "$resolver": "…", + "key": "user:rank", + "stringified": true +} +---- + +Resolve all fields into an object: + +[source,json] +---- +{ + "$resolver": "…" +} +---- + +Resolve all fields into an object such that values are converted to string: + +[source,json] +---- +{ + "$resolver": "…", + "stringified": true +} +---- + +Resolve all fields whose keys match with the `user:(role|rank)` regex into an +object: + +[source,json] +---- +{ + "$resolver": "…", + "pattern": "user:(role|rank)" +} +---- + +Resolve all fields whose keys match with the `user:(role|rank)` regex into an +object after removing the `user:` prefix in the key: + +[source,json] +---- +{ + "$resolver": "…", + "pattern": "user:(role|rank)", + "replacement": "$1" +} +---- + +Merge all fields whose keys are matching with the `user:(role|rank)` regex into +the parent: + +[source,json] +---- +{ + "$resolver": "…", + "flatten": true, + "pattern": "user:(role|rank)" +} +---- + +After converting the corresponding field values to string, merge all fields to +parent such that keys are prefixed with `_`: + +[source,json] +---- +{ + "$resolver": "…", + "stringified": true, + "flatten": { + "prefix": "_" + } +} +---- + +[#stack-trace-element-templates] +=== Stack Trace Element Templates + +xref:event-template-resolver-exception[] and +xref:event-template-resolver-exceptionRootCause[] event template resolvers can +serialize an exception stack trace (i.e., `StackTraceElement[]` returned by +`Throwable#getStackTrace()`) into a JSON array. While doing so, JSON templating +infrastructure is used again. + +`stackTraceElement[Uri]` describes the JSON structure `JsonTemplateLayout` uses +to format ``StackTraceElement``s. The default configuration (accessible by +`log4j.layout.jsonTemplate.stackTraceElementTemplate[Uri]` property) is set to +`classpath:StackTraceElementLayout.json` provided by the +`log4j-layout-template-json` artifact: + +[source,json] +---- +{ + "class": { + "$resolver": "stackTraceElement", + "field": "className" + }, + "method": { + "$resolver": "stackTraceElement", + "field": "methodName" + }, + "file": { + "$resolver": "stackTraceElement", + "field": "fileName" + }, + "line": { + "$resolver": "stackTraceElement", + "field": "lineNumber" + } +} +---- + +The allowed template configuration syntax is as follows: + +[source] +---- +config = "field" -> ( + "className" | + "fileName" | + "methodName" | + "lineNumber" ) +---- + +All above accesses to `StackTraceElement` is garbage-free. + +[#extending] +== Extending + +`JsonTemplateLayout` relies on Log4j link:plugins.html[plugin system] to build +up the features it provides. This enables feature customization a breeze for +users. As of this moment, following features are implemented by means of +plugins: + +* Event template resolvers (e.g., `exception`, `message`, `level` event template resolvers) +* Event template interceptors (e.g., injection of `eventTemplateAdditionalField`) +* Recycler factories + +Following sections cover these in detail. + +[#extending-plugins] +=== Plugin Preliminaries + +Log4j plugin system is the de facto extension mechanism embraced by various +Log4j components, including `JsonTemplateLayout`. Plugins make it possible +for extensible components _receive_ feature implementations without any explicit +links in between. It is analogous to a +https://en.wikipedia.org/wiki/Dependency_injection[dependency injection] +framework, but curated for Log4j-specific needs. + +In a nutshell, you annotate your classes with `@Plugin` and their (`static`) +creator methods with `@PluginFactory`. Last, you inform the Log4j plugin system +to discover these custom classes. This can be done either using `packages` +declared in your Log4j configuration or by various other ways described in +link:plugins.html[plugin system documentation]. + +[#extending-event-resolvers] +=== Extending Event Resolvers + +All available xref:event-template-resolvers[event template resolvers] are simple +plugins employed by `JsonTemplateLayout`. To add new ones, one just needs to +create their own `EventResolver` and instruct its injection via a +`@Plugin`-annotated `EventResolverFactory` class. + +For demonstration purposes, below we will create a `randomNumber` event resolver. +Let's start with the actual resolver: + +[source,java] +.Custom random number event resolver +---- +package com.acme.logging.log4j.layout.template.json; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolver; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; + +/** + * Resolves a random floating point number. + * + *

    Configuration

    + * + *
    + * config = ( [ range ] )
    + * range  = number[]
    + * 
    + * + * {@code range} is a number array with two elements, where the first number + * denotes the start (inclusive) and the second denotes the end (exclusive). + * {@code range} is optional and by default set to {@code [0, 1]}. + * + *

    Examples

    + * + * Resolve a random number between 0 and 1: + * + *
    + * {
    + *   "$resolver": "randomNumber"
    + * }
    + * 
    + * + * Resolve a random number between -0.123 and 0.123: + * + *
    + * {
    + *   "$resolver": "randomNumber",
    + *   "range": [-0.123, 0.123]
    + * }
    + * 
    + */ +public final class RandomNumberResolver implements EventResolver { + + private final double loIncLimit; + + private final double hiExcLimit; + + RandomNumberResolver(final TemplateResolverConfig config) { + final List rangeArray = config.getList("range", Number.class); + if (rangeArray == null) { + this.loIncLimit = 0D; + this.hiExcLimit = 1D; + } else if (rangeArray.size() != 2) { + throw new IllegalArgumentException( + "range array must be of size two: " + config); + } else { + this.loIncLimit = rangeArray.get(0).doubleValue(); + this.hiExcLimit = rangeArray.get(1).doubleValue(); + if (loIncLimit > hiExcLimit) { + throw new IllegalArgumentException("invalid range: " + config); + } + } + } + + static String getName() { + return "randomNumber"; + } + + @Override + public void resolve( + final LogEvent value, + final JsonWriter jsonWriter) { + final double randomNumber = + loIncLimit + (hiExcLimit - loIncLimit) * Math.random(); + jsonWriter.writeNumber(randomNumber); + } + +} +---- + +Next create a `EventResolverFactory` class to register `RandomNumberResolver` +into the Log4j plugin system. + +[source,java] +.Resolver factory class to register `RandomNumberResolver` into the Log4j plugin system +---- +package com.acme.logging.log4j.layout.template.json; + +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.PluginFactory; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; + +/** + * {@link RandomNumberResolver} factory. + */ +@Category(TemplateResolverFactory.CATEGORY) +@Plugin("RandomNumberResolverFactory") +public final class RandomNumberResolverFactory implements EventResolverFactory { + + private static final RandomNumberResolverFactory INSTANCE = + new RandomNumberResolverFactory(); + + private RandomNumberResolverFactory() {} + + @PluginFactory + public static RandomNumberResolverFactory getInstance() { + return INSTANCE; + } + + @Override + public String getName() { + return RandomNumberResolver.getName(); + } + + @Override + public RandomNumberResolver create( + final EventResolverContext context, + final TemplateResolverConfig config) { + return new RandomNumberResolver(config); + } + +} +---- + +Almost complete. Last, we need to inform the Log4j plugin system to discover +these custom classes: + +[source,xml] +.Log4j configuration employing custom `randomNumber` resolver +---- + + + + + + + + +---- + +All available event template resolvers are located in +`org.apache.logging.log4j.layout.template.json.resolver` package. It is a fairly +rich resource for inspiration while implementing new resolvers. + +[#extending-template-resolver] +=== Intercepting the Template Resolver Compiler + +`JsonTemplateLayout` allows interception of the template resolver compilation, +which is the process converting a template into a Java function performing the +JSON serialization. This interception mechanism is internally used to implement +`eventTemplateRootObjectKey` and `eventTemplateAdditionalField` features. In a +nutshell, one needs to create a `@Plugin`-annotated class extending from +`EventResolverInterceptor` interface. + +To see the interception in action, check out the `EventRootObjectKeyInterceptor` +class which is responsible for implementing the `eventTemplateRootObjectKey` +feature: + +[source,java] +.Event interceptor to add `eventTemplateRootObjectKey`, if present +---- +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptor; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverInterceptor; + +/** + * Interceptor to add a root object key to the event template. + */ +@Category(TemplateResolverInterceptor.CATEGORY) +@Plugin("EventRootObjectKeyInterceptor") +public class EventRootObjectKeyInterceptor implements EventResolverInterceptor { + + private static final EventRootObjectKeyInterceptor INSTANCE = + new EventRootObjectKeyInterceptor(); + + private EventRootObjectKeyInterceptor() {} + + @PluginFactory + public static EventRootObjectKeyInterceptor getInstance() { + return INSTANCE; + } + + @Override + public Object processTemplateBeforeResolverInjection( + final EventResolverContext context, + final Object node) { + String eventTemplateRootObjectKey = context.getEventTemplateRootObjectKey(); + return eventTemplateRootObjectKey != null + ? Collections.singletonMap(eventTemplateRootObjectKey, node) + : node; + } + +} +---- + +Here, `processTemplateBeforeResolverInjection()` method checks if the user has +provided an `eventTemplateRootObjectKey`. If so, it wraps the root `node` with a +new object; otherwise, returns the `node` as is. Note that `node` refers to the +root Java object of the event template read by `JsonReader`. + +[#extending-recycler] +=== Extending Recycler Factories + +`recyclerFactory` input `String` read from the layout configuration is converted +to a `RecyclerFactory` using the default `RecyclerFactoryConverter` extending +from `TypeConverter`. If one wants to change this behavior, +they simply need to add their own `TypeConverter` implementing +`Comparable>` to prioritize their custom converter. + +[source,java] +.Custom `TypeConverter` for `RecyclerFactory` +---- +package com.acme.logging.log4j.layout.template.json; + +import org.apache.logging.log4j.plugins.Plugin; +import org.apache.logging.log4j.plugins.convert.TypeConverter; + +@Category(TypeConverter.CATEGORY) +@Plugin("AcmeRecyclerFactoryConverter") +public final class AcmeRecyclerFactoryConverter + implements TypeConverter, Comparable> { + + @Override + public RecyclerFactory convert(final String recyclerFactorySpec) { + return AcmeRecyclerFactory.ofSpec(recyclerFactorySpec); + } + + @Override + public int compareTo(final TypeConverter ignored) { + return -1; + } + +} +---- + +Here note that `compareTo()` always returns -1 to rank it higher compared to +other matching converters. + +[#features] +== Features + +Below is a feature comparison matrix between `JsonTemplateLayout` and +alternatives. + +.Feature comparison matrix +[cols="3,1,1,1,1"] +|=== +| Feature +| `JsonTemplateLayout` +| link:layouts.html#JSONLayout[`JsonLayout`] +| link:layouts.html#GELFLayout[`GelfLayout`] +| https://github.com/elastic/java-ecs-logging/tree/master/log4j2-ecs-layout[`EcsLayout`] + +| Java version +| 8 +| 8 +| 8 +| 6 + +| Dependencies +| None +| Jackson +| None +| None + +| Schema customization? +| ✓ +| ✕ +| ✕ +| ✕ + +| Timestamp customization? +| ✓ +| ✕ +| ✕ +| ✕ + +| (Almost) garbage-free? +| ✓ +| ✕ +| ✓ +| ✓ + +| Custom typed `Message` serialization? +| ✓ +| ✕ +| ✕ +| ?footnote:[Only for ``ObjectMessage``s and if Jackson is in the classpath.] + +| Custom typed `MDC` value serialization? +| ✓ +| ✕ +| ✕ +| ✕ + +| Rendering stack traces as array? +| ✓ +| ✓ +| ✕ +| ✓ + +| Stack trace truncation? +| ✓ +| ✕ +| ✕ +| ✕ + +| JSON pretty print? +| ✕ +| ✓ +| ✕ +| ✕ + +| Additional string fields? +| ✓ +| ✓ +| ✓ +| ✓ + +| Additional JSON fields? +| ✓ +| ✕ +| ✕ +| ✕ + +| Custom resolvers? +| ✓ +| ✕ +| ✕ +| ✕ +|=== + +[#faq] +== F.A.Q. + +[#faq-lookups] +=== Are lookups supported in templates? + +Yes, link:lookups.html[lookups] (e.g., `${dollar}{java:version}`, +`${dollar}{env:USER}`, `${dollar}{date:MM-dd-yyyy}`) are supported in string +literals of templates. Though note that they are not garbage-free. + +=== Are recursive collections supported? + +No. Consider a `Message` containing a recursive value as follows: + +[source,java] +---- +Object[] recursiveCollection = new Object[1]; +recursiveCollection[0] = recursiveCollection; +---- + +While the exact exception might vary, you will most like get a +`StackOverflowError` for trying to render `recursiveCollection` into a +`String`. Note that this is also the default behaviour for other Java standard +library methods, e.g., `Arrays.toString()`. Hence mind self references while +logging. + +[#faq-garbage-free] +=== Is `JsonTemplateLayout` garbage-free? + +Yes, if the garbage-free layout behaviour toggling properties +`log4j2.enableDirectEncoders` and `log4j2.garbagefreeThreadContextMap` are +enabled. Take into account the following caveats: + +* The configured link:#recycling-strategy[recycling strategy] might not be + garbage-free. + +* Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`, + access to (and hence rendering of) stack traces are not garbage-free. + +* Serialization of ``MapMessage``s and ``ObjectMessage``s are mostly + garbage-free except for certain types (e.g., `BigDecimal`, `BigInteger`, + ``Collection``s, except `List`). + +* link:lookups.html[Lookups] (that is, `${...}` variables) are not garbage-free. + +Don't forget to check out xref:event-template-resolvers[the notes on garbage footprint of resolvers] +you employ in templates. diff --git a/src/site/asciidoc/manual/layouts.adoc b/src/site/asciidoc/manual/layouts.adoc new file mode 100644 index 00000000000..659eab8c852 --- /dev/null +++ b/src/site/asciidoc/manual/layouts.adoc @@ -0,0 +1,2397 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Layouts +Ralph Goers ; Gary Gregory ; Volkan Yazıcı + +An Appender uses a Layout to format a LogEvent into a form that meets +the needs of whatever will be consuming the log event. In Log4j 1.x and +Logback Layouts were expected to transform an event into a String. In +Log4j 2 Layouts return a byte array. This allows the result of the +Layout to be useful in many more types of Appenders. However, this means +you need to configure most Layouts with a +https://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[`Charset`] +to ensure the byte array contains correct values. + +The root class for layouts that use a Charset is +`org.apache.logging.log4j.core.layout.AbstractStringLayout` where the +default is UTF-8. Each layout that extends `AbstractStringLayout` can +provide its own default. See each layout below. + +A custom character encoder was added to Log4j 2.4.1 for the ISO-8859-1 +and US-ASCII charsets, to bring some of the performance improvements +built-in to Java 8 to Log4j for use on Java 7. For applications that log +only ISO-8859-1 characters, specifying this charset will improve +performance significantly. + +[#CSVLayouts] +== CSV Layouts + +As of Log4j 2.11.0, CSV support has moved from the existing module +`log4j-core` to the new module `log4j-csv`. + +This layout creates +https://en.wikipedia.org/wiki/Comma-separated_values[Comma Separated +Value (CSV)] records and requires +https://commons.apache.org/proper/commons-csv/[Apache Commons CSV] 1.4. + +The CSV layout can be used in two ways: First, using +`CsvParameterLayout` to log event parameters to create a custom +database, usually to a logger and file appender uniquely configured for +this purpose. Second, using `CsvLogEventLayout` to log events to create +a database, as an alternative to using a full DBMS or using a JDBC +driver that supports the CSV format. + +The `CsvParameterLayout` converts an event's parameters into a CSV +record, ignoring the message. To log CSV records, you can use the usual +Logger methods `info()`, `debug()`, and so on: + +[source,java] +---- +logger.info("Ignored", value1, value2, value3); +---- + +Which will create the CSV record: + +.... +value1, value2, value3 +.... + +Alternatively, you can use a `ObjectArrayMessage`, which only carries +parameters: + +[source,java] +---- +logger.info(new ObjectArrayMessage(value1, value2, value3)); +---- + +The layouts `CsvParameterLayout` and `CsvLogEventLayout` are configured +with the following parameters: + +.CsvParameterLayout and CsvLogEventLayout +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|format +|String +|One of the predefined formats: `Default`, `Excel`, +`MySQL`, `RFC4180`, `TDF`. See +https://commons.apache.org/proper/commons-csv/archives/1.4/apidocs/org/apache/commons/csv/CSVFormat.Predefined.html[CSVFormat.Predefined]. + +|delimiter +|Character +|Sets the delimiter of the format to the specified character. + +|escape +|Character +|Sets the escape character of the format to the specified character. + +|quote +|Character +|Sets the quoteChar of the format to the specified +character. + +|quoteMode +|String +|Sets the output quote policy of the format to the +specified value. One of: `ALL`, `MINIMAL`, `NON_NUMERIC`, `NONE`. + +|nullString +|String +|Writes null as the given nullString when writing records. + +|recordSeparator +|String +|Sets the record separator of the format to the specified String. + +|charset +|Charset +|The output Charset. + +|header +|Sets the header to include when the stream is opened. +|Desc. + +|footer +|Sets the footer to include when the stream is closed. +|Desc. +|=== + +Logging as a CSV events looks like this: + +[source,java] +---- +logger.debug("one={}, two={}, three={}", 1, 2, 3); +---- + +Produces a CSV record with the following fields: + +1. Time Nanos +2. Time Millis +3. Level +4. Thread ID +5. Thread Name +6. Thread Priority +7. Formatted Message +8. Logger FQCN +9. Logger Name +10. Marker +11. Thrown Proxy +12. Source +13. Context Map +14. Context Stack + +.... +0,1441617184044,DEBUG,main,"one=1, two=2, three=3",org.apache.logging.log4j.spi.AbstractLogger,,,,org.apache.logging.log4j.core.layout.CsvLogEventLayoutTest.testLayout(CsvLogEventLayoutTest.java:98),{},[] +.... + +Additional link:../runtime-dependencies.html[runtime dependencies] are +required for using CSV layouts. + +[#GELFLayout] +== GELF Layout + +Lays out events in the Graylog Extended Log Format (GELF) 1.1. + +This layout compresses JSON to GZIP or ZLIB (the `compressionType`) if +log event data is larger than 1024 bytes (the `compressionThreshold`). +This layout does not implement chunking. + +Configure as follows to send to a Graylog 2.x server with UDP: + +[source,xml] +---- + + + + + +---- + +Configure as follows to send to a Graylog 2.x server with TCP: + +[source,xml] +---- + + + + + +---- + +.GelfLayout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|host +|String +|The value of the `host` property (optional, defaults to local host name). + +|compressionType +|`GZIP`, `ZLIB` or `OFF` +|Compression to use (optional, defaults to `GZIP`) + +|compressionThreshold +|int +|Compress if data is larger than this number of bytes (optional, defaults to 1024) + +|includeMapMessage +|boolean +|Whether to include fields from MapMessages as additional fields (optional, default to true). + +|includeNullDelimiter +|boolean +|Whether to include NULL byte as delimiter after each event (optional, default to false). +Useful for Graylog GELF TCP input. Cannot be used with compression. + +|includeStacktrace +|boolean +|Whether to include full stacktrace of logged Throwables (optional, default to true). +If set to false, only the class name and message of the +https://docs.oracle.com/javase/6/docs/api/java/lang/Throwable.html[Throwable] +will be included. + +|includeThreadContext +|boolean +|Whether to include thread context as additional fields (optional, default to true). + +|mapMessageExcludes +|String +|A comma separated list of attributes from the MapMessage to exclude when formatting the event. This +attribute only applies when includeMapMessage="true" is specified. If mapMessageIncludes +are also specified this attribute will be ignored. + +|mapMessageIncludes +|String +|A comma separated list of attributes from the MapMessageto include when formatting the event. This +attribute only applies when includeMapMessage="true" is specified. If mapMessageExcludes +are also specified this attribute will override them. MapMessage fields specified here that +have no value will be omitted. + +|mapPrefix +|String +|A String to prepend to all elements of the MapMessage when rendered as a field. Defaults to an empty String. + +|messagePattern +|String +|The pattern to use to format the String. A messagePattern and patternSelector cannot both be +specified. If both are present the message pattern will be ignored and an error will be logged. +If not supplied only the text derived from the logging message will be used. See +link:#PatternLayout[PatternLayout] for information on the pattern strings. + +|omitEmptyFields +|boolean +|If true fields which are null or are zero-length strings will not be included as a field in +the Gelf JSON. This setting will not affect whether those fields appear in the message fields. The +default value is false. + +|patternSelector +|PatternSelector +|The PatternSelector to use to format the String. A messagePattern and patternSelector cannot both be +specified. If both are present the message pattern will be ignored and an error will be logged. +If not supplied only the text derived from the logging message will be used. +See link:#PatternSelectors[Pattern Selectors] for information on how to specify a +PatternSelector. +See link:#PatternLayout[PatternLayout] for information on the pattern strings. + +|threadContextExcludes +|String +|A comma separated list of ThreadContext attributes to exclude when formatting the event. This +attribute only applies when includeThreadContext="true" is specified. If threadContextIncludes +are also specified this attribute will be ignored. + +|threadContextIncludes +|String +|A comma separated list of ThreadContext attributes to include when formatting the event. This +attribute only applies when includeThreadContext="true" is specified. If threadContextExcludes +are also specified this attribute will override them. ThreadContext fields specified here that +have no value will be omitted. + +|threadContextPrefix +|String +|A String to prepend to all elements of the ThreadContextMap when rendered as a field. Defaults to an empty String. +|=== + +To include any custom field in the output, use following syntax: + +[source,xml] +---- + + %d %5p [%t] %c{1} %X{loginId, requestId} - %m%n + + + +---- + +Custom fields are included in the order they are declared. The values +support link:lookups.html[lookups]. + +See also: + +* The http://docs.graylog.org/en/latest/pages/gelf.html#gelf[GELF +specification] + +[#HTMLLayout] +== HTML Layout + +The HtmlLayout generates an HTML page and adds each LogEvent to a row in +a table. + +.HtmlLayout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|charset +|String +|The character set to use when converting the HTML +String to a byte array. The value must be a valid +http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[Charset]. +If not specified, this layout uses UTF-8. + +|contentType +|String +|The value to assign to the Content-Type header. +The default is "text/html". + +|locationInfo +|boolean +a|[[HtmlLocationInfo]] + +If true, the filename and line number will be included in the HTML +output. The default value is false. + +Generating link:#LocationInformation[location information] is an +expensive operation and may impact performance. Use with caution. + +|title +|String +|A String that will appear as the HTML title. + +|fontName +|String +|The `font-family` to use. The default is "arial,sans-serif". + +|fontSize +|String +|The `font-size` to use. The default is "small". + +|datePattern +|String +|The date format of the logging event. The default is "JVM_ELAPSE_TIME", which outputs the milliseconds since JVM started. For other valid values, refer to the link:#PatternDate[date pattern] of PatternLayout. + +|timezone +|String +|The timezone id of the logging event. If not specified, this layout uses the https://docs.oracle.com/javase/6/docs/api/java/util/TimeZone.html#getDefault()[java.util.TimeZone.getDefault] as default timezone. Like link:#PatternDate[date pattern] of PatternLayout, you can use timezone id from +https://docs.oracle.com/javase/6/docs/api/java/util/TimeZone.html#getTimeZone(java.lang.String)[java.util.TimeZone.getTimeZone]. + +|=== + +Configure as follows to use dataPattern and timezone in HtmlLayout: +[source,xml] +---- + + + + + +---- + +[#JSONLayout] +== JSON Layout + +**Note:** JsonTemplate is considered deprecated. JsonTemplateLayout provides more capabilitites and +should be used instead. + +Appends a series of JSON events as strings serialized as bytes. + +=== Complete well-formed JSON vs. fragment JSON + +If you configure `complete="true"`, the appender outputs a well-formed +JSON document. By default, with `complete="false"`, you should include +the output as an _external file_ in a separate file to form a +well-formed JSON document. + +If `complete="false"`, the appender does not write the JSON open array +character "[" at the start of the document, "]" and the end, nor comma +"," between records. + +Log event follows this pattern: + +[source,json] +---- +{ + "instant" : { + "epochSecond" : 1493121664, + "nanoOfSecond" : 118000000 + }, + "thread" : "main", + "level" : "INFO", + "loggerName" : "HelloWorld", + "marker" : { + "name" : "child", + "parents" : [ { + "name" : "parent", + "parents" : [ { + "name" : "grandparent" + } ] + } ] + }, + "message" : "Hello, world!", + "thrown" : { + "commonElementCount" : 0, + "message" : "error message", + "name" : "java.lang.RuntimeException", + "extendedStackTrace" : [ { + "class" : "logtest.Main", + "method" : "main", + "file" : "Main.java", + "line" : 29, + "exact" : true, + "location" : "classes/", + "version" : "?" + } ] + }, + "contextStack" : [ "one", "two" ], + "endOfBatch" : false, + "loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger", + "contextMap" : { + "bar" : "BAR", + "foo" : "FOO" + }, + "threadId" : 1, + "threadPriority" : 5, + "source" : { + "class" : "logtest.Main", + "method" : "main", + "file" : "Main.java", + "line" : 29 + } +} +---- + +If `complete="false"`, the appender does not write the JSON open array +character "[" at the start of the document, "]" and the end, nor comma +"," between records. + +=== Pretty vs. compact JSON + +The compact attribute determines whether the output will be "pretty" or not. The default value is "false", +which means the appender uses end-of-line characters and indents lines to format the text. If +`compact="true"`, then no end-of-line or indentation is used, which will cause the output +to take less space. Of course, the message content may contain, escaped end-of-lines. + +.JsonLayout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|charset +|String +|The character set to use when converting to a byte +array. The value must be a valid +http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[Charset]. +If not specified, UTF-8 will be used. + +|compact +|boolean +|If true, the appender does not use end-of-lines and +indentation. Defaults to false. + +|eventEol +|boolean +|If true, the appender appends an end-of-line after +each record. Defaults to false. Use with eventEol=true and compact=true +to get one record per line. + +|endOfLine +|String +|If set, overrides the default end-of-line string. E.g. set it to "\n" and use with eventEol=true and compact=true to have one record per line separated by "\n" instead of "\r\n". Defaults to null (i.e. not set). + +|complete +|boolean +|If true, the appender includes the JSON header and +footer, and comma between records. Defaults to false. + +|properties +|boolean +|If true, the appender includes the thread context +map in the generated JSON. Defaults to false. + +|propertiesAsList +|boolean +|If true, the thread context map is included +as a list of map entry objects, where each entry has a "key" attribute +(whose value is the key) and a "value" attribute (whose value is the +value). Defaults to false, in which case the thread context map is +included as a simple map of key-value pairs. + +|locationInfo +|boolean +a| +If true, the appender includes the location information in the generated +JSON. Defaults to false. + +Generating link:#LocationInformation[location information] is an +expensive operation and may impact performance. Use with caution. + +|includeStacktrace +|boolean +|If true, include full stacktrace of any logged +https://docs.oracle.com/javase/6/docs/api/java/lang/Throwable.html[Throwable] +(optional, default to true). + +|includeTimeMillis +|boolean +|If true, the timeMillis attribute is included in the Json payload instead of the instant. timeMillis +will contain the number of milliseconds since midnight, January 1, 1970 UTC. + +|stacktraceAsString +|boolean +|Whether to format the stacktrace as a +string, and not a nested object (optional, defaults to false). + +|includeNullDelimiter +|boolean +|Whether to include NULL byte as +delimiter after each event (optional, default to false). + +|objectMessageAsJsonObject +|boolean +|If true, ObjectMessage is +serialized as JSON object to the "message" field of the output log. +Defaults to false. +|=== + +To include any custom field in the output, use following syntax: + +[source,xml] +---- + + + + +---- + +Custom fields are always last, in the order they are declared. The +values support link:lookups.html[lookups]. + +Additional link:../runtime-dependencies.html[runtime dependencies] are +required for using JsonLayout. + +[#JSONTemplateLayout] +== JSON Template Layout + +`JsonTemplateLayout` is a customizable, efficient, and garbage-free JSON +emitting layout. It encodes ``LogEvent``s according to the structure described +by the JSON template provided. For instance, given the following JSON template +modelling https://github.com/logstash/log4j-jsonevent-layout[the official +Logstash `JSONEventLayoutV1`] + +[source,json] +---- +{ + "mdc": { + "$resolver": "mdc" + }, + "exception": { + "exception_class": { + "$resolver": "exception", + "field": "className" + }, + "exception_message": { + "$resolver": "exception", + "field": "message" + }, + "stacktrace": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } + }, + "line_number": { + "$resolver": "source", + "field": "lineNumber" + }, + "class": { + "$resolver": "source", + "field": "className" + }, + "@version": 1, + "source_host": "${hostName}", + "message": { + "$resolver": "message", + "stringified": true + }, + "thread_name": { + "$resolver": "thread", + "field": "name" + }, + "@timestamp": { + "$resolver": "timestamp" + }, + "level": { + "$resolver": "level", + "field": "name" + }, + "file": { + "$resolver": "source", + "field": "fileName" + }, + "method": { + "$resolver": "source", + "field": "methodName" + }, + "logger_name": { + "$resolver": "logger", + "field": "name" + } +} +---- + +in combination with the below Log4j configuration: + +[source,xml] +---- + +---- + +JSON Template Layout will render JSON documents as follows: + +[source,json] +---- +{ + "exception": { + "exception_class": "java.lang.RuntimeException", + "exception_message": "test", + "stacktrace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n" + }, + "line_number": 12, + "class": "org.apache.logging.log4j.JsonTemplateLayoutDemo", + "@version": 1, + "source_host": "varlik", + "message": "Hello, error!", + "thread_name": "main", + "@timestamp": "2017-05-25T19:56:23.370+02:00", + "level": "ERROR", + "file": "JsonTemplateLayoutDemo.java", + "method": "main", + "logger_name": "org.apache.logging.log4j.JsonTemplateLayoutDemo" +} +---- + +See link:json-template-layout.html[JSON Template Layout] page for the complete +documentation. + +[#PatternLayout] +== Pattern Layout + +A flexible layout configurable with pattern string. The goal of this +class is to format a LogEvent and return the results. The format of the +result depends on the _conversion pattern_. + +The conversion pattern is closely related to the conversion pattern of +the printf function in C. A conversion pattern is composed of literal +text and format control expressions called _conversion specifiers_. + +_Note that any literal text, including *Special Characters*, may be +included in the conversion pattern._ Special Characters include *\t*, +*\n*, *\r*, *\f*. Use *\\* to insert a single backslash into the output. + +Each conversion specifier starts with a percent sign (%) and is followed +by optional _format modifiers_ and a _conversion character_. The +conversion character specifies the type of data, e.g. category, +priority, date, thread name. The format modifiers control such things as +field width, padding, left and right justification. The following is a +simple example. + +Let the conversion pattern be *"%-5p [%t]: %m%n"* and assume that the +Log4j environment was set to use a PatternLayout. Then the statements + +.... +Logger logger = LogManager.getLogger("MyLogger"); +logger.debug("Message 1"); +logger.warn("Message 2"); +.... + +would yield the output + +.... +DEBUG [main]: Message 1 +WARN [main]: Message 2 +.... + +Note that there is no explicit separator between text and conversion +specifiers. The pattern parser knows when it has reached the end of a +conversion specifier when it reads a conversion character. In the +example above the conversion specifier *%-5p* means the priority of the +logging event should be left justified to a width of five characters. + +If the pattern string does not contain a specifier to handle a Throwable +being logged, parsing of the pattern will act as if the "%xEx" specifier +had be added to the end of the string. To suppress formatting of the +Throwable completely simply add "%ex{0}" as a specifier in the pattern +string. + +.PatternLayout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|charset +|String +|The character set to use when converting the syslog +String to a byte array. The String must be a valid +http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[Charset]. +If not specified, this layout uses the platform default character set. + +|pattern +|String +|A composite pattern string of one or more conversion +patterns from the table below. Cannot be specified with a +PatternSelector. + +|patternSelector +|PatternSelector +|A component that analyzes information +in the LogEvent and determines which pattern should be used to format +the event. The pattern and patternSelector parameters are mutually +exclusive. + +|replace +|RegexReplacement +|Allows portions of the resulting String to +be replaced. If configured, the replace element must specify the regular +expression to match and the substitution. This performs a function +similar to the RegexReplacement converter but applies to the whole +message while the converter only applies to the String its pattern +generates. + +|alwaysWriteExceptions +|boolean +|If `true` (it is by default) exceptions +are always written even if the pattern contains no exception +conversions. This means that if you do not include a way to output +exceptions in your pattern, the default exception formatter will be +added to the end of the pattern. Setting this to `false` disables this +behavior and allows you to exclude exceptions from your pattern output. + +|header +|String +|The optional header string to include at the top of +each log file. + +|footer +|String +|The optional footer string to include at the bottom of +each log file. + +|disableAnsi +|boolean +|If `true` (default is false), do not output ANSI +escape codes. + +|noConsoleNoAnsi +|boolean +|If `true` (default is false) and +`System.console()` is null, do not output ANSI escape codes. +|=== + +.RegexReplacement Parameters +|=== +|Parameter Name |Type |Description + +|regex +|String +|A Java-compliant regular expression to match in the resulting string. See +https://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html[Pattern]. + +|replacement +|String +|The string to replace any matched sub-strings with. +|=== + +[#Patterns] +=== Patterns + +The conversions that are provided with Log4j are: + +[cols="1,3a"] +|=== +|Conversion Pattern |Description + +|*c*{precision} + +*logger*{precision} +|Outputs the name of the logger that published the logging event. The +logger conversion specifier can be optionally followed by _precision +specifier_, which consists of a decimal integer, or a pattern starting +with a decimal integer. + +When the precision specifier is an integer value, it reduces the size of +the logger name. If the number is positive, the layout prints the +corresponding number of rightmost logger name components. If negative, +the layout removes the corresponding number of leftmost logger name +components. If the precision contains periods then the number before the first period +identifies the length to be printed from items that precede tokens in the rest of the pattern. +If the number after the first period is followed by an asterisk it indicates how many of the +rightmost tokens will be printed in full. See the table below for abbreviation examples. + +If the precision contains any non-integer characters, then the layout +abbreviates the name based on the pattern. If the precision integer is +less than one, the layout still prints the right-most token in full. By +default, the layout prints the logger name in full. + +!=== +!Conversion Pattern !Logger Name !Result + +!%c{1} +!org.apache.commons.Foo +!Foo + +!%c{2} +!org.apache.commons.Foo +!commons.Foo + +!%c{10} +!org.apache.commons.Foo +!org.apache.commons.Foo + +!%c{-1} +!org.apache.commons.Foo +!apache.commons.Foo + +!%c{-2} +!org.apache.commons.Foo +!commons.Foo + +!%c{-10} +!org.apache.commons.Foo +!org.apache.commons.Foo + +!%c{1.} +!org.apache.commons.Foo +!o.a.c.Foo + +!%c{1.1.\~.~} +!org.apache.commons.test.Foo +!o.a.~.~.Foo + +!%c{.} +!org.apache.commons.test.Foo +!....Foo + +!%c{1.1.1.*} +!org.apache.commons.test.Foo +!o.a.c.test.Foo + +!%c{1.2.*} +!org.apache.commons.test.Foo +!o.a.c.test.Foo + +!%c{1.3.*} +!org.apache.commons.test.Foo +!o.a.commons.test.Foo + +!%c{1.8.*} +!org.apache.commons.test.Foo +!org.apache.commons.test.Foo + +!=== + +|[[PatternClass]] *C*{precision} + +*class*{precision} +|Outputs the fully qualified class name of the caller issuing the logging +request. This conversion specifier can be optionally followed by +_precision specifier_, that follows the same rules as the logger name +converter. + +Generating the class name of the caller +(link:#LocationInformation[location information]) is an expensive +operation and may impact performance. Use with caution. + +|[[PatternDate]] *d*{pattern} + +*date*{pattern} +|Outputs the date of the logging event. The date conversion specifier may +be followed by a set of braces containing a date and time pattern string per +https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html[`SimpleDateFormat`]. + +The predefined _named_ formats are: + +[cols=",",options="header",] +!=== +!Pattern !Example + +!%d{DEFAULT} +!2012-11-02 14:34:02,123 + +!%d{DEFAULT_MICROS} +!2012-11-02 14:34:02,123456 + +!%d{DEFAULT_NANOS} +!2012-11-02 14:34:02,123456789 + +!%d{ISO8601} +!2012-11-02T14:34:02,781 + +!%d{ISO8601_BASIC} +!20121102T143402,781 + +!%d{ISO8601_OFFSET_DATE_TIME_HH} +!2012-11-02'T'14:34:02,781-07 + +!%d{ISO8601_OFFSET_DATE_TIME_HHMM} +!2012-11-02'T'14:34:02,781-0700 + +!%d{ISO8601_OFFSET_DATE_TIME_HHCMM} +!2012-11-02'T'14:34:02,781-07:00 + +!%d{ABSOLUTE} +!14:34:02,781 + +!%d{ABSOLUTE_MICROS} +!14:34:02,123456 + +!%d{ABSOLUTE_NANOS} +!14:34:02,123456789 + +!%d{DATE} +!02 Nov 2012 14:34:02,781 + +!%d{COMPACT} +!20121102143402781 + +!%d{UNIX} +!1351866842 + +!%d{UNIX_MILLIS} +!1351866842781 +!=== + +You can also use a set of braces containing a time zone id per +https://docs.oracle.com/javase/6/docs/api/java/util/TimeZone.html#getTimeZone(java.lang.String)[java.util.TimeZone.getTimeZone]. +If no date format specifier is given then the DEFAULT format is used. + +You can define custom date formats: + +[cols=",",options="header",] +!=== +!Pattern !Example + +!%d{HH:mm:ss,SSS} +!14:34:02,123 + +!%d{HH:mm:ss,nnnn} to %d{HH:mm:ss,nnnnnnnnn} +!14:34:02,1234 to 14:34:02,123456789 + +!%d{dd MMM yyyy HH:mm:ss,SSS} +!02 Nov 2012 14:34:02,123 + +!%d{dd MMM yyyy HH:mm:ss,nnnn} to %d{dd MMM yyyy HH:mm:ss,nnnnnnnnn} +!02 Nov 2012 14:34:02,1234 to 02 Nov 2012 14:34:02,123456789 + +!%d{HH:mm:ss}{GMT+0} +!18:34:02 +!=== + +%d{UNIX} outputs the UNIX time in seconds. %d{UNIX_MILLIS} outputs the +UNIX time in milliseconds. The UNIX time is the difference, in seconds +for UNIX and in milliseconds for UNIX_MILLIS, between the current time +and midnight, January 1, 1970 UTC. While the time unit is milliseconds, +the granularity depends on the operating system +(http://msdn.microsoft.com/en-us/windows/hardware/gg463266.aspx[Windows]). +This is an efficient way to output the event time because only a +conversion from long to String takes place, there is no Date formatting +involved. + +Log4j 2.11 adds limited support for timestamps more precise than +milliseconds when running on Java 9. Note that not all +https://docs.oracle.com/javase/9/docs/api/java/time/format/DateTimeFormatter.html[DateTimeFormatter] +formats are supported. Only timestamps in the formats mentioned in the +table above may use the "nano-of-second" pattern letter `n` instead of +the "fraction-of-second" pattern letter `S`. + +Users may revert back to a millisecond-precision clock when running on +Java 9 by setting system property `log4j2.Clock` to `SystemMillisClock`. + +|*enc*{_pattern_}{[HTML\|XML\|JSON\|CRLF]} + +*encode*{_pattern_}{[HTML\|XML\|JSON\|CRLF]} +|Encodes and escapes special characters suitable for output in specific +markup languages. By default, this encodes for HTML if only one option +is specified. The second option is used to specify which encoding format +should be used. This converter is particularly useful for encoding user +provided data so that the output data is not written improperly or +insecurely. + +A typical usage would encode the message `%enc{%m}` but user input could +come from other locations as well, such as the MDC `%enc{%mdc{key}}` + +Using the HTML encoding format, the following characters are replaced: + +!=== +!Character !Replacement + +!'\r', '\n' +!Converted into escaped strings "\\r" and "\\n" respectively + +!&, <, >, ", ', / +!Replaced with the corresponding HTML entity +!=== + +Using the XML encoding format, this follows the escaping rules specified +by https://www.w3.org/TR/xml/[the XML specification]: + +!=== +!Character !Replacement + +!&, <, >, ", ' +!Replaced with the corresponding XML entity +!=== + +Using the JSON encoding format, this follows the escaping rules +specified by https://www.ietf.org/rfc/rfc4627.txt[RFC 4627 section 2.5]: + +!=== +!Character !Replacement + +!U+0000 - U+001F +!\u0000 - \u001F + +!Any other control characters +!Encoded into its `\uABCD` equivalent escaped code point + +!" +!\" + +!\ +!\\ +!=== + +For example, the pattern `{"message": "%enc{%m}{JSON}"}` could be used +to output a valid JSON document containing the log message as a string +value. + +Using the CRLF encoding format, the following characters are replaced: + +!=== +!Character !Replacement + +!'\r', '\n' +!Converted into escaped strings "\\r" and "\\n" respectively +!=== + +|*equals*{pattern}{test}{substitution} + +*equalsIgnoreCase*{pattern}{test}{substitution} +|Replaces occurrences of 'test', a string, with its replacement +'substitution' in the string resulting from evaluation of the pattern. +For example, "%equals{[%marker]}{[]}\{}" will replace '[]' strings +produces by events without markers with an empty string. + +The pattern can be arbitrarily complex and in particular can contain +multiple conversion keywords. + +|**ex**\|**exception**\|*throwable* + +{ + +  [ "none" + +   \| "full" + +   \| depth + +   \| "short" + +   \| "short.className" + +   \| "short.fileName" + +   \| "short.lineNumber" + +   \| "short.methodName" + +   \| "short.message" + +   \| "short.localizedMessage"] + +} + +  {filters(package,package,...)} + +  {suffix(_pattern_)} + +  {separator(_separator_)} +|Outputs the Throwable trace bound to the logging event, by default this +will output the full trace as one would normally find with a call to +`Throwable.printStackTrace()`. + +You can follow the throwable conversion word with an option in the form +`%throwable{option}`. + +`%throwable{short}` outputs the first line of the Throwable. + +`%throwable{short.className}` outputs the name of the class where the +exception occurred. + +`%throwable{short.methodName}` outputs the method name where the +exception occurred. + +`%throwable{short.fileName}` outputs the name of the class where the +exception occurred. + +`%throwable{short.lineNumber}` outputs the line number where the +exception occurred. + +`%throwable{short.message}` outputs the message. + +`%throwable{short.localizedMessage}` outputs the localized message. + +`%throwable{n}` outputs the first n lines of the stack trace. + +Specifying `%throwable{none}` or `%throwable{0}` suppresses output of +the exception. + +Use `{filters(packages)}` where _packages_ is a list of package names to +suppress matching stack frames from stack traces. + +Use `{suffix(pattern)}` to add the output of _pattern_ at the end of +each stack frames. + +Use a `{separator(...)}` as the end-of-line string. For example: +`separator(\|)`. The default value is the `line.separator` system +property, which is operating system dependent. + +|[[PatternFile]] *F* + +*file* +|Outputs the file name where the logging request was issued. + +Generating the file information (link:#LocationInformation[location +information]) is an expensive operation and may impact performance. Use +with caution. + +|*highlight*{pattern}{style} +|Adds ANSI colors to the result of the enclosed pattern based on the +current event's logging level. (See Jansi link:#enable-jansi[configuration].) + +The default colors for each level are: + +!=== +!Level !ANSI color + +!FATAL +!Bright red + +!ERROR +!Bright red + +!WARN +!Yellow + +!INFO +!Green + +!DEBUG +!Cyan + +!TRACE +!Black (looks dark grey) +!=== + +The color names are ANSI names defined in the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/pattern/AnsiEscape.html[`AnsiEscape`] +class. + +The color and attribute names and are standard, but the exact shade, +hue, or value. + +.Color table +!=== +!Intensity Code !0 !1 !2 !3 !4 !5 !6 !7 + +!Normal !Black !Red !Green !Yellow !Blue !Magenta !Cyan !White + +!Bright !Black !Red !Green !Yellow !Blue !Magenta !Cyan !White +!=== + +You can use the default colors with: + +.... +%highlight{%d [%t] %-5level: %msg%n%throwable} +.... + +You can override the default colors in the optional {style} option. For +example: + +.... +%highlight{%d [%t] %-5level: %msg%n%throwable}{FATAL=white, ERROR=red, WARN=blue, INFO=black, DEBUG=green, TRACE=blue} +.... + +You can highlight only the a portion of the log event: + +.... +%d [%t] %highlight{%-5level: %msg%n%throwable} +.... + +You can style one part of the message and highlight the rest the log +event: + +.... +%style{%d [%t]}{black} %highlight{%-5level: %msg%n%throwable} +.... + +You can also use the STYLE key to use a predefined group of colors: + +.... +%highlight{%d [%t] %-5level: %msg%n%throwable}{STYLE=Logback} +.... + +The STYLE value can be one of: + +* Default: see above +* Logback: +!=== +!Level !ANSI color + +!FATAL !Blinking bright red + +!ERROR !Bright red + +!WARN !Red + +!INFO !Blue + +!DEBUG !Normal + +!TRACE !Normal +!=== + +|[[PatternMap]] *K*{key} + +*map*{key} + +*MAP*{key} +|Outputs the entries in a +link:../log4j-api/apidocs/org/apache/logging/log4j/message/MapMessage.html[MapMessage], +if one is present in the event. The *K* conversion character can be +followed by the key for the map placed between braces, as in +*%K{clientNumber}* where `clientNumber` is the key. The value in the +Map corresponding to the key will be output. If no additional sub-option +is specified, then the entire contents of the Map key value pair set is +output using a format {{key1,val1},{key2,val2}} + +|[[PatternLocation]] *l* + +*location* +|Outputs location information of the caller which generated the logging event. + +The location information depends on the JVM implementation but usually +consists of the fully qualified name of the calling method followed by +the callers source the file name and line number between parentheses. + +Generating link:#LocationInformation[location information] is an +expensive operation and may impact performance. Use with caution. + +|[[PatternLine]] *L* + +*line* +|Outputs the line number from where the logging request was issued. + +Generating line number information (link:#LocationInformation[location +information]) is an expensive operation and may impact performance. Use +with caution. + +|[[PatternMessage]] *m*{lookups}{ansi} + +*msg*{lookups}{ansi} + +*message{lookups}{ansi} +|Outputs the application supplied message associated with the logging +event. + +Add `{ansi}` to render messages with ANSI escape codes (requires JAnsi, +see link:#enable-jansi[configuration].) + +The default syntax for embedded ANSI codes is: + +.... +@\|code(,code)* text\|@ +.... + +For example, to render the message `"Hello"` in green, use: + +.... +@\|green Hello\|@ +.... + +To render the message `"Hello"` in bold and red, use: + +.... +@\|bold,red Warning!\|@ +.... + +You can also define custom style names in the configuration with the +syntax: + +.... +%message{ansi}{StyleName=value(,value)*( StyleName=value(,value)*)*}%n +.... + +For example: + +.... +%message{ansi}{WarningStyle=red,bold KeyStyle=white ValueStyle=blue}%n +.... + +The call site can look like this: + +.... +logger.info("@\|KeyStyle {}\|@ = @\|ValueStyle {}\|@", entry.getKey(), entry.getValue()); +.... + +Use `{lookups}` to log messages like `"${date:YYYY-MM-dd}"` using lookups. +using any lookups. This will replace the date template `{date:YYYY-MM-dd}` +with an actual date. This can be confusing in many cases, and it's often both easier and +more obvious to handle the lookup in code. +This feature is disabled by default and the message string is logged untouched. + +*Note:* Users are *STRONGLY* discouraged from using the lookups option. Doing so may allow uncontrolled user input +containing lookups to take unintended actions. In almost all cases the software developer can accomplish the same tasks +lookups perform directly in the application code. + +|[[PatternMethod]] *M* + +*method* +|Outputs the method name where the logging request was issued. + +Generating the method name of the caller +(link:#LocationInformation[location information]) is an expensive +operation and may impact performance. Use with caution. + +|[[PatternMarker]] *marker* +|The full name of the marker, including parents, if one is present. + +|[[PatternMarkerSimpleName]] *markerSimpleName* +|The simple name of the marker (not including parents), if one is present. + +|[[PatternMaxLength]] *maxLen* + +*maxLength* +|Outputs the result of evaluating the pattern and truncating the result. +If the length is greater than 20, then the output will contain a +trailing ellipsis. If the provided length is invalid, a default value of +100 is used. + +Example syntax: `%maxLen{%p: %c{1} - %m%notEmpty{ =>%ex{short}}}{160}` +will be limited to 160 characters with a trailing ellipsis. Another +example: `%maxLen{%m}{20}` will be limited to 20 characters and no +trailing ellipsis. + +|[[PatternNewLine]] *n* +|Outputs the platform dependent line separator character or characters. + +This conversion character offers practically the same performance as +using non-portable line separator strings such as "\n", or "\r\n". Thus, +it is the preferred way of specifying a line separator. + +|[[NanoTime]] *N* + +*nano* +|Outputs the result of `System.nanoTime()` at the time the log +event was created. + +|[[Process_ID]] *pid*{[defaultValue]} + +*processId*{[defaultValue]} +|Outputs the process ID if supported by the +underlying platform. An optional default value may be specified to be +shown if the platform does not support process IDs. + +|[[VariablesNotEmpty]] *variablesNotEmpty*{pattern} + +*varsNotEmpty*{pattern} + +*notEmpty*{pattern} +|Outputs the result of evaluating the pattern if and only if all +variables in the pattern are not empty. + +For example: + +.... +%notEmpty{[%marker]} +.... + +|[[PatternLevel]] **p**\|*level*{__level__=_label_, __level__=_label_, +...} **p**\|*level*{length=_n_} +**p**\|*level*{lowerCase=__true__\|_false_} +|Outputs the level of the logging event. You provide a level name map in +the form "level=value, level=value" where level is the name of the Level +and value is the value that should be displayed instead of the name of +the Level. + +For example: + +.... +%level{WARN=Warning, DEBUG=Debug, ERROR=Error, TRACE=Trace, INFO=Info} +.... + +Alternatively, for the compact-minded: + +.... +%level{WARN=W, DEBUG=D, ERROR=E, TRACE=T, INFO=I} +.... + +More succinctly, for the same result as above, you can define the length +of the level label: + +.... +%level{length=1} +.... + +If the length is greater than a level name length, the layout uses the +normal level name. + +You can combine the two kinds of options: + +.... +%level{ERROR=Error, length=2} +.... + +This give you the `Error` level name and all other level names of length +2. + +Finally, you can output lower-case level names (the default is +upper-case): + +.... +%level{lowerCase=true} +.... + +|[[PatternRelative]] *r* + +*relative* +|Outputs the number of milliseconds elapsed since the JVM was +started until the creation of the logging event. + +|[[PatternRepeat]] *R*{string}{count} + +*repeat*{string}{count} +|Produces a string containing the requested number of instances of the specified string. +For example, "%repeat{\*}{2}" will result in the string "**". + +|[[PatternReplace]] *replace*{pattern}{regex}{substitution} +|Replaces occurrences of 'regex', a regular expression, with its +replacement 'substitution' in the string resulting from evaluation of +the pattern. For example, "%replace{%msg}{\s}\{}" will remove all +spaces contained in the event message. + +The pattern can be arbitrarily complex and in particular can contain +multiple conversion keywords. For instance, "%replace{%logger +%msg}{\.}{/}" will replace all dots in the logger or the message of +the event with a forward slash. + +|[[PatternException]] **rEx**\|**rException**\|*rThrowable* + +  { + +    ["none" \| "short" \| "full" \| depth] + +    [,filters(package,package,...)] + +    [,separator(_separator_)] + +  } + +  {ansi( + +    Key=Value,Value,... + +    Key=Value,Value,... + +    ...) + +  } + +  {suffix(_pattern_)} + +|The same as the %throwable conversion word but the stack trace is +printed starting with the first exception that was thrown followed by +each subsequent wrapping exception. + +The throwable conversion word can be followed by an option in the form +`%rEx{short}` which will only output the first line of the Throwable or +`%rEx{n}` where the first n lines of the stack trace will be printed. + +Specifying `%rEx{none}` or `%rEx{0}` will suppress printing of the +exception. + +Use `filters(packages)` where _packages_ is a list of package names to +suppress matching stack frames from stack traces. + +Use a `separator` string to separate the lines of a stack trace. For +example: `separator(\|)`. The default value is the `line.separator` +system property, which is operating system dependent. + +Use `rEx{suffix(pattern)` to add the output of _pattern_ to the output +only when there is a throwable to print. + +|[[PatternSequenceNumber]] *sn* + +*sequenceNumber* +|Includes a sequence number that will be incremented in +every event. The counter is a static variable so will only be unique +within applications that share the same converter Class object. + +|[[PatternStyle]] *style*{pattern}{ANSI style} +|Uses ANSI escape sequences to style the result of the enclosed pattern. +The style can consist of a comma separated list of style names from the +following table. (See Jansi link:#enable-jansi[configuration].) + +!=== +!Style Name !Description + +!Normal +!Normal display + +!Bright +!Bold + +!Dim +!Dimmed or faint characters + +!Underline +!Underlined characters + +!Blink +!Blinking characters + +!Reverse +!Reverse video + +!Hidden +! + +!Black or FG_Black +!Set foreground color to black + +!Red or FG_Red +!Set foreground color to red + +!Green or FG_Green +!Set foreground color to green + +!Yellow or FG_Yellow +!Set foreground color to yellow + +!Blue or FG_Blue +!Set foreground color to blue + +!Magenta or FG_Magenta +!Set foreground color to magenta + +!Cyan or FG_Cyan +!Set foreground color to cyan + +!White or FG_White +!Set foreground color to white + +!Default or FG_Default +!Set foreground color to default (white) + +!BG_Black +!Set background color to black + +!BG_Red +!Set background color to red + +!BG_Green +!Set background color to green + +!BG_Yellow +!Set background color to yellow + +!BG_Blue +!Set background color to blue + +!BG_Magenta +!Set background color to magenta + +!BG_Cyan +!Set background color to cyan + +!BG_White +!Set background color to white +!=== + +For example: + +.... +%style{%d{ISO8601}}{black} %style{[%t]}{blue} %style{%-5level:}{yellow} %style{%msg%n%throwable}{green} +.... + +You can also combine styles: + +.... +%d %highlight{%p} %style{%logger}{bright,cyan} %C{1.} %msg%n +.... + +You can also use `%` with a color like `%black`, `%blue`, `%cyan`, and +so on. For example: + +.... +%black{%d{ISO8601}} %blue{[%t]} %yellow{%-5level:} %green{%msg%n%throwable} +.... + +|[[PatternThreadId]] *T* + +*tid* + +*threadId* +|Outputs the ID of the thread that generated the logging event. + +|[[PatternThreadName]] *t* + +*tn* + +*thread* + +*threadName* +|Outputs the name of the thread that generated the logging event. + +|[[PatternThreadPriority]] *tp* + +*threadPriority* +|Outputs the priority of the thread that generated the logging event. + +|[[PatternLoggerFqcn]] *fqcn* +|Outputs the fully qualified class name of the logger. + +|[[EndOfBatch]] *endOfBatch* +|Outputs the EndOfBatch status of the logging event, as "true" or "false". + +|[[PatternNDC]] *x* + +*NDC* +|Outputs the Thread Context Stack (also known as the Nested +Diagnostic Context or NDC) associated with the thread that generated the +logging event. + +|[[PatternMDC]] *X*{key[,key2...]} + +*mdc*{key[,key2...]} + +*MDC*{key[,key2...]} +|Outputs the Thread Context Map (also known as the Mapped Diagnostic +Context or MDC) associated with the thread that generated the logging +event. The *X* conversion character can be followed by one or more keys +for the map placed between braces, as in *%X{clientNumber}* where +`clientNumber` is the key. The value in the MDC corresponding to the key +will be output. + +If a list of keys are provided, such as *%X{name, number}*, then each +key that is present in the ThreadContext will be output using the format +{name=val1, number=val2}. The key/value pairs will be printed in the +order they appear in the list. + +If no sub-options are specified then the entire contents of the MDC key +value pair set is output using a format {key1=val1, key2=val2}. The +key/value pairs will be printed in sorted order. + +See the +link:../log4j-api/apidocs/org/apache/logging/log4j/ThreadContext.html[ThreadContext] +class for more details. + +|[[PatternUUID]] *u*{"RANDOM" \| "TIME"} + +*uuid* +|Includes either a random or a time-based UUID. The time-based +UUID is a Type 1 UUID that can generate up to 10,000 unique ids per +millisecond, will use the MAC address of each host, and to try to insure +uniqueness across multiple JVMs and/or ClassLoaders on the same host a +random number between 0 and 16,384 will be associated with each instance +of the UUID generator Class and included in each time-based UUID +generated. Because time-based UUIDs contain the MAC address and +timestamp they should be used with care as they can cause a security +vulnerability. + +|[[PatternExtendedException]] **xEx**\|**xException**\|*xThrowable* + +  { + +    ["none" \| "short" \| "full" \| depth] + +    [,filters(package,package,...)] + +    [,separator(_separator_)] + +  } + +  {ansi( + +    Key=Value,Value,... + +    Key=Value,Value,... + +    ...) + +  } + +  {suffix(_pattern_)} + +|The same as the %throwable conversion word but also includes class +packaging information. + +At the end of each stack element of the exception, a string containing +the name of the jar file that contains the class or the directory the +class is located in and the "Implementation-Version" as found in that +jar's manifest will be added. If the information is uncertain, then the +class packaging data will be preceded by a tilde, i.e. the '~' +character. + +The throwable conversion word can be followed by an option in the form +`%xEx{short}` which will only output the first line of the Throwable or +`%xEx{n}` where the first n lines of the stack trace will be printed. +Specifying `%xEx{none}` or `%xEx{0}` will suppress printing of the +exception. + +Use `filters(packages)` where _packages_ is a list of package names to +suppress matching stack frames from stack traces. + +Use a `separator` string to separate the lines of a stack trace. For +example: `separator(\|)`. The default value is the `line.separator` +system property, which is operating system dependent. + +The `ansi` option renders stack traces with ANSI escapes code using the +JAnsi library. (See link:#enable-jansi[configuration].) Use `{ansi}` to +use the default color mapping. You can specify your own mappings with +`key=value` pairs. The keys are: + +* Prefix +* Name +* NameMessageSeparator +* Message +* At +* CauseLabel +* Text +* More +* Suppressed +* StackTraceElement.ClassName +* StackTraceElement.ClassMethodSeparator +* StackTraceElement.MethodName +* StackTraceElement.NativeMethod +* StackTraceElement.FileName +* StackTraceElement.LineNumber +* StackTraceElement.Container +* StackTraceElement.ContainerSeparator +* StackTraceElement.UnknownSource +* ExtraClassInfo.Inexact +* ExtraClassInfo.Container +* ExtraClassInfo.ContainerSeparator +* ExtraClassInfo.Location +* ExtraClassInfo.Version + +The values are names from JAnsi's +https://fusesource.github.io/jansi/documentation/api/org/fusesource/jansi/AnsiRenderer.Code.html[Code] +class like `blue`, `bg_red`, and so on (Log4j ignores case.) + +The special key `StyleMapName` can be set to one of the following +predefined maps: `Spock`, `Kirk`. + +As with %throwable, the *%xEx{suffix(_pattern_)* conversion will add +the output of _pattern_ to the output only if there is a throwable to +print. + +|[[PatternPercentLiteral]] *%* +|The sequence %% outputs a single percent sign. +|=== + +By default the relevant information is output as is. However, with the +aid of format modifiers it is possible to change the minimum field +width, the maximum field width and justification. + +The optional format modifier is placed between the percent sign and the +conversion character. + +The first optional format modifier is the _left justification flag_ +which is just the minus (-) character. Then comes the optional _minimum +field width_ modifier. This is a decimal constant that represents the +minimum number of characters to output. If the data item requires fewer +characters, it is padded on either the left or the right until the +minimum width is reached. The default is to pad on the left (right +justify) but you can specify right padding with the left justification +flag. The padding character is space. If the data item is larger than +the minimum field width, the field is expanded to accommodate the data. +The value is never truncated. To use zeros as the padding character prepend +the _minimum field width_ with a zero. + +This behavior can be changed using the _maximum field width_ modifier +which is designated by a period followed by a decimal constant. If the +data item is longer than the maximum field, then the extra characters +are removed from the _beginning_ of the data item and not from the end. +For example, it the maximum field width is eight and the data item is +ten characters long, then the first two characters of the data item are +dropped. This behavior deviates from the printf function in C where +truncation is done from the end. + +Truncation from the end is possible by appending a minus character right +after the period. In that case, if the maximum field width is eight and +the data item is ten characters long, then the last two characters of +the data item are dropped. + +Below are various format modifier examples for the category conversion +specifier. + +.Pattern Converters +|=== +|Format modifier |left justify |minimum width |maximum width |comment + +|%20c +|false +|20 +|none +|Left pad with spaces if the category name is +less than 20 characters long. + +|%-20c +|true +|20 +|none +|Right pad with spaces if the category name is +less than 20 characters long. + +|%.30c +|NA +|none +|30 +|Truncate from the beginning if the category name +is longer than 30 characters. + +|%20.30c +|false +|20 +|30 +|Left pad with spaces if the category name is +shorter than 20 characters. However, if category name is longer than 30 +characters, then truncate from the beginning. + +|%-20.30c +|true +|20 +|30 +|Right pad with spaces if the category name is +shorter than 20 characters. However, if category name is longer than 30 +characters, then truncate from the beginning. + +|%-20.-30c +|true +|20 +|30 +|Right pad with spaces if the category name is +shorter than 20 characters. However, if category name is longer than 30 +characters, then truncate from the end. +|=== + +[#enable-jansi] +=== ANSI Styling on Windows + +ANSI escape sequences are supported natively on many platforms but are +not by default on Windows. To enable ANSI support add the +http://jansi.fusesource.org/[Jansi] jar to your application and set +property `log4j.skipJansi` to `false`. This allows Log4j to use Jansi to +add ANSI escape codes when writing to the console. + +NOTE: Prior to Log4j 2.10, Jansi was enabled by default. The fact that +Jansi requires native code means that Jansi can only be loaded by a +single class loader. For web applications this means the Jansi jar has +to be in the web container's classpath. To avoid causing problems for +web applications, Log4j will no longer automatically try to load Jansi +without explicit configuration from Log4j 2.10 onward. + +=== Example Patterns + +==== Filtered Throwables + +This example shows how to filter out classes from unimportant packages +in stack traces. + +[source,xml] +---- + + org.junit,org.apache.maven,sun.reflect,java.lang.reflect + +... + +---- + +The result printed to the console will appear similar to: + +.... +Exception java.lang.IllegalArgumentException: IllegalArgument +at org.apache.logging.log4j.core.pattern.ExtendedThrowableTest.testException(ExtendedThrowableTest.java:72) [test-classes/:?] +... suppressed 26 lines +at $Proxy0.invoke(Unknown Source)} [?:?] +... suppressed 3 lines +Caused by: java.lang.NullPointerException: null pointer +at org.apache.logging.log4j.core.pattern.ExtendedThrowableTest.testException(ExtendedThrowableTest.java:71) ~[test-classes/:?] +... 30 more +.... + +==== ANSI Styled + +The log level will be highlighted according to the event's log level. +All the content that follows the level will be bright green. + +[source,xml] +---- + + %d %highlight{%p} %style{%C{1.} [%t] %m}{bold,green}%n + +---- + +[#PatternSelectors] +=== Pattern Selectors + +The PatternLayout can be configured with a PatternSelector to allow it +to choose a pattern to use based on attributes of the log event or other +factors. A PatternSelector will normally be configured with a +defaultPattern attribute, which is used when other criteria don't match, +and a set of PatternMatch elements that identify the various patterns +that can be selected. + +[#LevelPatternSelector] +==== LevelPatternSelector + +The LevelPatternSelector selects patterns based on the log level of +the log event. If the Level in the log event is equal to (ignoring case) + the name specified on the PatternMatch key attribute, then +the pattern specified on that PatternMatch element will be used. + +[source,xml] +---- + + + + + +---- + +[#MarkerPatternSelector] +==== MarkerPatternSelector + +The MarkerPatternSelector selects patterns based on the Marker included +in the log event. If the Marker in the log event is equal to or is an +ancestor of the name specified on the PatternMatch key attribute, then +the pattern specified on that PatternMatch element will be used. + +[source,xml] +---- + + + + + +---- + +[#ScriptPatternSelector] +==== ScriptPatternSelector + +The ScriptPatternSelector executes a script as descibed in the +link:../configuration.html#Scripts[Scripts] section of the Configuration +chapter. The script is passed all the properties configured in the +Properties section of the configuration, the StrSubstitutor used by the +Confguration in the "substitutor" variables, and the log event in the +"logEvent" variable, and is expected to return the value of the +PatternMatch key that should be used, or null if the default pattern +should be used. + +[source,xml] +---- + + + + + + + +---- + +[#RFC5424Layout] +== RFC5424 Layout + +As the name implies, the Rfc5424Layout formats LogEvents in accordance +with http://tools.ietf.org/html/rfc5424[RFC 5424], the enhanced Syslog +specification. Although the specification is primarily directed at +sending messages via Syslog, this format is quite useful for other +purposes since items are passed in the message as self-describing +key/value pairs. + +.Rfc5424Layout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|appName +|String +|The value to use as the APP-NAME in the RFC 5424 +syslog record. + +|charset +|String +|The character set to use when converting the syslog +String to a byte array. The String must be a valid +http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[Charset]. +If not specified, the default system Charset will be used. + +|enterpriseNumber +|integer +|The IANA enterprise number as described in +http://tools.ietf.org/html/rfc5424#section-7.2.2[RFC 5424] + +|exceptionPattern +|String +|One of the conversion specifiers from +PatternLayout that defines which ThrowablePatternConverter to use to +format exceptions. Any of the options that are valid for those +specifiers may be included. The default is to not include the Throwable +from the event, if any, in the output. + +|facility +|String +|The facility is used to try to classify the message. +The facility option must be set to one of "KERN", "USER", "MAIL", +"DAEMON", "AUTH", "SYSLOG", "LPR", "NEWS", "UUCP", "CRON", "AUTHPRIV", +"FTP", "NTP", "AUDIT", "ALERT", "CLOCK", "LOCAL0", "LOCAL1", "LOCAL2", +"LOCAL3", "LOCAL4", "LOCAL5", "LOCAL6", or "LOCAL7". These values may be +specified as upper or lower case characters. + +|format +|String +|If set to "RFC5424" the data will be formatted in +accordance with RFC 5424. Otherwise, it will be formatted as a BSD +Syslog record. Note that although BSD Syslog records are required to be +1024 bytes or shorter the SyslogLayout does not truncate them. The +RFC5424Layout also does not truncate records since the receiver must +accept records of up to 2048 bytes and may accept records that are +longer. + +|id +|String +|The default structured data id to use when formatting +according to RFC 5424. If the LogEvent contains a StructuredDataMessage +the id from the Message will be used instead of this value. + +|includeMDC +|boolean +|Indicates whether data from the ThreadContextMap +will be included in the RFC 5424 Syslog record. Defaults to true. + +|loggerFields +|List of KeyValuePairs +|Allows arbitrary PatternLayout +patterns to be included as specified ThreadContext fields; no default +specified. To use, include a nested element, containing +one or more elements. Each must have a key +attribute, which specifies the key name which will be used to identify +the field within the MDC Structured Data element, and a value attribute, +which specifies the PatternLayout pattern to use as the value. + +|mdcExcludes +|String +|A comma separated list of mdc keys that should be +excluded from the LogEvent. This is mutually exclusive with the +mdcIncludes attribute. This attribute only applies to RFC 5424 syslog +records. + +|mdcIncludes +|String +|A comma separated list of mdc keys that should be +included in the FlumeEvent. Any keys in the MDC not found in the list +will be excluded. This option is mutually exclusive with the mdcExcludes +attribute. This attribute only applies to RFC 5424 syslog records. + +|mdcRequired +|String +|A comma separated list of mdc keys that must be +present in the MDC. If a key is not present a LoggingException will be +thrown. This attribute only applies to RFC 5424 syslog records. + +|mdcPrefix +|String +|A string that should be prepended to each MDC key in +order to distinguish it from event attributes. The default string is +"mdc:". This attribute only applies to RFC 5424 syslog records. + +|mdcId +|String +|A required MDC ID. This attribute only applies to RFC 5424 syslog records. + +|messageId +|String +|The default value to be used in the MSGID field of RFC 5424 syslog records. + +|newLine +|boolean +|If true, a newline will be appended to the end of the syslog record. The default is false. + +|newLineEscape +|String +|String that should be used to replace newlines within the message text. +|=== + +[#SerializedLayout] +== Serialized Layout + +The SerializedLayout simply serializes the LogEvent into a byte array +using Java Serialization. The SerializedLayout accepts no parameters. + +This layout is deprecated since version 2.9. Java Serialization has +inherent security weaknesses, using this layout is no longer +recommended. An alternative layout containing the same information is +link:#JSONLayout[JsonLayout], configured with `properties="true"`. + +[#SyslogLayout] +== Syslog Layout + +The SyslogLayout formats the LogEvent as BSD Syslog records matching the +same format used by Log4j 1.2. + +.SyslogLayout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|charset +|String +|The character set to use when converting the syslog +String to a byte array. The String must be a valid +http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[Charset]. +If not specified, this layout uses UTF-8. + +|facility +|String +|The facility is used to try to classify the message. +The facility option must be set to one of "KERN", "USER", "MAIL", +"DAEMON", "AUTH", "SYSLOG", "LPR", "NEWS", "UUCP", "CRON", "AUTHPRIV", +"FTP", "NTP", "AUDIT", "ALERT", "CLOCK", "LOCAL0", "LOCAL1", "LOCAL2", +"LOCAL3", "LOCAL4", "LOCAL5", "LOCAL6", or "LOCAL7". These values may be +specified as upper or lower case characters. + +|newLine +|boolean +|If true, a newline will be appended to the end of the +syslog record. The default is false. + +|newLineEscape +|String +|String that should be used to replace newlines +within the message text. +|=== + +[#XMLLayout] +== XML Layout + +Appends a series of `Event` elements as defined in the log4j.dtd. + +=== Complete well-formed XML vs. fragment XML + +If you configure `complete="true"`, the appender outputs a well-formed +XML document where the default namespace is the Log4j namespace +`"http://logging.apache.org/log4j/2.0/events"`. By default, with +`complete="false"`, you should include the output as an _external +entity_ in a separate file to form a well-formed XML document, in which +case the appender uses `namespacePrefix` with a default of `"log4j"`. + +A well-formed XML document follows this pattern: + +[source,xml] +---- + + + + + + + + + + + + Hello, world! + + + + + + one + two + + + + + + + + +---- + +If `complete="false"`, the appender does not write the XML processing +instruction and the root element. + +=== Marker + +Markers are represented by a `Marker` element within the `Event` +element. The `Marker` element appears only when a marker is used in the +log message. The name of the marker's parent will be provided in the +`parent` attribute of the `Marker` element. + +=== Pretty vs. compact XML + +By default, the XML layout is not compact (a.k.a. not "pretty") with +`compact="false"`, which means the appender uses end-of-line characters +and indents lines to format the XML. If `compact="true"`, then no +end-of-line or indentation is used. Message content may contain, of +course, end-of-lines. + +.XmlLayout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|charset +|String +|The character set to use when converting to a byte +array. The value must be a valid +https://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[Charset]. +If not specified, UTF-8 will be used. + +|compact +|boolean +|If true, the appender does not use end-of-lines and +indentation. Defaults to false. + +|complete +|boolean +|If true, the appender includes the XML header and +footer. Defaults to false. + +|properties +|boolean +|If true, the appender includes the thread context +map in the generated XML. Defaults to false. + +|locationInfo +|boolean +a|If true, the appender includes the location information in the generated +XML. Defaults to false. + +Generating link:#LocationInformation[location information] is an +expensive operation and may impact performance. Use with caution. + +|includeStacktrace +|boolean +|If true, include full stacktrace of any logged +https://docs.oracle.com/javase/6/docs/api/java/lang/Throwable.html[Throwable] +(optional, default to true). + +|stacktraceAsString +|boolean +|Whether to format the stacktrace as a +string, and not a nested object (optional, defaults to false). + +|includeNullDelimiter +|boolean +|Whether to include NULL byte as +delimiter after each event (optional, default to false). +|=== + +To include any custom field in the output, use following syntax: + +[source,xml] +---- + + + + +---- + +Custom fields are always last, in the order they are declared. The +values support link:lookups.html[lookups]. + +Additional link:../runtime-dependencies.html[runtime dependencies] are +required for using XmlLayout. + +[#YamlLayout] +== YAML Layout + +Appends a series of YAML events as strings serialized as bytes. + +A YAML log event follows this pattern: + +[source,yaml] +---- +--- +instant: + epochSecond: 1493121664 + nanoOfSecond: 118000000 +thread: "main" +level: "INFO" +loggerName: "HelloWorld" +marker: + name: "child" + parents: + - name: "parent" + parents: + - name: "grandparent" +message: "Hello, world!" +thrown: + commonElementCount: 0 + message: "error message" + name: "java.lang.RuntimeException" + extendedStackTrace: + - class: "logtest.Main" + method: "main" + file: "Main.java" + line: 29 + exact: true + location: "classes/" + version: "?" +contextStack: +- "one" +- "two" +endOfBatch: false +loggerFqcn: "org.apache.logging.log4j.spi.AbstractLogger" +contextMap: + bar: "BAR" + foo: "FOO" +threadId: 1 +threadPriority: 5 +source: + class: "logtest.Main" + method: "main" + file: "Main.java" + line: 29 +---- + +.YamlLayout Parameters +[cols="1m,1,4"] +|=== +|Parameter Name |Type |Description + +|charset +|String +|The character set to use when converting to a byte +array. The value must be a valid +https://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html[Charset]. +If not specified, UTF-8 will be used. + +|properties +|boolean +|If true, the appender includes the thread context +map in the generated YAML. Defaults to false. + +|locationInfo +|boolean +a| +If true, the appender includes the location information in the generated +YAML. Defaults to false. + +Generating link:#LocationInformation[location information] is an +expensive operation and may impact performance. Use with caution. + +|includeStacktrace +|boolean +|If true, include full stacktrace of any +logged +https://docs.oracle.com/javase/6/docs/api/java/lang/Throwable.html[Throwable] +(optional, default to true). + +|stacktraceAsString +|boolean +|Whether to format the stacktrace as a +string, and not a nested object (optional, defaults to false). + +|includeNullDelimiter +|boolean +|Whether to include NULL byte as +delimiter after each event (optional, default to false). +|=== + +To include any custom field in the output, use following syntax: + +[source,xml] +---- + + + + +---- + +Custom fields are always last, in the order they are declared. The +values support link:lookups.html[lookups]. + +Additional link:../runtime-dependencies.html[runtime dependencies] are +required for using YamlLayout. + +[#LocationInformation] +== Location Information + +If one of the layouts is configured with a location-related attribute +like HTML link:#HtmlLocationInfo[locationInfo], or one of the patterns +link:#PatternClass[%C or %class], link:#PatternFile[%F or %file], +link:#PatternLocation[%l or %location], link:#PatternLine[%L or %line], +link:#PatternMethod[%M or %method], Log4j will take a snapshot of the +stack, and walk the stack trace to find the location information. + +This is an expensive operation: 1.3 - 5 times slower for synchronous +loggers. Synchronous loggers wait as long as possible before they take +this stack snapshot. If no location is required, the snapshot will never +be taken. + +However, asynchronous loggers need to make this decision before passing +the log message to another thread; the location information will be lost +after that point. The +link:../performance.html#asyncLoggingWithLocation[performance impact] of +taking a stack trace snapshot is even higher for asynchronous loggers: +logging with location is 30-100 times slower than without location. For +this reason, asynchronous loggers and asynchronous appenders do not +include location information by default. + +You can override the default behaviour in your logger or asynchronous +appender configuration by specifying `includeLocation="true"`. diff --git a/src/site/asciidoc/manual/logbuilder.adoc b/src/site/asciidoc/manual/logbuilder.adoc new file mode 100644 index 00000000000..a03687e88aa --- /dev/null +++ b/src/site/asciidoc/manual/logbuilder.adoc @@ -0,0 +1,79 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Log4j 2 API + +== Log Builder + +Log4j has traditionally been used with logging statements like +[source,java] +---- +logger.error("Unable to process request due to {}", code, exception); +---- + +This has resulted in some confusion as to whether the exception should be a parameter to the message or +if Log4j should handle it as a throwable. In order to make logging clearer a builder pattern has been +added to the API. Using the builder syntax the above would be handled as: +[source,java] +---- +logger.atError().withThrowable(exception).log("Unable to process request due to {}", code); +---- + +With this syntax it is clear that the exception is to be treated as a Throwable by Log4j. + +The Logger class now returns a LogBuilder when any of the atTrace, atDebug, atInfo, atWarn, atError, +atFatal, always, or atLevel(Level) methods are called. The logBuilder then allows a Marker, Throwable, +and/or location to be added to the event before it is logged. A call to the log method always causes the +log event to be finalized and sent. + +A logging statement with a Marker, Throwable, and location would look like: +[source,java] +---- +logger.atInfo().withMarker(marker).withLocation().withThrowable(exception).log("Login for user {} failed", userId); +---- +Providing the location method on the LogBuilder provides two distinct advantages: + +1. Logging wrappers can use it to provide the location information to be used by Log4j. +2. The overhead of capturing location information when using the location method with no +parameters is much better than having to calculate the location information when it is needed. Log4j +can simply ask for the stack trace entry at a fixed index instead of having to walk the stack trace +to determine the calling class. Of course, if the location information will not be used by the layout +this will result in slower performance. + +=== Location Performance + +The table below shows some of the results from the FileAppenderBenchmark and FileAppenderWithLocationBenchmark +classes in the log4j-perf project when configured to use 4 threads. The results show that lazily including +the location information is about 8 times slower than not including location information. While using the +withLocation method of LogBuilder is about 3 times faster than lazily calculating the location information +it is still about 2.5 times slower than not including location information. + +The tests were run on a 2018 MacBook Pro with a 2.9 GHz Intel Core i9 processor with 6 cores, 32 GB of memory +and 1 TB of SSD storage on Java 11 using Log4j 2.13.0 and Logback 1.2.3. +image:../images/LocationPerf.png[Location Performance] + +|=== +|Test|Print Location Info|No Location Info Printed + +|Log4j2 File| 191,509.724 ± 11339.978 ops/s| 1,407,329.130 ± 22595.997 ops/s +|Log4j2 Log Builder withLocation()|469,200.684 ± 50025.985 ops/s|577,127.463 ± 11464.342 ops/s +|Logback File|159,116.538 ± 1884.969 ops/s|1,240,438.384 ± 76619.873 ops/s +|=== +As expected, when using LogBuilder with a call to the withLocation() method logging is much faster when +location information is used in the output but significantly slower when it is not. + +Note: Running the tests at various times provides varying results. Although some results have been as much +as 10% higher all results are generally affected similarly so the comparisons between them stay the same. \ No newline at end of file diff --git a/src/site/asciidoc/manual/logsep.adoc b/src/site/asciidoc/manual/logsep.adoc new file mode 100644 index 00000000000..99292978667 --- /dev/null +++ b/src/site/asciidoc/manual/logsep.adoc @@ -0,0 +1,125 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Logging Separation +Ralph Goers + +There are many well known use cases where applications may share an +environment with other applications and each has a need to have its own, +separate logging environment. This purpose of this section is to discuss +some of these cases and ways to accomplish this. + +[#UseCases] +== Use Cases + +This section describes some of the use cases where Log4j could be used +and what its desired behavior might be. + +=== Standalone Application + +Standalone applications are usually relatively simple. They typically +have one bundled executable that requires only a single logging +configuration. + +=== Web Applications + +A typical web application will be packaged as a WAR file and will +include all of its dependencies in WEB-INF/lib and will have its +configuration file located in the class path or in a location configured +in the web.xml. Be sure to follow the link:webapp.html[instructions to +initialize Log4j 2 in a web application]. + +=== Java EE Applications + +A Java EE application will consist of one or more WAR files and possible +some EJBs, typically all packaged in an EAR file. Usually, it is +desirable to have a single configuration that applies to all the +components in the EAR. The logging classes will generally be placed in a +location shared across all the components and the configuration needs to +also be shareable. Be sure to follow the link:webapp.html[instructions +to initialize Log4j 2 in a web application]. + +=== "Shared" Web Applications and REST Service Containers + +In this scenario there are multiple WAR files deployed into a single +container. Each of the applications should use the same logging +configuration and share the same logging implementation across each of +the web applications. When writing to files and streams each of the +applications should share them to avoid the issues that can occur when +multiple components try to write to the same file(s) through different +File objects, channels, etc. + +=== OSGi Applications + +An OSGi container physically separates each JAR into its own +ClassLoader, thus enforcing modularity of JARs as well as providing +standardized ways for JARs to share code based on version numbers. +Suffice to say, the OSGi framework is beyond the scope of this manual. +There are some differences when using Log4j in an OSGi container. By +default, each JAR bundle is scanned for its own Log4j configuration +file. Similar to the web application paradigm, every bundle has its own +LoggerContext. As this may be undesirable when a global Log4j +configuration is wanted, then the +link:extending.html#ContextSelector[ContextSelector] should be +overridden with `BasicContextSelector` or `JndiContextSelector`. + +[#Approaches] +== Approaches + +=== The Simple Approach + +The simplest approach for separating logging within applications is to +package each application with its own copy of Log4j and to use the +BasicContextSelector. While this works for standalone applications and +may work for web applications and possibly Java EE applications, it does +not work at all in the last case. However, when this approach does work +it should be used as it is ultimately the simplest and most +straightforward way of implementing logging. + +=== Using Context Selectors + +There are a few patterns for achieving the desired state of logging +separation using ContextSelectors: + +1. Place the logging jars in the container's classpath and set the +system property `log4j2.contextSelector` to +`org.apache.logging.log4j.core.selector.BasicContextSelector`. This will +create a single LoggerContext using a single configuration that will be +shared across all applications. +2. Place the logging jars in the container's classpath and use the +default ClassLoaderContextSelector. Follow the +link:webapp.html[instructions to initialize Log4j 2 in a web +application]. Each application can be configured to share the same +configuration used at the container or can be individually configured. +If status logging is set to debug in the configuration there will be +output from when logging is initialized in the container and then again +in each web application. +3. Follow the link:webapp.html[instructions to initialize Log4j 2 in a +web application] and set the system property or servlet context +parameter `log4j2.contextSelector` to +org.apache.logging.log4j.core.selector.JndiContextSelector. This will +cause the container to use JNDI to locate each web application's +`LoggerContext`. Be sure to set the `isLog4jContextSelectorNamed` +context parameter to true and also set the `log4jContextName` and +`log4jConfiguration` context parameters. + +The `JndiContextSelector` will not work unless `log4j2.enableJndi=true` is set as a system property +or environment variable. See the +link:./configuration.html#enableJndi[log4j2.enableJndi] system property. + +The exact method for setting system properties depends on the container. +For Tomcat, edit `$CATALINA_HOME/conf/catalina.properties`. Consult the +documentation for other web containers. diff --git a/src/site/asciidoc/manual/lookups.adoc b/src/site/asciidoc/manual/lookups.adoc new file mode 100644 index 00000000000..82c76911135 --- /dev/null +++ b/src/site/asciidoc/manual/lookups.adoc @@ -0,0 +1,801 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Lookups +Ralph Goers + +Lookups provide a way to add values to the Log4j configuration at +arbitrary places. They are a particular type of Plugin that implements +the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/lookup/StrLookup.html[`StrLookup`] +interface. Information on how to use Lookups in configuration files can +be found in the link:configuration.html#PropertySubstitution[Property +Substitution] section of the link:configuration.html[Configuration] +page. + +[#ContextMapLookup] +== Context Map Lookup + +The ContextMapLookup allows applications to store data in the Log4j +ThreadContext Map and then retrieve the values in the Log4j +configuration. In the example below, the application would store the +current user's login id in the ThreadContext Map with the key "loginId". +During initial configuration processing the first '$' will be removed. +The PatternLayout supports interpolation with Lookups and will then +resolve the variable for each event. Note that the pattern +"%X\{loginId}" would achieve the same result. + +[source,xml] +---- + + + %d %p %c{1.} [%t] $${ctx:loginId} %m%n + + +---- + +[#DateLookup] +== Date Lookup + +The DateLookup is somewhat unusual from the other lookups as it doesn't +use the key to locate an item. Instead, the key can be used to specify a +date format string that is valid for +http://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html[SimpleDateFormat]. +The current date, or the date associated with the current log event will +be formatted as specified. + +[source,xml] +---- + + + %d %p %c{1.} [%t] %m%n + + + +---- + +[#DockerLookup] +== Docker Lookup + +The DockerLookup can be used to lookup attributes from the Docker container the application is running in. + +Log4j Docker provides access to the following container attributes: +[cols="1m,4a"] +|=== +|Key |Description + +|containerId +|The full id assigned to the container. + +|containerName +|The name assigned to the container. + +|imageId +|The id assigned to the image. + +|imageName +|The name assigned to the image. + +|shortContainerId +|The first 12 characters of the container id. + +|shortImageId +|The first 12 characters of the image id. +|=== + +---- + + + + + +---- + +This Lookup is subject to the requirements listed at link:../log4j-docker/index.html[Log4j Docker Support] + +[#EnvironmentLookup] +== Environment Lookup + +The EnvironmentLookup allows systems to configure environment variables, +either in global files such as /etc/profile or in the startup scripts +for applications, and then retrieve those variables from within the +logging configuration. The example below includes the name of the +currently logged in user in the application log. + +[source,xml] +---- + + + %d %p %c{1.} [%t] $${env:USER} %m%n + + +---- + +This lookup also supports default value syntax. In the sample below, +when the `USER` environment variable is undefined, the default value +`jdoe` is used: + +[source,xml] +---- + + + %d %p %c{1.} [%t] $${env:USER:-jdoe} %m%n + + +---- +[#EventLookup] +== Event Lookup + +The EventLookup provides access to fields within the log event from the configuration. + +[cols="1m,4a"] +|=== +|Key |Description + +|Exception +|Returns the simple class name of the Exception, if one is included in the event. + +|Level +|Returns the logging Level of the event. + +|Logger +|Returns the name of the Logger. + +|Marker +|Returns the name of the Marker associated with the log event, if one is present. + +|Message +|Returns the formatted Message string. + +|ThreadId +|Returns the thread id associated with the log event. + +|ThreadName +|Returns the name of the thread associate with the log event. + +|Timestamp +|Returns the time in milliseconds when the event occurred. + +|=== + +In this example the RoutingAppender picks a route based on the presence of a Marker named "AUDIT" being +present in the log event. +[source,prettyprint,linenums] +---- + + + + + + + + + + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + + + + + + + + + + + +---- + +[#JavaLookup] +== Java Lookup + +The JavaLookup allows Java environment information to be retrieved in +convenient preformatted strings using the `java:` prefix. + +[cols="1m,4a"] +|=== +|Key |Description + +|version +|The short Java version, like: + +`Java version 1.7.0_67` + +|runtime +|The Java runtime version, like: + +`Java(TM) SE Runtime Environment (build 1.7.0_67-b01) from Oracle Corporation` + +|vm +|The Java VM version, like: + +`Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)` + +|os +|The OS version, like: + +`Windows 7 6.1 Service Pack 1, architecture: amd64-64` + +|locale +|Hardware information, like: + +`default locale: en_US, platform encoding: Cp1252` + +|hw +|System locale and file encoding information, like: + +`processors: 4, architecture: amd64-64, instruction sets: amd64` + +|=== + +For example: + +[source,xml] +---- + + + %d %m%n + + +---- + +[#JndiLookup] +== JNDI Lookup + +As of Log4j 2.15.1 JNDI operations require that `log4j2.enableJndi=true` be set as a system property or the +corresponding environment variable for this lookup to function. See the +link:./configuration.html#enableJndi[log4j2.enableJndi] system property. + +The JndiLookup allows variables to be retrieved via JNDI. By default the +key will be prefixed with java:comp/env/, however if the key contains a +":" no prefix will be added. + +By default the JNDI Lookup only supports the java, ldap, and ldaps protocols or no protocol. Additional +protocols may be supported by specifying them on the ``log4j2.allowedJndiProtocols`` property. +When using LDAP Java classes that implement the Referenceable interface are not supported for security +reasons. Only the Java primative classes are supported by default as well as any classes specified by the +``log4j2.allowedLdapClasses`` property. When using LDAP only references to the local host name +or ip address are supported along with any hosts or ip addresses listed in the +``log4j2.allowedLdapHosts`` property. + +[source,xml] +---- + + + %d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n + + +---- + +*Java's JNDI module is not available on Android.* + +[#JmxRuntimeInputArgumentsLookup] +== JVM Input Arguments Lookup (JMX) + +Maps JVM input arguments -- but not _main_ arguments -- using JMX to +acquire the JVM arguments. + +Use the prefix `jvmrunargs` to access JVM arguments. + +See the Javadocs for +https://docs.oracle.com/javase/8/docs/api/java/lang/management/RuntimeMXBean.html#getInputArguments--[java.lang.management.RuntimeMXBean.getInputArguments()]. + +*Java's JMX module is not available on Android or on Google App Engine.* + +[#KubernetesLookup] +== Kubernetes Lookup + +The KubernetesLookup can be used to lookup attributes from the Kubernetes environment for the container +the application is running in. + +Log4j Kubernetes provides access to the following container attributes: +[cols="1m,4a"] +|=== +|Attribute |Description +|accountName|The service account name +|clusterName|The name of the cluster the application is deployed in +|containerId|>The full id assigned to the container +|containerName|The name assigned to the container +|host|The name assigned to the host operating system +|hostIp|The host's ip address +|imageId|The id assigned to the container image +|imageName|The name assigned to the container image +|labels|All labels formatted in a list +|labels.app|The application name +|labels.podTemplateHash|The pod's template hash value +|masterUrl|The URL used to access the API server +|namespaceId|The id of the namespace the various kubernetes components are located within +|namespaceName|The namespace the various kubernetes components are located within +|podId|The pod's ip number +|podIp|The pod's ip address +|podName|The name of the pod + + + requestId,sessionId,loginId,userId,ipAddress,callingHost + %d [%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress} %C{1.}.%M:%L - %m%n + + + + + + + + + + + + + + + + + ]]> + +This Lookup is subject to the configuration requirements listed at link:../log4j-kubernetes/index.html[Log4j Kubernetes Support] + +[#Log4jConfigLookup] +== Log4j Configuration Location Lookup + +Log4j configuration properties. The expressions +`${log4j:configLocation}` and `${log4j:configParentLocation}` +respectively provide the absolute path to the log4j configuration file +and its parent folder. + +The example below uses this lookup to place log files in a directory +relative to the log4j configuration file. + +[source,xml] +---- + + + %d %p %c{1.} [%t] %m%n + + +---- + +[#LowerLookup] +== Lower Lookup + +The LowerLookup converts the passed in argument to lower case. Presumably the value will be the +result of a nested lookup. + +[source,xml] +---- + + + %d %p %c{1.} [%t] $${lower:${spring:spring.application.name}} %m%n + + +---- + +[#AppMainArgsLookup] +== Main Arguments Lookup (Application) + +This lookup requires that you manually provide the main arguments of the +application to Log4j: + +[source,java] +---- +import org.apache.logging.log4j.core.lookup.MainMapLookup; + +public static void main(String args[]) { + MainMapLookup.setMainArguments(args); + ... +} +---- + +If the main arguments have been set, this lookup allows applications to +retrieve these main argument values from within the logging +configuration. The key that follows the `main:` prefix can either be a +0-based index into the argument list, or a string, where +`${main:myString}` is substituted with the value that follows `myString` +in the main argument list. + +Note: Many applications use leading dashes to identify command arguments. Specifying +`${main:--file}` would result in the lookup failing because it would look for a variable +named "main" with a default value of "-file". To avoid this the ":" separating the Lookup name from the +key must be followed by a backslash as an escape character as in `${main:\--file}`. + +For example, suppose the static void main String[] arguments are: + +.... +--file foo.txt --verbose -x bar +.... + +Then the following substitutions are possible: + +[cols="m,m"] +|=== +|Expression |Result + +|${main:0} +|--file + +|${main:1} +|foo.txt + +|${main:2} +|--verbose + +|${main:3} +|-x + +|${main:4} +|bar + +|${main:\--file} +|foo.txt + +|${main:\-x} +|bar + +|${main:bar} +|null +|=== + +|${main:\--quiet:-true} +|true +|=== + +Example usage: + +[source,xml] +---- + + + %d %m%n + + +---- + +[#MapLookup] +== Map Lookup + +The MapLookup serves several purposes. + +1. Provide the base for Properties declared in the configuration file. +2. Retrieve values from MapMessages in LogEvents. +3. Retrieve values set with +link:../log4j-core/apidocs/org/apache/logging/log4j/core/lookup/MapLookup.html#setMainArguments%28java.lang.String%5B%5D%29[MapLookup.setMainArguments(String[])] + +The first item simply means that the MapLookup is used to substitute +properties that are defined in the configuration file. These variables +are specified without a prefix - e.g. `${name}`. The second usage allows +a value from the current +link:../log4j-api/apidocs/org/apache/logging/log4j/message/MapMessage.html[`MapMessage`], +if one is part of the current log event, to be substituted. In the +example below the RoutingAppender will use a different +RollingFileAppender for each unique value of the key named "type" in the +MapMessage. Note that when used this way a value for "type" should be +declared in the properties declaration to provide a default value in +case the message is not a MapMessage or the MapMessage does not contain +the key. See the link:configuration.html#PropertySubstitution[Property +Substitution] section of the link:configuration.html[Configuration] +page for information on how to set the default values. + +[source,xml] +---- + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + +---- + +The marker lookup allows you to use markers in interesting +configurations like a routing appender. Consider the following YAML +configuration and code that logs to different files based on markers: + +[source,yaml] +---- +Configuration: + status: debug + + Appenders: + Console: + RandomAccessFile: + - name: SQL_APPENDER + fileName: logs/sql.log + PatternLayout: + Pattern: "%d{ISO8601_BASIC} %-5level %logger{1} %X %msg%n" + - name: PAYLOAD_APPENDER + fileName: logs/payload.log + PatternLayout: + Pattern: "%d{ISO8601_BASIC} %-5level %logger{1} %X %msg%n" + - name: PERFORMANCE_APPENDER + fileName: logs/performance.log + PatternLayout: + Pattern: "%d{ISO8601_BASIC} %-5level %logger{1} %X %msg%n" + + Routing: + name: ROUTING_APPENDER + Routes: + pattern: "$${marker:}" + Route: + - key: PERFORMANCE + ref: PERFORMANCE_APPENDER + - key: PAYLOAD + ref: PAYLOAD_APPENDER + - key: SQL + ref: SQL_APPENDER + + Loggers: + Root: + level: trace + AppenderRef: + - ref: ROUTING_APPENDER +---- + +[source,java] +---- +public static final Marker SQL = MarkerFactory.getMarker("SQL"); +public static final Marker PAYLOAD = MarkerFactory.getMarker("PAYLOAD"); +public static final Marker PERFORMANCE = MarkerFactory.getMarker("PERFORMANCE"); + +final Logger logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + +logger.info(SQL, "Message in Sql.log"); +logger.info(PAYLOAD, "Message in Payload.log"); +logger.info(PERFORMANCE, "Message in Performance.log"); +---- + +Note the key part of the configuration is `pattern: "$${marker:}"`. This +will produce three log files, each with a log event for a specific +marker. Log4j will route the log event with the `SQL` marker to +`sql.log`, the log event with the `PAYLOAD` marker to `payload.log`, and +so on. + +You can use the notation `"${marker:name}"` and `"$${marker:name}"` to +check for the existence of a marker where `name` is the marker name. If +the marker exists, the expression returns the name, otherwise `null`. + +[#StructuredDataLookup] +== Structured Data Lookup + +The StructuredDataLookup is very similar to the MapLookup in that it +will retrieve values from StructuredDataMessages. In addition to the Map +values it will also return the name portion of the id (not including the +enterprise number) and the type field. The main difference between the +example below and the example for MapMessage is that the "type" is an +attribute of the +link:../log4j-api/apidocs/org/apache/logging/log4j/message/StructuredDataMessage.html[StructuredDataMessage] +while "type" would have to be an item in the Map in a MapMessage. + +[source,xml] +---- + + + + + + %d %p %c{1.} [%t] %m%n + + + + + + +---- + +[#SpringLookup] +== Spring Boot Lookup + + +The Spring Boot Lookup retrieves the values of Spring properties from the Spring configuration as well as +values of the active and default profiles. Specifying a key of `profiles.active` will reutrn the active +profiles while a key of `profiles.default` will return the default profiles. The default and active +profiles can be an array. If more than one profile is present they will be returned as a comma separated +list. To retrieve a single item from the array append `[{index}]` to the key. For example, to return the +first active profile in the list specify `profiles.active[0]`. + +This Lookup will return null values until Spring Boot initializes application logging. The Spring Boot +Lookup requires the `log4j-spring-boot` jar be included as a dependency. + + +[source,xml] +---- + + + %d %p %c{1.} [%t] $${spring:spring.application.name} %m%n + + +---- + +This Lookup requires log4j-spring-cloud-config-client be included in the application. + +[#SystemPropertiesLookup] +== System Properties Lookup + +As it is quite common to define values inside and outside the +application by using System Properties, it is only natural that they +should be accessible via a Lookup. As system properties are often +defined outside the application it would be quite common to see +something like: + +[source,xml] +---- + + + +---- + +This lookup also supports default value syntax. In the sample below, +when the `logPath` system property is undefined, the default value +`/var/logs` is used: + +[source,xml] +---- + + + +---- + +[#UpperLookup] +== Upper Lookup + +The LowerLookup converts the passed in argument to upper case. Presumably the value will be the +result of a nested lookup. + +[source,xml] +---- + + + %d %p %c{1.} [%t] $${upper:{${spring:spring.application.name}} %m%n + + +---- + +[#WebLookup] +== Web Lookup + +The WebLookup allows applications to retrieve variables that are +associated with the ServletContext. In addition to being able to +retrieve various fields in the ServletContext, WebLookup supports +looking up values stored as attributes or configured as initialization +parameters. The following table lists various keys that can be +retrieved: + +[cols="1m,4"] +|=== +|Key |Description + +|attr._name_ +|Returns the ServletContext attribute with the specified name + +|request.attr._name_ +|Returns the ServletRequest attribute with the specified name - requires Log4jServletFilter + +|header._name_ +|Returns the HttpServletRequest header with the specified name - requires Log4jServletFilter + +|cookie._name_ +|Returns the HttpServletRequest cookie with the specified name - requires Log4jServletFilter + +|header._name_ +|Returns the HttpServletRequest header with the specified name - requires Log4jServletFilter + +|request._method_ +|Returns the HttpServletRequest method - requires Log4jServletFilter + +|request._uri_ +|Returns the HttpServletRequest URI - requires Log4jServletFilter + +|request._url_ +|Returns the HttpServletRequest URL - requires Log4jServletFilter + +|request._remoteAddress_ +|Returns the HttpServletRequest remote address - requires Log4jServletFilter + +|request._remoteHost_ +|Returns the HttpServletRequest remote host - requires Log4jServletFilter + +|request.parameter._name_ +|Returns the HttpServletRequest parameter - requires Log4jServletFilter + +|request.principal +|Returns the HttpServletRequest principal name - requires Log4jServletFilter + +|session.id +|Returns the HttpSession id or null if none is started - requires Log4jServletFilter + +|session.attr._name_ +|Returns the HttpSession attribute value (using _toString()_ if not null) or null if absent - requires Log4jServletFilter + +|contextPath +|The context path of the web application + +|contextPathName +|The first token in the context path of the web application splitting on "/" characters. + +|effectiveMajorVersion +|Gets the major version of the Servlet specification that the application +represented by this ServletContext is based on. + +|effectiveMinorVersion +|Gets the minor version of the Servlet specification that the application +represented by this ServletContext is based on. + +|initParam._name_ +|Returns the ServletContext initialization parameter with the specified name + +|majorVersion +|Returns the major version of the Servlet API that this servlet container supports. + +|minorVersion +|Returns the minor version of the Servlet API that this servlet container supports. + +|rootDir +|Returns the result of calling getRealPath with a value of "/". + +|serverInfo +|Returns the name and version of the servlet container on which the servlet is running. + +|servletContextName +|Returns the name of the web application as defined in the display-name element of the deployment descriptor +|=== + +Any other key names specified will first be checked to see if a +ServletContext attribute exists with that name and then will be checked +to see if an initialization parameter of that name exists. If the key is +located then the corresponding value will be returned. + +[source,xml] +---- + + + +---- + +=== Request lookups and asynchronous calls + +Servlet 3.0 supports asynchronous calls, by default the request tracking - and therefore request related lookups, +will not work. To make it work you can extract the servlet context attribute `log4j.requestExecutor` which +is a `BiConsumer` and call it passing the correct request and task to execute +synchronously. During this task execution the lookups will be set up properly: + +[source,java] +---- +@GET // example using JAX-RS asynchronous feature backed by servlet AsyncContext +public void get(@Suspended AsyncResponse response, + @Context ServletContext context, + @Context ServletRequest request) { + final BiConsumer log4jWrapper = + (BiConsumer) context.getAttribute("log4j.requestExecutor"); + myThreadPool.submit(() -> log4jWrapper.accept(request, () -> response.resume(doInternalGet())); +} +---- diff --git a/src/site/asciidoc/manual/markers.adoc b/src/site/asciidoc/manual/markers.adoc new file mode 100644 index 00000000000..c705f7163a8 --- /dev/null +++ b/src/site/asciidoc/manual/markers.adoc @@ -0,0 +1,104 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Log4j 2 API +Ralph Goers + +[#Markers] +== Markers + +One of the primary purpose of a logging framework is to provide the +means to generate debugging and diagnostic information only when it is +needed, and to allow filtering of that information so that it does not +overwhelm the system or the individuals who need to make use of it. As +an example, an application desires to log its entry, exit and other +operations separately from SQL statements being executed, and wishes to +be able to log queries separate from updates. One way to accomplish this +is shown below: + +[source,java] +---- +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.MarkerManager; +import java.util.Map; + +public class MyApp { + + private Logger logger = LogManager.getLogger(MyApp.class.getName()); + private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL"); + private static final Marker UPDATE_MARKER = MarkerManager.getMarker("SQL_UPDATE").setParents(SQL_MARKER); + private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY").setParents(SQL_MARKER); + + public String doQuery(String table) { + logger.traceEntry(); + + logger.debug(QUERY_MARKER, "SELECT * FROM {}", table); + + String result = ... + + return logger.traceExit(result); + } + + public String doUpdate(String table, Map params) { + logger.traceEntry(); + + if (logger.isDebugEnabled()) { + logger.debug(UPDATE_MARKER, "UPDATE {} SET {}", table, formatCols()); + } + + String result = ... + + return logger.traceExit(result); + } + + private String formatCols(Map cols) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry entry : cols.entrySet()) { + if (!first) { + sb.append(", "); + } + sb.append(entry.getKey()).append("=").append(entry.getValue()); + first = false; + } + return sb.toString(); + } +} +---- + +In the example above it is now possible to add MarkerFilters to only +allow SQL update operations to be logged, all SQL updates to be logged +or to log everything in MyApp. + +Some important rules about Markers must be considered when using them. + +1. Markers must be unique. They are permanently registered by name so +care should be taken to insure that Markers used in your application are +distinct from those in the application's dependencies, unless that is +what is desired. +2. Parent Markers can be added or removed dynamically. However, this is +fairly expensive to do. Instead, it is recommended that the parents be +identified when obtaining the Marker the first time as shown in the +examples above. Specifically, the set method replaces all the markers in +a single operation while add and remove act on only a single Marker at a +time. +3. Evaluating Markers with multiple ancestors is much more expensive +than Markers with no parents. For example, in one set of tests to +evaluate whether a Marker matched its grandparent took 3 times longer +than evaluating the Marker itself. Even then though, evaluating Markers +is inexpensive compared to resolving the callers class name or line +number. diff --git a/src/site/asciidoc/manual/messages.adoc b/src/site/asciidoc/manual/messages.adoc new file mode 100644 index 00000000000..35e297c4cb4 --- /dev/null +++ b/src/site/asciidoc/manual/messages.adoc @@ -0,0 +1,333 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Log4j 2 API +Ralph Goers + +[#Messages] +== Messages + +Although Log4j 2 provides Logger methods that accept Strings and +Objects, all of these are ultimately captured in Message objects that +are then associated with the log event. Applications are free to +construct Messages of their own and pass them to the Logger. Although it +may seem more expensive than passing the message format and parameters +directly to the event, testing has shown that with modern JVMs the cost +of creating and destroying events is minor, especially when complex +tasks are encapsulated in the Message instead of the application. In +addition, when using the methods that accept Strings and parameters, the +underlying Message object will only be created if any configured global +filters or the Logger's log level allow the message to be processed. + +Consider an application that has a Map object containing \{"Name" = +"John Doe", "Address" = "123 Main St.", "Phone" = "(999) 555-1212"} and +a User object that has a getId method that returns "jdoe". The developer +would like to add an informational message that returns "User John Doe +has logged in using id jdoe". The way this could be accomplished is by +doing: + +[source,java] +---- +logger.info("User {} has logged in using id {}", map.get("Name"), user.getId()); +---- + +While there is nothing inherently wrong with this, as the complexity of +the objects and desired output increases this technique becomes harder +to use. As an alternative, using Messages allows: + +[source,java] +---- +logger.info(new LoggedInMessage(map, user)); +---- + +In this alternative the formatting is delegated to the `LoggedInMessage` +object's `getFormattedMessage` method. Although in this alternative a new +object is created, none of the methods on the objects passed to the +`LoggedInMessage` are invoked until the `LoggedInMessage` is formatted. This +is especially useful when an `Object`'s `toString` method does not produce +the information you would like to appear in the log. + +Another advantage to Messages is that they simplify writing Layouts. In +other logging frameworks the Layout must loop through the parameters +individually and determine what to do based on what objects are +encountered. With Messages the Layout has the option of delegating the +formatting to the Message or performing its formatting based on the type +of Message encountered. + +Borrowing from the earlier example illustrating Markers to identify SQL +statements being logged, Messages can also be leveraged. First, the +Message is defined. + +[source,java] +---- +public class SQLMessage implements Message { + public enum SQLType { + UPDATE, + QUERY + }; + + private final SQLType type; + private final String table; + private final Map cols; + + public SQLMessage(SQLType type, String table) { + this(type, table, null); + } + + public SQLMessage(SQLType type, String table, Map cols) { + this.type = type; + this.table = table; + this.cols = cols; + } + + public String getFormattedMessage() { + switch (type) { + case UPDATE: + return createUpdateString(); + break; + case QUERY: + return createQueryString(); + break; + default; + } + } + + public String getMessageFormat() { + return type + " " + table; + } + + public Object getParameters() { + return cols; + } + + private String createUpdateString() { + } + + private String createQueryString() { + } + + private String formatCols(Map cols) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Map.Entry entry : cols.entrySet()) { + if (!first) { + sb.append(", "); + } + sb.append(entry.getKey()).append("=").append(entry.getValue()); + first = false; + } + return sb.toString(); + } +} +---- + +Next we can use the message in our application. + +[source,java] +---- +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import java.util.Map; + +public class MyApp { + + private Logger logger = LogManager.getLogger(MyApp.class.getName()); + private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL"); + private static final Marker UPDATE_MARKER = MarkerManager.getMarker("SQL_UPDATE", SQL_MARKER); + private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY", SQL_MARKER); + + public String doQuery(String table) { + logger.entry(param); + + logger.debug(QUERY_MARKER, new SQLMessage(SQLMessage.SQLType.QUERY, table)); + + return logger.exit(); + } + + public String doUpdate(String table, Map params) { + logger.entry(param); + + logger.debug(UPDATE_MARKER, new SQLMessage(SQLMessage.SQLType.UPDATE, table, parmas); + + return logger.exit(); + } +} +---- + +Notice that in contrast to the prior version of this example, the +`logger.debug` in `doUpdate` no longer needs to be wrapped in an +`isDebugEnabled` call as creation of the `SQLMessage` is on the same order +of magnitude of performing that check. Furthermore, all the formatting +of the SQL columns is now hidden in the `SQLMessage` instead of having to +take place in the business logic. Finally, if desired, Filters and/or +Layouts can be written to take special action when an `SQLMessage` is +encountered. + +[#FormattedMessage] +=== FormattedMessage + +The message pattern passed to a +link:../log4j-api/apidocs/org/apache/logging/log4j/message/FormattedMessage.html[`FormattedMessage`] +is first checked to see if it is a valid `java.text.MessageFormat` +pattern. If it is, a `MessageFormatMessage` is used to format it. If not +it is next checked to see if it contains any tokens that are valid +format specifiers for `String.format()`. If so, a `StringFormattedMessage` +is used to format it. Finally, if the pattern doesn't match either of +those then a `ParameterizedMessage` is used to format it. + +[#LocalizedMessage] +=== LocalizedMessage + +link:../log4j-api/apidocs/org/apache/logging/log4j/message/LocalizedMessage.html[`LocalizedMessage`] +is provided primarily to provide compatibility with Log4j 1.x. +Generally, the best approach to localization is to have the client UI +render the events in the client's locale. + +`LocalizedMessage` incorporates a `ResourceBundle` and allows the message +pattern parameter to be the key to the message pattern in the bundle. If +no bundle is specified, `LocalizedMessage` will attempt to locate a bundle +with the name of the Logger used to log the event. The message retrieved +from the bundle will be formatted using a FormattedMessage. + +[#LoggerNameAwareMessage] +=== LoggerNameAwareMessage + +`LoggerNameAwareMessage` is an interface with a `setLoggerName` method. This +method will be called during event construction so that the Message has +the name of the Logger used to log the event when the message is being +formatted. + +[#MapMessage] +=== MapMessage + +A `MapMessage` contains a Map of String keys and values. `MapMessage` +implements `FormattedMessage` and accepts format specifiers of "XML", +"JSON" or "JAVA", in which case the Map will be formatted as XML, JSON +or as documented by +https://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#toString()[`java.util.AbstractMap.toString()`]. +Otherwise, the Map will be formatted as `"key1=value1 key2=value2 ..."`. + +Some Appenders make special use of `MapMessage` objects: + +* When a link:appenders.html#JMSAppender[JMS Appender] is configured +with a `MessageLayout`, it converts a Log4j `MapMessage` to a JMS +`javax.jms.MapMessage`. +* When a link:appenders.html#JDBCAppender[JDBC Appender] is configured +with a `MessageLayout`, it converts a Log4j `MapMessage` to values in a +SQL INSERT statement. +* When a link:appenders.html#NoSQLAppenderMongoDB3[MongoDB3 Appender] or +link:appenders.html#NoSQLAppenderMongoDB4[MongoDB4 Appender] is +configured with a `MessageLayout`, it converts a Log4j `MapMessage` to +fields in a MongoDB object. + +When an Appender is `MessageLayout`-aware, the object Log4j sends to +target is not a Log4j Log Event but a custom object. + +[#MessageFormatMessage] +=== MessageFormatMessage + +link:../log4j-api/apidocs/org/apache/logging/log4j/message/MessageFormatMessage.html[`MessageFormatMessage`] +handles messages that use a +https://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html[conversion +format]. While this `Message` has more flexibility than +`ParameterizedMessage`, it is also about two times slower. + +[#MultiformatMessage] +=== MultiformatMessage + +A `MultiformatMessage` will have a getFormats method and a +`getFormattedMessage` method that accepts and array of format Strings. The +`getFormats` method may be called by a Layout to provide it information on +what formatting options the Message supports. The Layout may then call +`getFormattedMessage` with one or more for the formats. If the Message +doesn't recognize the format name it will simply format the data using +its default format. An example of this is `StructuredDataMessage` +which accepts a format String of "XML" which will cause it to format the +event data as XML instead of the RFC 5424 format. + +[#ObjectMessage] +=== ObjectMessage + +Formats an `Object` by calling its `toString` method. Since Log4j 2.6, +Layouts trying to be low-garbage or garbage-free will call the +`formatTo(StringBuilder)` method instead. + +[#ParameterizedMessage] +=== ParameterizedMessage + +link:../log4j-api/apidocs/org/apache/logging/log4j/message/ParameterizedMessage.html[`ParameterizedMessage`] +handles messages that contain "\{}" in the format to represent +replaceable tokens and the replacement parameters. + +[#ReusableObjectMessage] +=== ReusableObjectMessage + +In garbage-free mode, this message is used to pass logged Objects to the +Layout and Appenders. Functionally equivalent to +<>. + +[#ReusableParameterizedMessage] +=== ReusableParameterizedMessage + +In garbage-free mode, this message is used to handle messages that +contain "\{}" in the format to represent replaceable tokens and the +replacement parameters. Functionally equivalent to +<>. + +[#ReusableSimpleMessage] +=== ReusableSimpleMessage + +In garbage-free mode, this message is used to pass logged `String`s and +`CharSequence`s to the Layout and Appenders. Functionally equivalent to +<>. + +[#SimpleMessage] +=== SimpleMessage + +`SimpleMessage` contains a `String` or `CharSequence` that requires no +formatting. + +[#StringFormattedMessage] +=== StringFormattedMessage + +link:../log4j-api/apidocs/org/apache/logging/log4j/message/StringFormattedMessage.html[`StringFormattedMessage`] +handles messages that use a +https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax[conversion +format] that is compliant with +https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#format(java.lang.String,%20java.lang.Object...)[java.lang.String.format()]. +While this Message has more flexibility than `ParameterizedMessage`, it is +also 5 to 10 times slower. + +[#StructuredDataMessage] +=== StructuredDataMessage + +link:../log4j-api/apidocs/org/apache/logging/log4j/message/StructuredDataMessage.html[`StructuredDataMessage`] +allows applications to add items to a `Map` as well as set the id to allow +a message to be formatted as a Structured Data element in accordance +with http://tools.ietf.org/html/rfc5424[RFC 5424]. + +[#ThreadDumpMessage] +=== ThreadDumpMessage + +A ThreadDumpMessage, if logged, will generate stack traces for all +threads. The stack traces will include any locks that are held. + +[#TimestampMessage] +=== TimestampMessage + +A TimestampMessage will provide a `getTimestamp` method that is called +during event construction. The timestamp in the Message will be used in +lieu of the current timestamp. diff --git a/src/site/asciidoc/manual/migration.adoc b/src/site/asciidoc/manual/migration.adoc new file mode 100644 index 00000000000..dd9d260af44 --- /dev/null +++ b/src/site/asciidoc/manual/migration.adoc @@ -0,0 +1,383 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Migrating from Log4j 1.x +Ralph Goers + +[#Log4j1.2Bridge] +== When to use this the Log4j 1.x bridge + +This bridge is useful as a dependency of an "application" which would like to use Log4j v2 as its main logging framework, if either that application itself hasn't been changed from using the Log4j 1.x API to the 1.x API, or if such an application depends on one or several (perhaps old) libraries that are "out of your control", and which themselves still contain code and thus a compile dependency on Log 1.x only. + +Once you have migrated all of your own application & library code under your control, you may not need this bridge. Note that when you use a library/framework that can be configured to use several logging frameworks, then you typically don't need this bridge either anymore, as you may be able to directly configure it to use Log4j v2 instead v1. Some libraries/frameworks even auto-detect the presence of certain logging framework implementations on their classpath, and automagically switch their internal logging delegation accordingly; try simple removing the Log4j v1 dependency instead of replacing it with this bridge, and test if logging from all of your depenencies still work. + +If you own or can contribute open source to the library you depend on, then you likely would prefer replacing its use of the Log4j v1 Logging API with the v2 API. (It typically does not make sense for libraries to depend on this bridge and support both the v1 and v2 Log4j API, because end-users of such a library would still have to replace their Log4j configuration v1 with a different v2 config anyway; see below.) + +== Using the Log4j 1.x bridge + +Perhaps the simplest way to convert to using Log4j 2 is to replace the +log4j 1.x jar file with Log4j 2's `log4j-1.2-api.jar`. However, to use +this successfully applications must meet the following requirements: + +1. They must not access methods and classes internal to the Log4j 1.x +implementation such as `Appender`s, `LoggerRepository` or `Category`'s +`callAppenders` method. +2. They must not programmatically configure Log4j. +3. They must not configure by calling the classes `DOMConfigurator` or +`PropertyConfigurator`. + +== Converting to the Log4j 2 API + +For the most part, converting from the Log4j 1.x API to Log4j 2 should +be fairly simple. Many of the log statements will require no +modification. However, where necessary the following changes must be +made. + +. The main package in version 1 is `org.apache.log4j`, in version 2 it +is `org.apache.logging.log4j` +. Calls to `org.apache.log4j.Logger.getLogger()` must be modified to +`org.apache.logging.log4j.LogManager.getLogger()`. +. Calls to `org.apache.log4j.Logger.getRootLogger()` or +`org.apache.log4j.LogManager.getRootLogger()` must be replaced with +`org.apache.logging.log4j.LogManager.getRootLogger()`. +. Calls to `org.apache.log4j.Logger.getLogger` that accept a +`LoggerFactory` must remove the `org.apache.log4j.spi.LoggerFactory` and +use one of Log4j 2's other extension mechanisms. +. Replace calls to `org.apache.log4j.Logger.getEffectiveLevel()` with +`org.apache.logging.log4j.Logger.getLevel()`. +. Remove calls to `org.apache.log4j.LogManager.shutdown()`, they are +not needed in version 2 because the Log4j Core now automatically adds a +JVM shutdown hook on start up to perform any Core clean ups. +.. Starting in Log4j 2.1, you can specify a custom +link:../log4j-core/apidocs/org/apache/logging/log4j/core/util/ShutdownCallbackRegistry.html[`ShutdownCallbackRegistry`] +to override the default JVM shutdown hook strategy. +.. Starting in Log4j 2.6, you can now use +`org.apache.logging.log4j.LogManager.shutdown()` to initiate shutdown +manually. +. Calls to `org.apache.log4j.Logger.setLevel()` or similar methods are +not supported in the API. Applications should remove these. Equivalent +functionality is provided in the Log4j 2 implementation classes, see +`org.apache.logging.log4j.core.config.Configurator.setLevel()`, but may +leave the application susceptible to changes in Log4j 2 internals. +. Where appropriate, applications should convert to use parameterized +messages instead of String concatenation. +. http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html[`org.apache.log4j.MDC`] +and +http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/NDC.html[`org.apache.log4j.NDC`] +have been replaced by the link:thread-context.html[Thread Context]. + +== Configuring Log4j 2 + +Although the Log4j 2 configuration syntax is different than that of +Log4j 1.x, most, if not all, of the same functionality is available. + +Note that system property interpolation via the `${foo}` syntax has been +extended to allow property lookups from many different sources. See the +link:lookups.html[Lookups] documentation for more details. For example, +using a lookup for the system property named `catalina.base`, in Log4j +1.x, the syntax would be `${catalina.base}`. In Log4j 2, the syntax +would be `${sys:catalina.base}`. + +Log4j 1.x has a XMLLayout which is different from the XmlLayout in Log4j +2, the log4j-1.2-api module contains a `Log4j1XmlLayout` which produce +output in the format as in Log4j 1.x. The Log4j 1.x `SimpleLayout` can +be emulated with PatternLayout "%level - %m%n". The Log4j 1.x +`TTCCLayout` can be emulated with PatternLayout "%r [%t] %p %c +%notEmpty\{%ndc }- %m%n". + +Both `PatternLayout` and `EnhancedPatternLayout` in Log4j 1.x can be +replaced with `PatternLayout` in Log4j 2. The log4j-1.2-api module +contains two pattern conversions "%ndc" and "%properties" which can be +used to emulate "%x" and "%X" in Log4j 1.x PatternLayout ("%x" and %X" +in Log4j 2 have a slightly different format). + +Below are the example configurations for Log4j 1.x and their +counterparts in Log4j 2. + +=== Sample 1 - Simple configuration using a Console Appender + +Log4j 1.x XML configuration + +.log4j.xml +[source,xml] +---- + + + + + + + + + + + + + + + + +---- + +Log4j 2 XML configuration + +.log4j2.xml +[source,xml] +---- + + + + + + + + + + + + + + +---- + +=== Sample 2 - Simple configuration using a File Appender, XMLLayout and SimpleLayout + +Log4j 1.x XML configuration + +.log4j.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + +---- + +Log4j 2 XML configuration + +.log4j2.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + + + +---- + +=== Sample 3 - SocketAppender + +Log4j 1.x XML configuration. This example from Log4j 1.x is misleading. +The SocketAppender does not actually use a Layout. Configuring one will +have no effect. + +.log4j.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + + + +---- + +Log4j 2 XML configuration + +.log4j2.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + + + +---- + +=== Sample 4 - AsyncAppender and TTCCLayout + +Log4j 1.x XML configuration using the AsyncAppender. + +.log4j.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + + + +---- + +Log4j 2 XML configuration. + +.log4j2.xml +[source,xml] +---- + + + + + + + + + + + + + + + + +---- + +=== Sample 5 - AsyncAppender with Console and File + +Log4j 1.x XML configuration using the AsyncAppender. + +.log4j.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + + + + +---- + +Log4j 2 XML configuration. Note that the Async Appender should be +configured after the appenders it references. This will allow it to +shutdown properly. + +.log4j2.xml +[source,xml] +---- + + + + + + + + + + + + + + + + + + + + +---- diff --git a/src/site/asciidoc/manual/plugins.adoc b/src/site/asciidoc/manual/plugins.adoc new file mode 100644 index 00000000000..3185575c79f --- /dev/null +++ b/src/site/asciidoc/manual/plugins.adoc @@ -0,0 +1,262 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Plugins +Ralph Goers ; Matt Sicker + +Log4j 1.x allowed for extension by requiring class attributes on most of +the configuration declarations. In the case of some elements, notably +the PatternLayout, the only way to add new pattern converters was to +extend the PatternLayout class and add them via code. One goal of Log4j +2 is to make extending it extremely easy through the use of plugins. + +In Log4j 3.x, a plugin is declared by adding a link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/Plugin.html[`@Plugin`] and `@Namespace` annotation to the class declaration. +During initialization the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/Configuration.html[`Configuration`] +will invoke the +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/util/PluginRegistry.html[`PluginRegistry`] +to load the built-in Log4j plugins as well as any custom plugins. The +`Injector` locates plugins by looking in the following places: + +1. Plugin collection classes on the classpath that are loaded by java.util.ServiceLoader. +These classes are generated automatically during the build (more details below). +2. (OSGi only) Serialized plugin listing files in each active OSGi +bundle. A `BundleListener` is added on activation to continue checking +new bundles after `log4j-plugins` has started. Bundles must register their plugin collection +class as an OSGi service. +3. Serialized plugin listing files on the classpath. These files were generated by +the plugin annotation processor in Log4j 2 2.x. These are processed to allow +compatibility. + +When multiple plugins use the same case-insensitive `name` within the same plugin category, then which one is selected is determined first by the presence of `@PluginOrder` annotations and then by the previously described plugin loading order. +For example, to override the `File` plugin which is provided by the built-in `FileAppender` class, you would need to place your plugin in a JAR file in the CLASSPATH ahead of`log4j-core.jar`. +This is not recommended; plugin name collisions will cause a warning to be emitted. +Note that in an OSGi environment, the order that bundles are scanned for plugins generally follows the same order that bundles were installed into the framework. +See https://www.osgi.org/javadoc/r5/core/org/osgi/framework/BundleContext.html#getBundles()[`getBundles()`] and https://www.osgi.org/javadoc/r5/core/org/osgi/framework/SynchronousBundleListener.html[`SynchronousBundleListener`]. +In short, name collisions are even more unpredictable in an OSGi environment without additional `@PluginOrder` usage. + +Plugin collection classes are generated by an annotation processor contained +in the log4j-plugins artifact which will automatically scan your code for +Log4j 2 plugins and generate a Java source file that references all the +located plugins. It will also generate a +META-INF/services/org.apache.logging.log4j.plugins.model.PluginService +file in compliance with java.util.ServiceLoader. +There is nothing extra that needs to be done to enable this; +the Java compiler will automatically pick up the annotation processor on +the class path unless you explicitly disable it. + +If annotation processing is disabled plugins may still be registered by either +[loweralpha] +.. manually providing a class that extends `org.apache.logging.log4j.plugins.model.PluginService` +and identifies all the plugins and also declaring a +`META-INF/services/org.apache.logging.log4j.plugins.model.PluginService` file +that provides the fully qualified name of the implemented class or +.. adding another compiler pass to the build process that +only handles annotation processing using the Log4j 2 annotation +processor class, +`org.apache.logging.log4j.plugin.processor.PluginProcessor`. +To do this using Apache Maven, add the following execution to your +_maven-compiler-plugin_ (version 2.2 or higher) build plugin: + +[source,xml] +---- + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + + log4j-plugin-processor + + compile + + process-classes + + only + + org.apache.logging.log4j.plugin.processor.PluginProcessor + + + + + +---- + +As the configuration is processed the appropriate plugins will be +automatically configured and initialized. + +Log4j 3 utilizes a few different namespaces of plugins which are described in the following sections. + +[#Core] +== Core + +Core plugins are those that are directly represented by an element in a +configuration file and correspond to `@Configurable` namespaced `@Plugin` classes such as an `Appender`, `Layout`, `Logger` or `Filter`. +Custom plugins that conform to the rules laid out in the next paragraph may simply be referenced in the configuration. + +Core plugins support a few different dependency injection rules for binding configuration node data and other injectable classes into a plugin instance. +Each plugin must declare a static method annotated with `@Factory` or `@PluginFactory` or contain an `@Inject`-annotated constructor or no-args constructor. +These static factory method or `@Inject` constructor parameters are used for initial dependency injection. +When a static factory method returns a type that implements `java.util.function.Supplier` such as through `Builder`, then that supplier instance first has its members injected before invoking `Supplier::get` to obtain the resulting plugin instance. +See link:./dependencyinjection.html[Dependency Injection] for further details on how dependency injection works. +Plugins are configured using additional plugin qualifier annotations such as `@PluginAttribute`, `@PluginBuilderAttribute`, `@PluginElement`, `@PluginValue`, etc., as described below. + +There are dozens of plugins in Log4j Core that can be +used as examples for more complex scenarios including hierarchical +builder classes (e.g., see `FileAppender`). See +link:extending.html#Plugin_Builders[Extending Log4j with Plugin +Builders] for more details. + +=== Attribute Types + +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginAttribute.html[`PluginAttribute`]:: + The parameter must be convertible from a String using a + link:#TypeConverters[TypeConverter]. Most built-in types are already + supported, but custom `TypeConverter` plugins may also be provided for + more type support. Note that `PluginBuilderAttribute` can be used in + builder class fields as an easier way to provide default values. +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginElement.html[`PluginElement`]:: + The parameter may represent a complex object that itself has + parameters that can be configured. This also supports injecting an + array of elements. +link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.html[`PluginConfiguration`]:: + The current `Configuration` object will be passed to the plugin as a + parameter. +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginNode.html[`PluginNode`]:: + The current `Node` being parsed will be passed to the plugin as a + parameter. +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginValue.html[`PluginValue`]:: + The value of the current `Node` or its attribute named `value`. + +=== Constraint Validators + +Plugin factory fields, methods, and parameters can be automatically validated at +runtime using constraint validators inspired by the +http://beanvalidation.org/[Bean Validation spec]. The following +annotations are bundled in Log4j, but custom +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/validation/ConstraintValidator.html[`ConstraintValidators`] +can be created as well. + +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/validation/constraints/Required.html[`Required`]:: + This annotation validates that a value is non-empty. This covers a + check for `null` as well as several other scenarios: empty + `CharSequence` objects, empty arrays, empty `Collection` instances, + and empty `Map` instances. +`RequiredClass`:: + This annotation validates that a class name can be loaded. This is useful for plugins that should only be loaded when an optional class is present. +`RequiredProperty`:: + This annotation validates that a system property is set to some value. +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.html[`ValidHost`]:: + This annotation validates that a value corresponds to a valid + hostname. This uses the same validation as + http://docs.oracle.com/javase/8/docs/api/java/net/InetAddress.html#getByName-java.lang.String-[`InetAddress::getByName`]. +link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidPort.html[`ValidPort`]:: + This annotation validates that a value corresponds to a valid port + number between 0 and 65535. + +[#Converters] +== Converters + +Converters are used by +link:../log4j-core/apidocs/org/apache/logging/log4j/core/layout/PatternLayout.html[`PatternLayout`] +to render the elements identified by the conversion pattern. Every +converter must specify its category as "Converter" on the `@Plugin` +annotation, have a static `newInstance` method that accepts an array of +`String` as its only parameter and returns an instance of the +Converter, and must have a `@ConverterKeys` annotation present that +contains the array of converter patterns that will cause the Converter +to be selected. Converters that are meant to handle `LogEvent` must +extend the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/layout/LogEventPatternConverter.html[`LogEventPatternConverter`] +class and must implement a format method that accepts a `LogEvent` and a +`StringBuilder` as arguments. The Converter should append the result of +its operation to the `StringBuilder`. + +A second type of Converter is the FileConverter - which must have +"FileConverter" specified in the category attribute of the `@Plugin` +annotation. While similar to a `LogEventPatternConverter`, instead of a +single format method these Converters will have two variations; one that +takes an `Object` and one that takes an array of `Object` instead of +the `LogEvent`. Both append to the provided `StringBuilder` in the same +fashion as a `LogEventPatternConverter`. These Converters are typically +used by the `RollingFileAppender` to construct the name of the file to +log to. + +If multiple Converters specify the same `ConverterKeys`, then the load +order above determines which one will be used. For example, to override +the `%date` converter which is provided by the built-in +`DatePatternConverter` class, you would need to place your plugin in a +JAR file in the CLASSPATH ahead of `log4j-core.jar`. This is not +recommended; pattern ConverterKeys collisions will cause a warning to be +emitted. Try to use unique ConverterKeys for your custom pattern +converters. + +[#KeyProviders] +== KeyProviders + +Some components within Log4j may provide the ability to perform data +encryption. These components require a secret key to perform the +encryption. Applications may provide the key by creating a class that +implements the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/util/SecretKeyProvider.html[`SecretKeyProvider`] +interface. + +[#Lookups] +== Lookups + +Lookups are perhaps the simplest plugins of all. They must declare their +type as "Lookup" on the plugin annotation and must implement the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/lookup/StrLookup.html[`StrLookup`] +interface. They will have two methods; a `lookup` method that accepts a +`String` key and returns a `String` value and a second `lookup` method that +accepts both a `LogEvent` and a `String` key and returns a `String`. Lookups +may be referenced by specifying $\{name:key} where name is the name +specified in the Plugin annotation and key is the name of the item to +locate. + +[#TypeConverters] +== TypeConverters + +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/convert/TypeConverter.html[`TypeConverter`]s +are a sort of meta-plugin used for converting strings into other types +in a plugin factory method parameter. Other plugins can already be +injected via the `@PluginElement` annotation; now, any type supported by +the type conversion system can be used in a `@PluginAttribute` +parameter. Conversion of enum types are supported on demand and do not +require custom `TypeConverter` classes. A large number of built-in Java +classes are already supported; see +link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/convert/TypeConverters.html[`TypeConverters`] +and +link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/convert/CoreConverters.html[`CoreConverters`] +for a more exhaustive listing. + +Unlike other plugins, the plugin name of a `TypeConverter` is purely +cosmetic. Appropriate type converters are looked up via the `Type` +interface rather than via `Class` objects only. Do note that +`TypeConverter` plugins must have a default constructor. + +When multiple converters match for a type, the first will be returned. +If any extends from `Comparable>`, it will be used for determining the order. + +[#DeveloperNotes] +== Developer Notes + +If a plugin class implements +http://docs.oracle.com/javase/6/docs/api/java/util/Collection.html[`Collection`] +or http://docs.oracle.com/javase/6/docs/api/java/util/Map.html[`Map`], +then no factory method is used. Instead, the class is instantiated using +the default constructor, and all child configuration nodes are added to +the `Collection` or `Map`. diff --git a/src/site/asciidoc/manual/scala-api.adoc b/src/site/asciidoc/manual/scala-api.adoc new file mode 100644 index 00000000000..685f3705436 --- /dev/null +++ b/src/site/asciidoc/manual/scala-api.adoc @@ -0,0 +1,81 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Log4j 2 Scala API +Mikael Ståldal + +Log4j 2 has a companion Log4j Scala project that contains a convenient +Scala wrapper for the +link:../log4j-api/apidocs/org/apache/logging/log4j/Logger.html[`Logger`] +API. + +== Requirements + +Log4j 2 Scala API is dependent on the Log4j 2 API, Scala runtime library +and reflection. It currently supports Scala 2.10, 2.11, 2.12 and 2.13. +Scala 3 support has been recently added. +See link:../maven-artifacts.html#Scala_API[instructions] on including this +in an SBT, Maven, Ivy, or Gradle project. + +== Example + +[source,scala] +---- +import org.apache.logging.log4j.scala.Logging +import org.apache.logging.log4j.Level + +class MyClass extends BaseClass with Logging { + def doStuff(): Unit = { + logger.info("Doing stuff") + } + def doStuffWithLevel(level: Level): Unit = { + logger(level, "Doing stuff with arbitrary level") + } +} +---- + +The output from the call to `logger.info()` will vary significantly +depending on the configuration used. See the +link:./configuration.html[Configuration] section for more details. + +== Substituting Parameters + +Frequently the purpose of logging is to provide information about what +is happening in the system, which requires including information about +the objects being manipulated. In Scala, you can use +http://docs.scala-lang.org/overviews/core/string-interpolation.html[string +interpolation] to achieve this: + +[source,scala] +---- +logger.debug(s"Logging in user ${user.getName} with birthday ${user.calcBirthday}") +---- + +Since the Scala Logger is implemented with macros, the String +construction and method invocations will only occur when debug logging +is enabled. + +== Logger Names + +Most logging implementations use a hierarchical scheme for matching +logger names with logging configuration. In this scheme the logger name +hierarchy is represented by '.' characters in the logger name, in a +fashion very similar to the hierarchy used for Java/Scala package names. +The Logging trait will automatically name the Logger accordingly to the +class it is being used in. + +Please see the http://logging.apache.org/log4j/scala/index.html[Log4j +Scala] project for more details. diff --git a/src/site/asciidoc/manual/thread-context.adoc b/src/site/asciidoc/manual/thread-context.adoc new file mode 100644 index 00000000000..c90501d3577 --- /dev/null +++ b/src/site/asciidoc/manual/thread-context.adoc @@ -0,0 +1,239 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Log4j 2 API +Ralph Goers ; Gary Gregory + +== Thread Context + +Log4j introduced the concept of the Mapped Diagnostic Context or MDC. It +has been documented and discussed in numerous places including +https://veerasundar.com/blog/log4j-mdc-mapped-diagnostic-context-what-and-why/[Log4j +MDC: What and Why] and +http://blog.f12.no/wp/2004/12/09/log4j-and-the-mapped-diagnostic-context/[Log4j +and the Mapped Diagnostic Context]. In addition, Log4j 1.x provides +support for a Nested Diagnostic Context or NDC. It too has been +documented and discussed in various places such as +http://lstierneyltd.com/blog/development/log4j-nested-diagnostic-contexts-ndc/[Log4j +NDC]. SLF4J/Logback followed with its own implementation of the MDC, +which is documented very well at +http://logback.qos.ch/manual/mdc.html[Mapped Diagnostic Context]. + +Log4j 2 continues with the idea of the MDC and the NDC but merges them +into a single Thread Context. The Thread Context Map is the equivalent +of the MDC and the Thread Context Stack is the equivalent of the NDC. +Although these are frequently used for purposes other than diagnosing +problems, they are still frequently referred to as the MDC and NDC in +Log4j 2 since they are already well known by those acronyms. + +=== Fish Tagging + +Most real-world systems have to deal with multiple clients +simultaneously. In a typical multithreaded implementation of such a +system, different threads will handle different clients. Logging is +especially well suited to trace and debug complex distributed +applications. A common approach to differentiate the logging output of +one client from another is to instantiate a new separate logger for each +client. This promotes the proliferation of loggers and increases the +management overhead of logging. + +A lighter technique is to uniquely stamp each log request initiated from +the same client interaction. Neil Harrison described this method in the +book "Patterns for Logging Diagnostic Messages," in _Pattern Languages +of Program Design 3_, edited by R. Martin, D. Riehle, and F. Buschmann +(Addison-Wesley, 1997). Just as a fish can be tagged and have its +movement tracked, stamping log events with a common tag or set of data +elements allows the complete flow of a transaction or a request to be +tracked. We call this _Fish Tagging_. + +Log4j provides two mechanisms for performing Fish Tagging; the Thread +Context Map and the Thread Context Stack. The Thread Context Map allows +any number of items to be added and be identified using key/value pairs. +The Thread Context Stack allows one or more items to be pushed on the +Stack and then be identified by their order in the Stack or by the data +itself. Since key/value pairs are more flexible, the Thread Context Map +is recommended when data items may be added during the processing of the +request or when there are more than one or two items. + +To uniquely stamp each request using the Thread Context Stack, the user +pushes contextual information on to the Stack. + +[source,java] +---- +ThreadContext.push(UUID.randomUUID().toString()); // Add the fishtag; + +logger.debug("Message 1"); + +// ... + +logger.debug("Message 2");. + +// ... + +ThreadContext.pop(); +---- + +The alternative to the Thread Context Stack is the Thread Context Map. +In this case, attributes associated with the request being processed are +adding at the beginning and removed at the end as follows: + +[source,java] +---- +ThreadContext.put("id", UUID.randomUUID().toString()); // Add the fishtag; +ThreadContext.put("ipAddress", request.getRemoteAddr()); +ThreadContext.put("loginId", session.getAttribute("loginId")); +ThreadContext.put("hostName", request.getServerName()); + +// ... + +logger.debug("Message 1"); + +// ... + +logger.debug("Message 2"); + +// ... + +ThreadContext.clear(); +---- + +=== CloseableThreadContext + +When placing items on the stack or map, it's necessary to remove then +again when appropriate. To assist with this, the +`CloseableThreadContext` implements the +http://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html[`AutoCloseable` +interface]. This allows items to be pushed to the stack or put in the +map, and removed when the `close()` method is called - or automatically +as part of a try-with-resources. For example, to temporarily push +something on to the stack and then remove it: + +[source,java] +---- +// Add to the ThreadContext stack for this try block only; +try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.push(UUID.randomUUID().toString())) { + + logger.debug("Message 1"); + +// ... + + logger.debug("Message 2"); + +// ... + +} +---- + +Or, to temporarily put something in the map: + +[source,java] +---- +// Add to the ThreadContext map for this try block only; +try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put("id", UUID.randomUUID().toString()) + .put("loginId", session.getAttribute("loginId"))) { + + logger.debug("Message 1"); + +// ... + + logger.debug("Message 2"); + +// ... + +} +---- + +If you're using a thread pool, then you can initialise a +CloseableThreadContext by using the +`putAll(final Map values)` and/or +`pushAll(List messages)` methods; + +[source,java] +---- +for( final Session session : sessions ) { + try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put("loginId", session.getAttribute("loginId"))) { + logger.debug("Starting background thread for user"); + final Map values = ThreadContext.getImmutableContext(); + final List messages = ThreadContext.getImmutableStack().asList(); + executor.submit(new Runnable() { + public void run() { + try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.putAll(values).pushAll(messages)) { + logger.debug("Processing for user started"); + +// ... + + logger.debug("Processing for user completed"); + } + }); + } +} +---- + +=== Implementation details + +The Stack and the Map are managed per thread and are based on +http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html[`ThreadLocal`] +by default. The Map can be configured to use an +http://docs.oracle.com/javase/6/docs/api/java/lang/InheritableThreadLocal.html[`InheritableThreadLocal`] +(see the Configuration section). When configured this way, the contents of the Map will be passed +to child threads. However, as discussed in the +http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/Executors.html#privilegedThreadFactory()[`Executors`] +class and in other cases where thread pooling is utilized, the +ThreadContext may not always be automatically passed to worker threads. +In those cases the pooling mechanism should provide a means for doing +so. The `getContext()` and `cloneStack()` methods can be used to obtain +copies of the Map and Stack respectively. + +Note that all methods of the +link:../log4j-api/apidocs/org/apache/logging/log4j/ThreadContext.html[`ThreadContext`] +class are static. + +==== Configuration +Set the system property `log4j2.disableThreadContextMap` to `true` to disable the Thread Context Map. +Set the system property `log4j2.disableThreadContextStack` to `true` to disable the Thread Context Stack. +Set the system property `log4j2.disableThreadContext` to `true` to disable both the Thread Context Map and Stack. +Set the system property `log4j2.isThreadContextMapInheritable` to `true` to enable child threads to inherit the Thread +Context Map. + +=== Including the ThreadContext when writing logs + +The +link:../log4j-core/apidocs/org/apache/logging/log4j/core/layout/PatternLayout.html[`PatternLayout`] +provides mechanisms to print the contents of the +link:../log4j-api/apidocs/org/apache/logging/log4j/ThreadContext.html[`ThreadContext`] +Map and Stack. + +* Use `%X` by itself to include the full contents of the Map. +* Use `%X{key}` to include the specified key. +* Use `%x` to include the full contents of the +http://docs.oracle.com/javase/6/docs/api/java/util/Stack.html[Stack]. + +=== Custom context data injectors for non thread-local context data + +With the ThreadContext logging statements can be tagged so log entries +that were related in some way can be linked via these tags. The +limitation is that this only works for logging done on the same +application thread (or child threads when configured). + +Some applications have a thread model that delegates work to other +threads, and in such models, tagging attributes that are put into a +thread-local map in one thread are not visible in the other threads and +logging done in the other threads will not show these attributes. + +Log4j 2.7 adds a flexible mechanism to tag logging statements with +context data coming from other sources than the ThreadContext. See the +manual page on link:extending.html#Custom_ContextDataInjector[extending +Log4j] for details. diff --git a/src/site/asciidoc/manual/usage.adoc b/src/site/asciidoc/manual/usage.adoc new file mode 100644 index 00000000000..1eaa5e40c43 --- /dev/null +++ b/src/site/asciidoc/manual/usage.adoc @@ -0,0 +1,227 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// + += Usage + +[#Static_vs_Non_Static] +== Static vs Non-Static Loggers +As with any variable in Java, Loggers may be declared as static variables or class member variables. However, +there are a few factors to consider when choosing to declare a logger as static vs non-static. Generally, it +is better to declare Loggers as static. + +1. Instantiation of a new Logger is a fairly expensive operation when using the default ContextSelector, +link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[ClassLoaderContextSelector]. +When the Logger is created the `ClassLoaderContextSelector` will locate the ClassLoader for the Class the Logger +is associated with and add the Logger to the LoggerContext associated with that ClassLoader. +2. Once a Logger is created it will not be deleted until the `LoggerContext` it is associated with +is deleted. Typically, this will only happen when the application is shut down or un-deployed. Each call +to getLogger with the same logger name will return the same Logger instance. Thus, there is very little +difference between a static or non-static Logger. +3. There is no behavioral difference between a static and non-static Logger. Both will have the Logger name +assigned when they are created, which usually will be the name of the class they are associated with. See +the discussion below on logger names vs class names and the example for more information. + +== Logging the Logger name vs the Class name +The logger name of a Logger is specified when the Logger is created. When a log method is called the +class name value in the log event will reflect the name of the class the log method was called from, which is +not necessarily the same as the class that created the Logger. The following example illustrations this. + +The base class creates a static Logger and a logger variable that is initialized as that same Logger. + It has two methods that perform logging, once that uses the static logger and one that uses a Logger that + can be overridden. + + +[source] +---- + package org.apache.logging; + + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.Marker; + + /** + * + */ + public abstract class Parent { + + // The name of this Logger will be "org.apache.logging.Parent" + protected static final Logger parentLogger = LogManager.getLogger(); + + private Logger logger = parentLogger; + + protected Logger getLogger() { + return logger; + } + + protected void setLogger(Logger logger) { + this.logger = logger; + } + + + public void log(Marker marker) { + logger.debug(marker,"Parent log message"); + } + } +---- + +This class extends the base class. It provides its own logger and has three methods, one that uses the +logger in this class,one that uses the static logger from the base class, and one that where the logger +may be set to either the parent or the child. + +[source] +---- + package org.apache.logging; + + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.Marker; + + /** + * + */ + public class Child extends Parent { + + // The name of this Logge will be "org.apache.logging.Child" + public Logger childLogger = LogManager.getLogger(); + + public void childLog(Marker marker) { + childLogger.debug(marker,"Child logger message"); + } + + public void logFromChild(Marker marker) { + getLogger().debug(marker,"Log message from Child"); + } + + public void parentLog(Marker marker) { + parentLogger.debug(marker,"Parent logger, message from Child"); + } + } +---- + +The application exercises all the logging methods four times. The first two times the Logger in the base +class is set to the static Logger. The second two times the Logger in the base class is set to use the +Logger in the subclass. In the first and third invocation of each method a null Marker is passed. In the +second and fourth a Marker named "CLASS" is passed. + +[source] +---- + package org.apache.logging; + + import org.apache.logging.log4j.Marker; + import org.apache.logging.log4j.MarkerManager; + + public class App { + + public static void main( String[] args ) { + Marker marker = MarkerManager.getMarker("CLASS"); + Child child = new Child(); + + System.out.println("------- Parent Logger ----------"); + child.log(null); + child.log(marker); + child.logFromChild(null); + child.logFromChild(marker); + child.parentLog(null); + child.parentLog(marker); + + child.setLogger(child.childLogger); + + System.out.println("------- Parent Logger set to Child Logger ----------"); + child.log(null); + child.log(marker); + child.logFromChild(null); + child.logFromChild(marker); + } + } +---- + +The configuration takes advantage of Log4j's ability to select a pattern based upon attributes of the log event. +In this case %C, the class name pattern, is used when the CLASS Marker is present, and %c, the logger name +is used when the CLASS marker is not present. + +[source,xml] +---- + + + + + + + + + + + + + + + + + +---- + +The output below illustrates the difference between using the Logger name and the Class name in the pattern. All +the odd numbered items print the name of the logger (%c) while all the even numbered items print the +name of the class that called the logging method (%C). The numbers in the description of the outcomes in the +following list match the corresponding numbers shown in the output. + +1. Logging is performed in the parent class using the static logger with the Logger name pattern. The +logger name matches the name of the parent class. +2. Logging is performed in the parent class using the static logger with the Class name pattern. Although +the method was called against the Child instance it is implemented in Parent so that is what appears. +3. Logging is performed in Child using the logger in the parent, so the name of the parent is printed as the logger +name. +4. Logging is performed in Child using the logger in the parent. Since the method calling the logging +method is in Child that is the class name that appears. +5. Logging is performed in Child using the static logger in the parent, so the name of the parent is printed as the +logger name. +6. Logging is performed in Child using the static logger in the parent. Since the method calling the logging +method is in Child that is the class name that appears. +7. Logging is performed in the parent class using the logger of Child. The logger name matches the name of the child +and so it is printed. +8. Logging is performed in the parent class using the logger of the Child. Although the method was called against +the Child instance it is implemented in Parent so that is what appears as the class name. +9. Logging is performed in Child using the logger in the parent which is set to the child logger, so the name of the +child is printed as the logger name. +10. Logging is performed in Child using the logger in the parent, which is set to the child logger. Since +the method calling the logging method is in Child that is the class name that appears. + +[source] +---- + ------- Parent Logger ---------- + 1. Parent log message: Logger=org.apache.logging.Parent + 2. Parent log message: Class=org.apache.logging.Parent + 3. Log message from Child: Logger=org.apache.logging.Parent + 4. Log message from Child: Class=org.apache.logging.Child + 5. Parent logger, message from Child: Logger=org.apache.logging.Parent + 6. Parent logger, message from Child: Class=org.apache.logging.Child + ------- Parent Logger set to Child Logger ---------- + 7. Parent log message: Logger=org.apache.logging.Child + 8. Parent log message: Class=org.apache.logging.Parent + 9. Log message from Child: Logger=org.apache.logging.Child + 10. Log message from Child: Class=org.apache.logging.Child +---- + +In the example above there are two Loggers declared. One is static and one is non-static. When looking at +the results it is clear that the outcomes would be exactly the same regardless of whether how the loggers +are declared. The name of the logger will always originate from the class in which it is created and the +Class name in each log event will always reflect the Class from which the logging method was called. + +It should be noted that there is a substantial performance penalty for printing the location information +(class name, method name, and line number). If the method name and line number are not important it is +usually better to make sure that each class has its own Logger so the logger name accurately reflects the +class performing the logging. diff --git a/src/site/asciidoc/manual/webapp.adoc b/src/site/asciidoc/manual/webapp.adoc new file mode 100644 index 00000000000..f4d2c410bd9 --- /dev/null +++ b/src/site/asciidoc/manual/webapp.adoc @@ -0,0 +1,497 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Web Applications +Nick Williams ; Matt Sicker + +== Using Log4j 2 in Web Applications + +You must take particular care when using Log4j or any other logging +framework within a Java EE web application. It's important for logging +resources to be properly cleaned up (database connections closed, files +closed, etc.) when the container shuts down or the web application is +undeployed. Because of the nature of class loaders within web +applications, Log4j resources cannot be cleaned up through normal means. +Log4j must be "started" when the web application deploys and "shut down" +when the web application undeploys. How this works varies depending on +whether your application is a link:#Servlet-3.0[Servlet 3.0 or newer] or +link:#Servlet-2.5[Servlet 2.5] web application. + + +Due to the namespace change from `javax` to `jakarta` you need to use +`log4j-jakarta-web` instead of `log4j-web` for Servlet 5.0 or newer. + + +In either case, you'll need to add the `log4j-web` module to your +deployment as detailed in the link:../maven-artifacts.html[Maven, Ivy, +and Gradle Artifacts] manual page. + +NOTE: To avoid problems the Log4j shutdown hook will automatically be +disabled when the log4j-web jar is included. + +[#Configuration] +== Configuration + +Log4j allows the configuration file to be specified in web.xml using the +`log4jConfiguration` context parameter. Log4j will search for +configuration files by: + +1. If a location is provided it will be searched for as a servlet +context resource. For example, if `log4jConfiguration` contains +"logging.xml" then Log4j will look for a file with that name in the root +directory of the web application. +2. If no location is defined Log4j will search for a file that starts +with "log4j2" in the WEB-INF directory. If more than one file is found, +and if a file that starts with "log4j2-name" is present, where name is +the name of the web application, then it will be used. Otherwise the +first file will be used. +3. The "normal" search sequence using the classpath and file URLs will +be used to locate the configuration file. + +[#Servlet-3.0] +== Servlet 3.0 and Newer Web Applications + +A Servlet 3.0 or newer web application is any `` whose +`version` attribute has a value of "3.0" or higher. Of course, the +application must also be running in a compatible web container. + +Some examples are: + +- Tomcat 7.0 and higher +- GlassFish 3.0 and higher +- JBoss 7.0 and higher +- Oracle WebLogic 12c and higher +- IBM WebSphere 8.0 and higher. + +=== The Short Story + +Log4j 2 "just works" in Servlet 3.0 and newer web applications. It is +capable of automatically starting when the application deploys and +shutting down when the application undeploys. Thanks to the +https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html[`ServletContainerInitializer`] +API added to Servlet 3.0, the relevant `Filter` and +`ServletContextListener` classes can be registered dynamically on web +application startup. + +NOTE: For performance reasons, containers often ignore +certain JARs known not to contain TLDs or `ServletContainerInitializer` +and do not scan them for web-fragments and initializers. Importantly, +Tomcat 7 <7.0.43 ignores all JAR files named log4j*.jar, which prevents +this feature from working. This has been fixed in Tomcat 7.0.43, Tomcat +8, and later. In Tomcat 7 <7.0.43 you will need to change +`catalina.properties` and remove "log4j*.jar" from the `jarsToSkip` +property. You may need to do something similar on other containers if +they skip scanning Log4j JAR files. + +=== The Long Story + +The Log4j 2 Web JAR file is a web-fragment configured to order before +any other web fragments in your application. It contains a +`ServletContainerInitializer` +(link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletContainerInitializer +.html[`Log4jServletContainerInitializer`]) +that the container automatically discovers and initializes. This adds +the +link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletContextListener.html[`Log4jServletContextListener`] +and +link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletFilter.html[`Log4jServletFilter`] +to the `ServletContext`. These classes properly initialize and +deinitialize the Log4j configuration. + +For some users, automatically starting Log4j is problematic or +undesirable. You can easily disable this feature using the +`isLog4jAutoInitializationDisabled` context parameter. Simply add it to +your deployment descriptor with the value "true" to disable +auto-initialization. You _must_ define the context parameter in +`web.xml`. If you set in programmatically, it will be too late for Log4j +to detect the setting. + +[source,xml] +---- + + isLog4jAutoInitializationDisabled + true + +---- + +Once you disable auto-initialization, you must initialize Log4j as you +would a link:#Servlet-2.5[Servlet 2.5 web application]. You must do so +in a way that this initialization happens before any other application +code (such as Spring Framework startup code) executes. + +You can customize the behavior of the listener and filter using the +`log4jContextName`, `log4jConfiguration`, and/or +`isLog4jContextSelectorNamed` context parameters. Read more about this +in the link:#ContextParams[Context Parameters] section below. You _must +not_ manually configure the `Log4jServletContextListener` or +`Log4jServletFilter` in your deployment descriptor (`web.xml`) or in +another initializer or listener in a Servlet 3.0 or newer application +_unless you disable auto-initialization_ with +`isLog4jAutoInitializationDisabled`. Doing so will result in startup +errors and unspecified erroneous behavior. + +[#Servlet-2.5] +== Servlet 2.5 Web Applications + +A Servlet 2.5 web application is any `` whose `version` +attribute has a value of "2.5." The `version` attribute is the only +thing that matters; even if the web application is running in a Servlet +3.0 or newer container, it is a Servlet 2.5 web application if the +`version` attribute is "2.5." Note that Log4j 2 does not support Servlet +2.4 and older web applications. + +If you are using Log4j in a Servlet 2.5 web application, or if you have +disabled auto-initialization with the +`isLog4jAutoInitializationDisabled` context parameter, you _must_ +configure the +link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletContextListener.html[`Log4jServletContextListener`] +and +link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletFilter.html[`Log4jServletFilter`] +in the deployment descriptor or programmatically. The filter should +match all requests of any type. The listener should be the very first +listener defined in your application, and the filter should be the very +first filter defined and mapped in your application. This is easily +accomplished using the following `web.xml` code: + +[source,xml] +---- + + org.apache.logging.log4j.web.Log4jServletContextListener + + + + log4jServletFilter + org.apache.logging.log4j.web.Log4jServletFilter + + + log4jServletFilter + /* + REQUEST + FORWARD + INCLUDE + ERROR + ASYNC + +---- + +You can customize the behavior of the listener and filter using the +`log4jContextName`, `log4jConfiguration`, and/or +`isLog4jContextSelectorNamed` context parameters. Read more about this +in the link:#ContextParams[Context Parameters] section below. + +[#ContextParams] +== Context Parameters + +By default, Log4j 2 uses the `ServletContext`'s +https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html#getServletContextName()[context +name] as the `LoggerContext` name and uses the standard pattern for +locating the Log4j configuration file. There are three context +parameters that you can use to control this behavior. The first, +`isLog4jContextSelectorNamed`, specifies whether the context should be +selected using the +link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/JndiContextSelector.html[`JndiContextSelector`]. +If `isLog4jContextSelectorNamed` is not specified or is anything other +than `true`, it is assumed to be `false`. + +If `isLog4jContextSelectorNamed` is `true`, `log4jContextName` must be +specified or `display-name` must be specified in `web.xml`; otherwise, +the application will fail to start with an exception. +`log4jConfiguration` _should_ also be specified in this case, and must +be a valid URI for the configuration file; however, this parameter is +not required. + +If `isLog4jContextSelectorNamed` is not `true`, `log4jConfiguration` may +optionally be specified and must be a valid URI or path to a +configuration file or start with "classpath:" to denote a configuration +file that can be found on the classpath. Without this parameter, Log4j +will use the standard mechanisms for locating the configuration file. + +When specifying these context parameters, you must specify them in the +deployment descriptor (`web.xml`) even in a Servlet 3.0 or never +application. If you add them to the `ServletContext` within a listener, +Log4j will initialize before the context parameters are available and +they will have no effect. Here are some sample uses of these context +parameters. + +=== Set the Logging Context Name to "myApplication" + +[source,xml] +---- + + log4jContextName + myApplication + +---- + +=== Set the Configuration Path/File/URI to "/etc/myApp/myLogging.xml" + +[source,xml] +---- + + log4jConfiguration + file:///etc/myApp/myLogging.xml + +---- + +=== Use the `JndiContextSelector` + +[source,xml] +---- + + isLog4jContextSelectorNamed + true + + + log4jContextName + appWithJndiSelector + + + log4jConfiguration + file:///D:/conf/myLogging.xml + +---- + +Note that in this case you must also set the "Log4jContextSelector" +system property to +"org.apache.logging.log4j.core.selector.JndiContextSelector". + +[#WebLookup] +== Using Web Application Information During the Configuration + +You may want to use information about the web application during +configuration. For example, you could embed the web application's +context path in the name of a Rolling File Appender. See WebLookup in +link:./lookups.html#WebLookup[Lookups] for more information. + +[#JspLogging] +== JavaServer Pages Logging + +You may use Log4j 2 within JSPs just as you would within any other Java +code. Simple obtain a `Logger` and call its methods to log events. +However, this requires you to use Java code within your JSPs, and some +development teams rightly are not comfortable with doing this. If you +have a dedicated user interface development team that is not familiar +with using Java, you may even have Java code disabled in your JSPs. + +For this reason, Log4j 2 provides a JSP Tag Library that enables you to +log events without using any Java code. To read more about using this +tag library, link:../log4j-taglib/index.html[read the Log4j Tag Library +documentation.] + +NOTE: As noted above, containers often ignore certain +JARs known not to contain TLDs and do not scan them for TLD files. +Importantly, Tomcat 7 <7.0.43 ignores all JAR files named log4j*.jar, +which prevents the JSP tag library from being automatically discovered. +This does not affect Tomcat 6.x and has been fixed in Tomcat 7.0.43, +Tomcat 8, and later. In Tomcat 7 <7.0.43 you will need to change +`catalina.properties` and remove "log4j*.jar" from the `jarsToSkip` +property. You may need to do something similar on other containers if +they skip scanning Log4j JAR files. + +[#Async] +== Asynchronous Requests and Threads + +The handling of asynchronous requests is tricky, and regardless of +Servlet container version or configuration Log4j cannot handle +everything automatically. When standard requests, forwards, includes, +and error resources are processed, the `Log4jServletFilter` binds the +`LoggerContext` to the thread handling the request. After request +processing completes, the filter unbinds the `LoggerContext` from the +thread. + +Similarly, when an internal request is dispatched using a +`javax.servlet.AsyncContext`, the `Log4jServletFilter` also binds the +`LoggerContext` to the thread handling the request and unbinds it when +request processing completes. However, this only happens for requests +_dispatched_ through the `AsyncContext`. There are other asynchronous +activities that can take place other than internal dispatched requests. + +For example, after starting an `AsyncContext` you could start up a +separate thread to process the request in the background, possibly +writing the response with the `ServletOutputStream`. Filters cannot +intercept the execution of this thread. Filters also cannot intercept +threads that you start in the background during non-asynchronous +requests. This is true whether you use a brand new thread or a thread +borrowed from a thread pool. So what can you do for these special +threads? + +You may not need to do anything. If you didn't use the +`isLog4jContextSelectorNamed` context parameter, there is no need to +bind the `LoggerContext` to the thread. Log4j can safely locate the +`LoggerContext` on its own. In these cases, the filter provides only +very modest performance gains, and only when creating new `Logger` instances. +However, if you _did_ specify the `isLog4jContextSelectorNamed` context +parameter with the value "true", you will need to manually bind the +`LoggerContext` to asynchronous threads. Otherwise, Log4j will not be +able to locate it. + +Thankfully, Log4j provides a simple mechanism for binding the +`LoggerContext` to asynchronous threads in these special circumstances. +The simplest way to do this is to wrap the `Runnable` instance that is +passed to the `AsyncContext.start()` method. + +[source,java] +---- +import java.io.IOException; +import javax.servlet.AsyncContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.web.WebLoggerContextUtils; + +public class TestAsyncServlet extends HttpServlet { + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final AsyncContext asyncContext = req.startAsync(); + asyncContext.start(WebLoggerContextUtils.wrapExecutionContext(this.getServletContext(), new Runnable() { + @Override + public void run() { + final Logger logger = LogManager.getLogger(TestAsyncServlet.class); + logger.info("Hello, servlet!"); + } + })); + } + + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final AsyncContext asyncContext = req.startAsync(); + asyncContext.start(new Runnable() { + @Override + public void run() { + final Log4jWebSupport webSupport = + WebLoggerContextUtils.getWebLifeCycle(TestAsyncServlet.this.getServletContext()); + webSupport.setLoggerContext(); + // do stuff + webSupport.clearLoggerContext(); + } + }); + } +} +---- + +This can be slightly more convenient when using Java 1.8 and lambda +functions as demonstrated below. + +[source,java] +---- +import java.io.IOException; +import javax.servlet.AsyncContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.web.WebLoggerContextUtils; + +public class TestAsyncServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + final AsyncContext asyncContext = req.startAsync(); + asyncContext.start(WebLoggerContextUtils.wrapExecutionContext(this.getServletContext(), () -> { + final Logger logger = LogManager.getLogger(TestAsyncServlet.class); + logger.info("Hello, servlet!"); + })); + } +} +---- + +Alternatively, you can obtain the +link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jWebLifeCycle.html[`Log4jWebLifeCycle`] +instance from the `ServletContext` attributes, call its +`setLoggerContext` method as the very first line of code in your +asynchronous thread, and call its `clearLoggerContext` method as the +very last line of code in your asynchronous thread. The following code +demonstrates this. It uses the container thread pool to execute +asynchronous request processing, passing an anonymous inner `Runnable` +to the `start` method. + +[source,java] +---- +import java.io.IOException; +import javax.servlet.AsyncContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.web.Log4jWebLifeCycle; +import org.apache.logging.log4j.web.WebLoggerContextUtils; + +public class TestAsyncServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + final AsyncContext asyncContext = req.startAsync(); + asyncContext.start(new Runnable() { + @Override + public void run() { + final Log4jWebLifeCycle webLifeCycle = + WebLoggerContextUtils.getWebLifeCycle(TestAsyncServlet.this.getServletContext()); + webLifeCycle.setLoggerContext(); + try { + final Logger logger = LogManager.getLogger(TestAsyncServlet.class); + logger.info("Hello, servlet!"); + } finally { + webLifeCycle.clearLoggerContext(); + } + } + }); + } +} +---- + +Note that you _must_ call `clearLoggerContext` once your thread is +finished processing. Failing to do so will result in memory leaks. If +using a thread pool, it can even disrupt the logging of other web +applications in your container. For that reason, the example here shows +clearing the context in a `finally` block, which will always execute. + +== Using the Servlet Appender + +Log4j provides a Servlet Appender that uses the servlet context as the +log target. For example: + +[source,xml] +---- + + + + + + + + + + + + + + + +---- + +To avoid double logging of exceptions to the servlet context, you must +use `%ex{none}` in your `PatternLayout` as shown in the example. The +exception will be omitted from the message text but it is passed to the +servlet context as the actual `Throwable` object. diff --git a/src/site/asciidoc/performance.adoc b/src/site/asciidoc/performance.adoc new file mode 100644 index 00000000000..cb82e84ff3b --- /dev/null +++ b/src/site/asciidoc/performance.adoc @@ -0,0 +1,571 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Performance +Remko Popma ; Ralph Goers + +//// +One of the often-cited arguments against logging is its +computational cost. This is a legitimate concern as even moderately +sized applications can generate thousands of log requests. Much +effort was spent measuring and tweaking logging performance. Log4j +claims to be fast and flexible: speed first, flexibility second. +//// + +Apart from functional requirements, an important reason for selecting a +logging library is often how well it fulfills non-functional +requirements like reliability and performance. + +This page compares the performance of a number of logging frameworks +(java.util.logging "JUL", Logback, Log4j 1.2 and Log4j 2.6), and +documents some performance trade-offs for Log4j 2 functionality. + +[#benchmarks] +== Benchmarks + +Performance can mean different things to different people. Common terms +in this context are throughput and latency: _Throughput_ is a measure of +capacity and can be expressed in a single number: how many messages can +be logged in a certain period of time. _Response time latency_ is how +long it takes to log a message. This cannot be expressed in a single +number because each measurement has its own response time and we are +often most interested in the outliers: how many there were and how large +they were. + +When evaluating a logging framework's performance these may be useful +questions to ask: + +[qanda] +What is its *peak throughput*?:: +Many systems that react to external +events need to log bursts of messages followed by periods of relative +quiet. This number is the maximum throughput measured over a short +period of time and gives some idea of how well the logging library deals +with bursts. For systems that need to log a lot at a constant high rate +(for example, batch jobs) this is less likely to be a useful measure of +performance. + +What is the *maximum sustained throughput*?:: +This is the throughput +averaged over a long time. This is a useful measure of the "upper limit" +of the capacity of the logging library. It is not recommended that +reactive applications actually log at this rate since under this load +they will likely experience jitter and large response time spikes. + +What is its *response time* behaviour under various loads?:: +This is the +most important question for applications that need to react to external +events in a timely manner. Response time is the total amount of time it +takes to log a message and is the sum of the service time and wait time. +The *service time* is the time it takes to do the work to log the +message. As the workload increases, the service time often varies +little: to do X amount of work it always takes X amount of time. The +*wait time* is how long the request had to wait in a queue before being +serviced. _As the workload increases, wait time often grows to many +times the service time._ + +[[responseTimeVsServiceTime]] + +.Why Care About Response Time Latency? +**** +[width="100%",cols="50%,50%"] +|=== +a| +What is often measured and reported as _latency_ is actually _service +time_, and omits that a service time spike adds wait time for many +subsequent events. This may present results that are more optimistic +than what users experience. + +The graph on the right illustrates how much more optimistic service time +is than response time. The graph shows response time and service time +for the same system under a load of 100,000 messages per second. Out of +24 million measurements, only ~50 are more than 250 microseconds, less +than 0.001%. In a service time-only graph this would hardly be visible. +However, the depending on the load it will take a while to catch up +after a spike. + +The response time graph shows that in reality many more events are +impacted by these delays than the service time numbers alone would +suggest. + +To learn more, watch Gil Tene's eye-opening presentation +http://www.infoq.com/presentations/latency-response-time[How NOT to +measure latency]. + + +|link:images/ResponseTimeVsServiceTimeAsyncLoggers.png[image:images/ResponseTimeVsServiceTimeAsyncLoggers.png[image,width=480,height=288]] +|=== +**** + +[#loglibComparison] +== Logging Library Performance Comparison + +[#asyncLogging] +=== Asynchronous Logging - Peak Throughput Comparison + +Asynchronous logging is useful to deal with bursts of events. How this +works is that a minimum amount of work is done by the application thread +to capture all required information in a log event, and this log event +is then put on a queue for later processing by a background thread. As +long as the queue is sized large enough, the application threads should +be able to spend very little time on the logging call and return to the +business logic very quickly. + +It turns out that the choice of queue is extremely important for peak +throughput. Log4j 2's Async Loggers use a +https://lmax-exchange.github.io/disruptor/[lock-free data structure], +whereas Logback, Log4j 1.2 and Log4j 2's Asynchronous Appenders use an +ArrayBlockingQueue. With a blocking queue, multi-threaded applications +often experience lock contention when trying to enqueue the log event. + +The graph below illustrates the difference a lock-free data structure +can make to throughput in multi-threaded scenarios. _Log4j 2 scales +better with the number of threads: an application with more threads can +log more. The other logging libraries suffer from lock contention and +total throughput stays constant or drops when more threads are logging. +This means that with the other logging libraries, each individual thread +will be able to log less._ + +Bear in mind that this is _peak_ throughput: Log4j 2's Async Loggers +give better throughput up to a point, but once the queue is full, the +appender thread needs to wait until a slot becomes available in the +queue, and throughput will drop to the maximum sustained throughput of +the underlying appenders at best. + +image:images/async-throughput-comparison.png[Peak throughput comparison] + +For details, see the link:manual/async.html[Async Loggers] manual page. + +[#asyncLoggingResponseTime] +=== Asynchronous Logging Response Time + +Response time behaviour varies a lot with the workload and the number of +threads that log concurrently. The link:manual/async.html#Latency[Async +Loggers] manual page and the +link:manual/garbagefree.html#Latency[garbage-free logging] manual page +provide some graphs showing response time behaviour under various loads. + +This section shows another graph showing response time latency behaviour +under a modest total workload of 64,000 messages per second, with 4 +threads logging concurrently. At this load and on this hardware/OS/JVM +configuration, lock contention and context switches play less of a role +and the pauses are mostly caused by minor garbage collections. Garbage +collection pause duration and frequency can vary a lot: when testing the +Log4j 1.2.17 Async Appender a minor GC pause of 7 milliseconds occurred +while the Log4j 2 Async Appender test only saw a GC pause of a little +over 2 milliseconds. This does not necessarily mean that one is better +than the other. + +Generally, garbage-free async loggers had the best response time +behaviour in all configurations we tested. + +image:images/ResponseTimeAsyncLogging4Threads@16kEach.png[Response time comparison] + +The above result was obtained with the ResponseTimeTest class which can +be found in the Log4j 2 unit test source directory, running on JDK +1.8.0_45 on RHEL 6.5 (Linux 2.6.32-573.1.1.el6.x86_64) with 10-core Xeon +CPU E5-2660 v3 @2.60GHz with hyperthreading switched on (20 virtual +cores). + +[#asyncLoggingWithParams] +=== Asynchronous Logging Parameterized Messages + +Many logging libraries offer an API for logging parameterized messages. +This enables application code to look something like this: + +[source,java] +---- +logger.debug("Entry number: {} is {}", i, entry[i]); +---- + +In the above example, the fully formatted message text is not created +unless the DEBUG level is enabled for the logger. Without this API, you +would need three lines of code to accomplish the same: + +[source,java] +---- +if (logger.isDebugEnabled()) { + logger.debug("Entry number: " + i + " is " + entry[i].toString()); +} +---- + +If the DEBUG level _is_ enabled, then at some point the message needs to +be formatted. When logging asynchronously, the message parameters may be +changed by the application thread before the background thread had a +chance to log the message. This would show the wrong values in the log +file. To prevent this, Log4j 2, Log4j 1.2 and Logback format the message +text in the application thread _before_ passing off the log event to the +background thread. + +This is the safe thing to do, but the formatting has a performance cost. +The graph below compares the throughput of logging messages with +parameters using various logging libraries. These are all asynchronous +logging calls, so these numbers do not include the cost of disk I/O and +represent _peak_ throughput. + +JUL (java.util.logging) does not have a built-in asynchronous Handler. +https://docs.oracle.com/javase/8/docs/api/java/util/logging/MemoryHandler.html[`MemoryHandler`] +is the nearest thing available so we included it here. MemoryHandler +does _not_ do the safe thing of taking a snapshot of the current +parameter state (it just keeps a reference to the original parameter +objects), and as a result it is very fast when single-threaded. However, +when more application threads are logging concurrently, the cost of lock +contention outweighs this gain. + +In absolute numbers, _Log4j 2's Async Loggers perform well compared to +the other logging frameworks, but notice that the message formatting +cost increases sharply with the number of parameters. In this area, +Log4j 2 still has work to do to improve: we would like to keep this cost +more constant._ + +image:images/ParamMsgThrpt1-4T.png[image] + +The results above are for JUL (java.util.logging) 1.8.0_45, Log4j 2.6, +Log4j 1.2.17 and Logback 1.1.7, and were obtained with the +http://openjdk.java.net/projects/code-tools/jmh/[JMH] Java benchmark +harness. See the AsyncAppenderLog4j1Benchmark, +AsyncAppenderLog4j2Benchmark, AsyncAppenderLogbackBenchmark, +AsyncLoggersBenchmark and the MemoryHandlerJULBenchmark source code in +the log4j-perf module. + +[#asyncLoggingWithLocation] +=== Asynchronous Logging with Caller Location Information + +Some layouts can show the class, method and line number in the +application where the logging call was made. In Log4j 2, examples of +such layout options are HTML +link:layouts.html#HtmlLocationInfo[locationInfo], or one of the patterns +link:layouts.html#PatternClass[%C or $class], +link:layouts.html#PatternFile[%F or %file], +link:layouts.html#PatternLocation[%l or %location], +link:layouts.html#PatternLine[%L or %line], +link:layouts.html#PatternMethod[%M or %method]. In order to provide +caller location information, the logging library will take a snapshot of +the stack, and walk the stack trace to find the location information. + +The graph below shows the performance impact of capturing caller +location information when logging asynchronously from a single thread. +Our tests show that _capturing caller location has a similar impact +across all logging libraries, and slows down asynchronous logging by +about 30-100x_. + +image:images/AsyncWithLocationThrpt1T-labeled.png[image] + +The results above are for JUL (java.util.logging) 1.8.0_45, Log4j 2.6, +Log4j 1.2.17 and Logback 1.1.7, and were obtained with the +http://openjdk.java.net/projects/code-tools/jmh/[JMH] Java benchmark +harness. See the AsyncAppenderLog4j1LocationBenchmark, +AsyncAppenderLog4j2LocationBenchmark, +AsyncAppenderLogbackLocationBenchmark, AsyncLoggersLocationBenchmark and +the MemoryHandlerJULLocationBenchmark source code in the log4j-perf +module. + +[#fileLoggingComparison] +=== Synchronous File Logging - Sustained Throughput Comparison + +This section discusses the maximum sustained throughput of logging to a +file. In any system, the maximum sustained throughput is determined by +its slowest component. In the case of logging, this is the appender, +where the message formatting and disk I/O takes place. For this reason +we will look at simple _synchronous_ logging to a file, without queues +or background threads. + +The graph below compares Log4j 2.6's RandomAccessFile appender to the +respective File appenders of Log4j 1.2.17, Logback 1.1.7 and Java util +logging (JUL) on Oracle Java 1.8.0_45. ImmediateFlush was set to false +for all loggers that support this. The JUL results are for the +XMLFormatter (which in our measurements was about twice as fast as the +SimpleFormatter). + +_Log4j 2's sustained throughput drops a little when more threads are +logging simultaneously, but its fine-grained locking pays off and +throughput stays relatively high. The other logging frameworks' +throughput drops dramatically in multi-threaded applications: Log4j 1.2 +has 1/4th of its single-threaded capacity, Logback has 1/10th of its +single-threaded capacity, and JUL steadily drops from 1/4th to 1/10th of +its single-threaded throughput as more threads are added._ + +image:images/SyncThroughputLoggerComparisonLinux.png[image] + +The synchronous logging throughput results above are obtained with the +http://openjdk.java.net/projects/code-tools/jmh/[JMH] Java benchmark +harness. See the FileAppenderBenchmark source code in the log4j-perf +module. + +=== Synchronous File Logging - Response Time Comparison + +Response time for synchronous file logging varies a lot with the +workload and the number of threads. Below is a sample for a workload of +32,000 events per second, with 2 threads logging 16,000 events per +second each. + +image:images/SynchronousFileResponseTime2T32k-labeled.png[image] + +The above result was obtained with the ResponseTimeTest class which can +be found in the Log4j 2 unit test source directory, running on JDK +1.8.0_45 on RHEL 6.5 (Linux 2.6.32-573.1.1.el6.x86_64) with 10-core Xeon +CPU E5-2660 v3 @2.60GHz with hyperthreading switched on (20 virtual +cores). + +//// +TODO +=== Synchronous Socket Sustained Throughput Comparison + +=== Synchronous Syslog Sustained Throughput Comparison +//// + +[#filtering] +=== Filtering by Level + +The most basic filtering a logging framework provides is filtering by +log level. When logging is turned off entirely or just for a set of +Levels, the cost of a log request consists of a number of method +invocations plus an integer comparison. Unlike Log4j, Log4j 2 Loggers +don't "walk a hierarchy". Loggers point directly to the Logger +configuration that best matches the Logger's name. This incurs extra +overhead when the Logger is first created but reduces the overhead every +time the Logger is used. + +=== Advanced Filtering + +Both Logback and Log4j 2 support advanced filtering. Logback calls them +TurboFilters while Log4j 2 has a single Filter object. Advanced +filtering provides the capability to filter LogEvents using more than +just the Level before the events are passed to Appenders. However, this +flexibility does come with some cost. Since multi-threading can also +have an impact on the performance of advanced filtering, the chart below +shows the difference in performance of filtering based on a Marker or a +Marker's parent. + +The "Simple Marker" comparison checks to see if a Marker that has no +references to other markers matches the requested Marker. The "Parent +Marker" comparison checks to see if a Marker that does have references +to other markers matches the requested Marker. + +It appears that coarse-grained synchronization in SLF4J can impact +performance in multi-threaded scenarios. See +http://jira.qos.ch/browse/SLF4J-240[SLF4J-240]. + +image:images/MarkerFilterCostComparison.png[image] + +Log4j and Logback also support filtering on a value in the Log4j +ThreadContext vs filtering in Logback on a value in the MDC. The graph +below shows that the performance difference between Log4j 2 and Logback +is small for the ThreadContext filter. + +image:images/ThreadContextFilterCostComparison.png[image] + +The Filter comparison results above are obtained with the +http://openjdk.java.net/projects/code-tools/jmh/[JMH] Java benchmark +harness. See the MarkerFilterBenchmark and MDCFilterBenchmark in the +log4j-perf module for details on these benchmarks. + +[#tradeoffs] +== Trade-offs + +[#whichAppender] +=== Which Log4j 2 Appender to Use? + +Assuming that you selected Log4j 2 as your logging framework, next you +may be interested in learning what the performance trade-offs are for +selecting a specific Log4j 2 configuration. For example, there are three +appenders for logging to a file: the File, RandomAccessFile and +MemoryMappedFile appenders. Which one should you use? + +If performance is all you care about, the graphs below show your best +choice is either the MemoryMappedFile appender or the RandomAccessFile +appender. Some things to bear in mind: + +* MemoryMappedFile appender does not have a rolling variant yet. +* When the log file size exceeds the MemoryMappedFile's region length, +the file needs to be remapped. This can be a very expensive operation, +taking several seconds if the region is large. +* MemoryMappedFile appender creates a presized file from the beginning +and fills it up gradually. This can confuse tools like `tail`; many such +tools don't work very well with memory mapped files. +* On Windows, using a tool like `tail` on a file created by +RandomAccessFile appender can hold a lock on this file which may prevent +Log4j from opening the file again when the application is restarted. In +a development environment where you expect to restart your application +regularly while using tools like tail to view the log file contents, the +File appender may be a reasonable trade-off between performance and +flexibility. For production environments performance may have higher +priority. + +The graph below shows sustained throughput for the console and file +appenders in Log4j 2.6, and for reference also provides the 2.5 +performance. + +It turns out that the garbage-free text encoding logic in 2.6 gives +these appenders a performance boost compared to Log4j 2.5. It used to be +that the RandomAccessFile appender was significantly faster, especially +in multi-threaded scenarios, but with the 2.6 release the File appender +performance has improved and the performance difference between these +two appender is smaller. + +Another takeaway is just how much of a performance drag logging to the +console can be. Considering logging to a file and using a tool like +`tail` to watch the file change in real time. + +image:images/Log4j2AppenderThroughputComparison-linux.png[image] + +On Windows, the results are similar but the RandomAccessFile and +MemoryMappedFile appenders outperform the plain File appender in +multi-threaded scenarios. The absolute numbers are higher on Windows: we +don't know why but it looks like Windows handles lock contention better +than Linux. + +image:images/Log4j2AppenderThroughputComparison-windows.png[image] + +The Log4j 2 appender comparison results above are obtained with the +http://openjdk.java.net/projects/code-tools/jmh/[JMH] Java benchmark +harness. See the Log4j2AppenderComparisonBenchmark source code in the +log4j-perf module. + +//// +The user should be aware of the following performance issues. + +=== Logging performance when logging is turned off. + +When logging is turned off entirely or just for a set of Levels, the +cost of a log request consists of two method invocations plus an integer +comparison. On a 2.53 GHz Intel Core 2 Duo MacBook Pro calling +isDebugEnabled 10 million times produces an average result in +nanoseconds of: + +.... + Log4j: 4 + Logback: 5 + Log4j 2: 3 + +.... + +The numbers above will vary slightly from run to run so the only +conclusion that should be drawn is that all 3 frameworks perform +similarly on this task. + +However, The method invocation involves the "hidden" cost of parameter +construction. + +For example, + +.... + logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); + +.... + +incurs the cost of constructing the message parameter, i.e. converting +both integer `i` and `entry[i]` to a String, and concatenating +intermediate strings, regardless of whether the message will be logged +or not. This cost of parameter construction can be quite high and it +depends on the size of the parameters involved. A comparison run on the +same hardware as above yields: + +.... + Log4j: 188 + Logback: 183 + Log4j 2: 188 + +.... + +Again, no conclusion should be drawn regarding relative differences +between the frameworks on this task, but it should be obvious that it is +considerably more expensive than simply testing the level. + +The best approach to avoid the cost of parameter construction is to use +Log4j 2's formatting capabilities. For example, instead of the above +write: + +.... + logger.debug("Entry number: {} is {}", i, entry[i]); + +.... + +Using this approach, a comparison run again on the same hardware +produces: + +.... + Log4j: Not supported + Logback: 9 + Log4j 2: 4 + +.... + +These results show that the difference in performance between the call +to isDebugEnabled and logger.debug is barely discernible. + +In some circumstances one of the parameters to logger.debug will be a +costly method call that should be avoided if debugging is disabled. In +those cases write: + +.... + if(logger.isDebugEnabled() { + logger.debug("Entry number: " + i + " is " + entry[i].toString()); + } + +.... + +This will not incur the cost of whatever the toString() method needs to +do if debugging is disabled. On the other hand, if the logger is enabled +for the debug level, it will incur twice the cost of evaluating whether +the logger is enabled or not: once in `isDebugEnabled` and once in +`debug`. This is an insignificant overhead because evaluating a logger +takes about 1% of the time it takes to actually log. + +Certain users resort to pre-processing or compile-time techniques to +compile out all log statements. This leads to perfect performance +efficiency with respect to logging. However, since the resulting +application binary does not contain any log statements, logging cannot +be turned on for that binary. This seems to be a disproportionate price +to pay in exchange for a small performance gain. + +The performance of deciding whether to log or not to log when logging is +turned on. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Unlike Log4j, Log4j 2 Loggers don't "walk a hierarchy". Loggers point +directly to the Logger configuration that best matches the Logger's +name. This incurs extra overhead when the Logger is first created but +reduces the overhead every time the Logger is used. + +=== Actually outputting log messages + +This is the cost of formatting the log output and sending it to its +target destination. Here again, a serious effort was made to make +layouts (formatters) perform as quickly as possible. The same is true +for appenders. One of the fundamental tenets of Log4j 2 is to use +immutable objects whenever possible and to lock at the lowest +granularity possible. However, the cost of actually formatting and +delivering log events will never be insignificant. For example, the +results of writing to a simple log file using the same format using +Log4j, Logback and Log4j 2 are: + +.... + Log4j: 1651 + Logback: 1419 + Log4j 2.0: 1542 + +.... + +As with many of the other results on this page the differences between +the frameworks above should be considered insignificant. The values will +change somewhat on each execution and changing the order the frameworks +are tested or adding calls to System.gc() between the tests can cause a +variation in the reported times. However, these results show that +actually writing out the events can be at least 1000 times more +expensive than when they are disabled, so it is always recommended to +take advantage of Log4j 2's fine-grained filtering capabilities. +//// diff --git a/src/site/asciidoc/runtime-dependencies.adoc b/src/site/asciidoc/runtime-dependencies.adoc new file mode 100644 index 00000000000..c8cc8d52c9f --- /dev/null +++ b/src/site/asciidoc/runtime-dependencies.adoc @@ -0,0 +1,310 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Runtime Dependencies + +Some Log4j features depend on external libraries. This page lists the +required and optional dependencies. + +As of version 2.10.0 the Log4j API is a Java module (with a +module-info.java). Many of the other jars are automatic modules. The +characteristics of the modules are: + +[cols="3h,5m,12a"] +|=== +|Artifact Name |Module Name |Module Characteristics + +|log4j-api +|org.apache.logging.log4j +| +[cols="m,"] +!=== +!Module Directive !Notes + +2+!exports org.apache.logging.log4j + +2+!exports org.apache.logging.log4j.message + +2+!exports org.apache.logging.log4j.simple + +2+!exports org.apache.logging.log4j.spi + +2+!exports org.apache.logging.log4j.spi + +!exports org.apache.logging.log4j.util +!Some classes in this package are used by the logging implementation and should be considered private. +The module info definition may be modified in the future to export these only to the logging implementation. + +!uses org.apache.logging.log4j.spi.Provider +!Service that must be provided by the logging implementation. +!=== + +|log4j-core +|org.apache.logging.log4j.core +|Automatic Module + +|log4j-1.2-api +|org.apache.log4j +|Automatic Module + +|log4j-appserver +|org.apache.logging.log4j.appserver +|Automatic Module + +|log4j-cassandra +|org.apache.logging.log4j.cassandra +|Automatic Module + +|log4j-couchdb +|org.apache.logging.log4j.couchdb +|Automatic Module + +|log4j-docker +|org.apache.logging.log4j.docker +|Automatic Module + +|log4j-flume-ng +|org.apache.logging.log4j.flume +|Automatic Module + +|log4j-iostreams +|org.apache.logging.log4j.iostreams +|Automatic Module + +|log4j-jcl +|org.apache.logging.log4j.jcl +|Automatic Module + +|log4j-jmx-gui +|org.apache.logging.log4j.jmx.gui +|Automatic Module + +|log4j-jul +|org.apache.logging.log4j.jul +|Automatic Module + +|log4j-mongodb +|org.apache.logging.log4j.mongodb +|Automatic Module + +|log4j-osgi +|org.apache.logging.log4j.osgi +|Automatic Module. Unclear how OSGi will support Java modules. + +|log4j-slf4j-impl +|org.apache.logging.log4j.slf4j.impl +|Automatic Module. May require renaming should SLF4J ever require all implementations to have the same module name. + +|log4j-to-slf4j +|org.apache.logging.log4j.slf4j +|Automatic Module + +|log4j-taglib +|org.apache.logging.log4j.taglib +|Automatic Module + +|log4j-web +|org.apache.logging.log4j.web +|Automatic Module +|=== + +As of version 2.9.1 Log4j supports Java 9 but will still work in Java 7 +or 8. In this version log4j-api is packaged as a multi-release jar and +supports the use of the StackWalker and Process APIs. + +As of version 2.4, Log4j requires Java 7. + +Log4j version 2.3 and older require Java 6. + +log4j-api[[log4j-api]]:: +The Log4j link:log4j-api/index.html[API] module has no external +dependencies. + +log4j-core[[log4j-core]]:: +The Log4j link:log4j-core/index.html[Implementation] has several +optional link:log4j-core/dependencies.html[dependencies]. See the +link:log4j-core/dependencies.html#Dependency_Tree[Dependency Tree] for +the exact list of JAR files needed for these features. + +.Optional Dependencies per Feature in Log4j Implementation +[cols="1,3"] +|=== +|Feature |Requirements + +|XML configuration +|- + +|Properties configuration +|- + +|JSON configuration +|https://github.com/FasterXML/jackson[Jackson core and databind] + +|YAML configuration +|https://github.com/FasterXML/jackson[Jackson databind] and https://github.com/FasterXML/jackson-dataformat-yaml[YAML data format] + +|CSV Layout +|https://commons.apache.org/proper/commons-csv/[Apache Commons CSV] + +|JSON Layout +|https://github.com/FasterXML/jackson[Jackson core and databind] + +|XML Layout +|https://github.com/FasterXML/jackson[Jackson core, databind and dataformat XML] and `com.fasterxml.woodstox:woodstox-core:5.0.2` + +|YAML Layout +|https://github.com/FasterXML/jackson[Jackson core, databind] and https://github.com/FasterXML/jackson-dataformat-yaml[YAML data format] + +|Async Loggers +|http://lmax-exchange.github.io/disruptor/[LMAX Disruptor] + +|Kafka Appender +|http://kafka.apache.org/[Kafka client library]. +[NOTE] +==== +You need to use a version of the Kafka client library matching the Kafka server used. +==== + +|SMTP Appender +|an implementation of `javax.mail` + +|JMS Appender +|a JMS broker like http://activemq.apache.org/[Apache ActiveMQ] + +|Windows console color support +|http://jansi.fusesource.org/[Jansi] + +|JDBC Appender +|a JDBC driver for the database you choose to write events to + +|JPA Appender +|the Java Persistence API classes, a JPA provider implementation, and a decorated entity that the user implements. +It also requires an appropriate JDBC driver + +|NoSQL Appender with MongoDB provider +|MongoDB Java Client driver and Log4j MongoDB library + +|NoSQL Appender with Apache CouchDB provider +|LightCouch CouchDB client library and Log4j CouchDB library + +|Cassandra Appender +|Datastax Cassandra driver and Log4j Cassandra library + +|Bzip2, Deflate, Pack200, and XZ compression on rollover +|http://commons.apache.org/proper/commons-compress/[Apache Commons Compress]. +In addition, XZ requires http://tukaani.org/xz/java.html[XZ for Java]. + +|ZeroMQ Appender +|The ZeroMQ appender uses the https://github.com/zeromq/jeromq[JeroMQ] library which is licensed under the terms of the Mozilla Public License Version 2.0 (MPLv2). +For details see the file https://github.com/zeromq/jeromq/blob/master/LICENSE[LICENSE] included with the JeroMQ distribution. + +|log4j-1.2-api[[log4j-1.2-api]] +|The link:log4j-1.2-api/index.html[Log4j 1.2 Bridge] has no external +dependencies. This only requires the Log4j API. Including Log4j Core provides optional, extra functionality. + +|log4j-api-scala[[log4j-api-scala]] +|The Log4j link:manual/scala-api.html[Scala API] requires Scala runtime +library and reflection in addition to the Log4j API. + +|log4j-cassandra[[log4j-cassandra]] +|The Log4j link:log4j-cassandra/index.html[Cassandra] module depends on the +http://docs.datastax.com/en/developer/driver-matrix/doc/javaDrivers.html[Datastax +Cassandra driver]. + +|log4j-couchdb[[log4j-couchdb]] +|The Log4j link:log4j-couchdb/index.html[CouchDB] module depends on the +http://www.lightcouch.org/[LightCouch] CouchDB client library. + +|log4j-docker +|link:log4j-docker/index.html[Log4j Docker Support] requires +https://github.com/FasterXML/jackson[Jackson annotations, core, and databind]. See the +link:log4j-docker/dependencies.html#Dependency_Tree[Dependency Tree] for the exact list of +JAR files needed. + +|log4j-flume-ng[[log4j-flume-ng]] +|The link:log4j-flume-ng/index.html[Flume Appender] requires +http://flume.apache.org/[Apache Flume] and +http://avro.apache.org/[Apache Avro]. The persistent agent uses Berkeley DB. See the +link:log4j-flume-ng/dependencies.html#Dependency_Tree[Dependency Tree] +for the exact list of JAR files needed. + +|log4j-iostreams[[log4j-iostreams]] +|The Log4j link:log4j-iostreams/index.html[IO Streams] module has no +external dependencies. This only requires the Log4j API. + +|log4j-jcl[[log4j-jcl]] +|The link:log4j-jcl/index.html[Commons Logging Bridge] requires +http://commons.apache.org/proper/commons-logging/[Commons Logging]. See +the link:log4j-jcl/dependencies.html#Dependency_Tree[Dependency Tree] +for the exact list of JAR files needed. + +|log4j-jmx-gui[[log4j-jmx-gui]] +|The Log4j link:log4j-jmx-gui/index.html[JMX GUI] requires the JConsole +jar when run as a JConsole plugin. Otherwise it has no external +dependencies. See the +link:log4j-jmx-gui/dependencies.html#Dependency_Tree[Dependency Tree] +for the exact list of JAR files needed. + +|log4j-jul[[log4j-jul]] +|The Log4j 2 link:log4j-jul/index.html[Java Util Logging Adapter] has no +external dependencies. It optionally depends on the +link:log4j-api/index.html[Log4j Core] library. The only required module +is the Log4j API. + +|log4j-mongodb[[log4j-mongodb]] +|The Log4j link:log4j-mongodb/index.html[MongoDB] module depends on the +http://docs.mongodb.org/ecosystem/drivers/java/[MongoDB Java Client +driver]. + +|log4j-slf4j-impl[[log4j-slf4j-impl]] +|The Log4j 2 link:log4j-slf4j-impl/index.html[SLF4J Binding] depends on +the http://www.slf4j.org/[SLF4J] API. See the +link:log4j-slf4j-impl/dependencies.html#Dependency_Tree[Dependency Tree] +for the exact list of JAR files needed. + +WARNING: Do not use this with the link:#log4j-to-slf4j[log4j-to-slf4j] module. + +|log4j-spring-cloud-config-client[[log4j-spring-cloud-config-client]] +|link:log4j-spring-cloud-config/log4j-spring-cloud-config-client/index.html[Log4j Spring Cloud Config Client] requires +https://spring.io/projects/spring-cloud-config[Spring Cloud Config]. +https://spring.io/projects/spring-cloud-bus[Spring Cloud Bus] is required if notification of logging +configuration changes is desired. https://spring.io/projects/spring-boot[Spring Boot] is required +but applications do not have to be packaged as a Spring Boot application. +See link:log4j-spring-cloud-config/log4j-spring-cloud-config-client/dependencies.html#Dependency_Tree[Dependency Tree] +for the exact list of JAR files needed. + +|log4j-taglib[[log4j-taglib]] +|The Log4j link:log4j-taglib/index.html[Log Tag Library] requires the +http://jakarta.apache.org/taglibs/log/[Jakarta Commons Log Taglib] and +the Servlet API. See the +link:log4j-taglib/dependencies.html#Dependency_Tree[Dependency Tree] for +the exact list of JAR files needed. + +|log4j-to-slf4j[[log4j-to-slf4j]] +|The link:log4j-to-slf4j/index.html[Log4j 2 to SLF4J Adapter] requires +the http://www.slf4j.org/[SLF4J] API and an SLF4J implementation. See +the link:log4j-to-slf4j/dependencies.html#Dependency_Tree[Dependency +Tree] for the exact list of JAR files needed. + +WARNING: Do not use this with the link:#log4j-slf4j-impl[log4j-slf4j-impl] module. + +|log4j-web[[log4j-web]] +|The Log4j link:log4j-web/index.html[Web] module requires the Servlet +API. See the link:log4j-web/dependencies.html#Dependency_Tree[Dependency +Tree] for the exact list of JAR files needed. Note that this works with +the Servlet 2.5 API as well as the Servlet 3.x API. + +|=== \ No newline at end of file diff --git a/src/site/asciidoc/security.adoc b/src/site/asciidoc/security.adoc new file mode 100644 index 00000000000..ab9a90efd9c --- /dev/null +++ b/src/site/asciidoc/security.adoc @@ -0,0 +1,178 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// + += Apache Log4j Security Vulnerabilities + +This page lists all the security vulnerabilities fixed in released versions of Apache Log4j 2. +Each vulnerability is given a link:#Security_Impact_Levels[security impact rating] +by the mailto:private@logging.apache.org[Apache Logging security team]. +please note that this rating may vary from platform to platform. We also list the versions +of Apache Log4j the flaw is known to affect, and where a flaw has not been verified list +the version with a question mark. + +Note: Vulnerabilities that are not Log4j vulnerabilities but have either been incorrectly +reported against Log4j or where Log4j provides a workaround are listed at the end of this page. + +Please note that Log4j 1.x has reached end of life and is no longer supported. Vulnerabilities +reported after August 2015 against Log4j 1.x were not checked and will not be fixed. Users should +upgrade to Log4j 2 to obtain security fixes. + +Please note that binary patches are never provided. If you need to apply a source code patch, +use the building instructions for the Apache Log4j version that you are using. For +Log4j 2 this is BUILDING.md. This file can be found in the +root subdirectory of a source distributive. + +If you need help on building or configuring Log4j or other help on following the instructions +to mitigate the known vulnerabilities listed here, please send your questions to the public +Log4j Users mailing list + +If you have encountered an unlisted security vulnerability or other unexpected behaviour +that has security impact, or if the descriptions here are incomplete, please report them +privately to the mailto:private@logging.apache.org[Log4j Security Team]. Thank you. + +[#log4j-2-15-0] +=== Fixed in Log4j 2.15.0 + +https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228[CVE-2021-4422]: Apache Log4j2 JNDI +features do not protect against attacker controlled LDAP and other JNDI related endpoints. + +Severity: Critical + +Base CVSS Score: 10.0 CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H + +Versions Affected: all versions from 2.0-beta9 to 2.14.1 + +Descripton: Apache Log4j2 <= 2.14.1 JNDI features used in configuration, log messages, and parameters do not +protect against attacker controlled LDAP and other JNDI related endpoints. An attacker who can control log +messages or log message parameters can execute arbitrary code loaded from LDAP servers when message lookup +substitution is enabled. From log4j 2.15.0, this behavior has been disabled by default. + +Mitigation: In releases >= 2.10, this behavior can be mitigated by setting either the system property +`log4j2.formatMsgNoLookups` or the environment variable `LOG4J_FORMAT_MSG_NO_LOOKUPS` to `true`. +For releases from 2.7 through 2.14.1 all PatternLayout patterns can be modified to specify the message converter as +`%m{nolookups}` instead of just `%m`. +For releases from 2.0-beta9 to 2.7, the only mitigation is to remove the `JndiLookup` class from the classpath: +`zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class`. + +Credit: This issue was discovered by Chen Zhaojun of Alibaba Cloud Security Team. + +References: https://issues.apache.org/jira/browse/LOG4J2-3201[https://issues.apache.org/jira/browse/LOG4J2-3201] +and https://issues.apache.org/jira/browse/LOG4J2-3198[https://issues.apache.org/jira/browse/LOG4J2-3198]. + +[#log4j-2-13-2] +=== Fixed in Log4j 2.13.2 + +https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-9488[CVE-2020-9488]: +Improper validation of certificate with host mismatch in Apache Log4j SMTP appender. + +Severity: Low + +CVSS Base Score: 3.7 (Low) CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N + +Versions Affected: all versions from 2.0-alpha1 to 2.13.1 + +Descripton: Improper validation of certificate with host mismatch in +Log4j2 SMTP appender. This could allow an SMTPS connection to be +intercepted by a man-in-the-middle attack which could leak any log +messages sent through that appender. + +The reported issue was caused by an error in SslConfiguration. Any element using SslConfiguration +in the Log4j Configuration is also affected by this issue. This includes HttpAppender, +SocketAppender, and SyslogAppender. Usages of SslConfiguration that are configured via system +properties are not affected. + +Mitigation: Users should upgrade to Apache Log4j 2.13.2 which fixed +this issue in LOG4J2-2819 by making SSL settings configurable for +SMTPS mail sessions. As a workaround for previous releases, users can +set the `mail.smtp.ssl.checkserveridentity` system property to `true` +to enable SMTPS hostname verification for all SMTPS mail sessions. + +Credit: This issues was discovered by Peter Stöckli. + +References: https://issues.apache.org/jira/browse/LOG4J2-2819 + +[#log4j-2-8-2] +=== Fixed in Log4j 2.8.2 + +https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5645[CVE-2017-5645]: Apache Log4j socket +receiver deserialization vulnerability. + +Severity: Moderate + +CVSS Base Score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P) + +Versions Affected: all versions from 2.0-alpha1 to 2.8.1 + +Description: When using the TCP socket server or UDP socket server to +receive serialized log events from another application, a specially crafted +binary payload can be sent that, when deserialized, can execute arbitrary +code. + +Mitigation: Java 7+ users should migrate to version 2.8.2 or avoid using +the socket server classes. Java 6 users should avoid using the TCP or UDP +socket server classes, or they can manually backport the security fix from +2.8.2: https://github.com/apache/logging-log4j2/commit/5dcc192 + +Credit: This issue was discovered by Marcio Almeida de Macedo of Red Team +at Telstra + +References: + +[#impact-levels] +== Summary of security impact levels for Apache Log4j +The Apache Log4j Security Team rates the impact of each security flaw that affects Log4j. +We've chosen a rating scale quite similar to those used by other major vendors in order to +be consistent. Basically the goal of the rating system is to answer the question "How worried +should I be about this vulnerability?". + +Note that the rating chosen for each flaw is the worst possible case across all architectures. +To determine the exact impact of a particular vulnerability on your own systems you will still +need to read the security advisories to find out more about the flaw. + +We use the following descriptions to decide on the impact rating to give each vulnerability: + +[#impact-levels-critical] +=== Critical +A vulnerability rated with a Critical impact is one which could potentially be exploited by +a remote attacker to get Log4j to execute arbitrary code (either as the user the server is +running as, or root). These are the sorts of vulnerabilities that could be exploited automatically +by worms. + +[#impact-levels-important] +=== Important +A vulnerability rated as Important impact is one which could result in the compromise of data +or availability of the server. For Log4j this includes issues that allow an easy remote denial +of service (something that is out of proportion to the attack or with a lasting consequence), +access to arbitrary files outside of the context root, or access to files that should be otherwise +prevented by limits or authentication. + +[#impact-levels-moderate] +=== Moderate +A vulnerability is likely to be rated as Moderate if there is significant mitigation to make the +issue less of an impact. This might be because the flaw does not affect likely configurations, or +it is a configuration that isn't widely used. + +[#impact-levels-low] +=== Low +All other security flaws are classed as a Low impact. This rating is used for issues that are believed +to be extremely hard to exploit, or where an exploit gives minimal consequences. + +[#cve-creation] +== CVE creation process + +Found security vulnerabilities are subject to voting (by means of https://logging.apache.org/guidelines.html[_lazy approval_], preferably) before creating a CVE and populating its associated content. +This procedure involves only the creation of CVEs and blocks neither (vulnerability) fixes, nor releases. diff --git a/src/site/asciidoc/support.adoc b/src/site/asciidoc/support.adoc new file mode 100644 index 00000000000..0ec797ad655 --- /dev/null +++ b/src/site/asciidoc/support.adoc @@ -0,0 +1,50 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// + += Log4j Support + +The Apache Software Foundation does not employ individuals to develop and support any of its projects. The +individuals who contribute to Apache projects do it either as part of specific tasks assigned to them by their +employer, on their own initiative to benefit their employer, or on their own free time. While some projects +at the ASF have employees who are specifically paid to work on the project, none of the committers to any +of the Logging Services projects are directly paid to work on them. + +The Log4j project uses https://issues.apache.org/jira/projects/LOG4J2[Jira] as its issue tracking system. +Issues get resolved in one of the following ways: + +1. The reporter or another interested party provide a patch attached to the Jira issue, or (preferred) a pull request +is provided at the https://github.com/apache/logging-log4j2[Log4j GitHub site]. +2. A committer is interested in the issue and decides to work on it. +3. The reporter or another interested party sponsors one or more of the people listed below to encourage them to +work on the issue. + +== GitHub Sponsorship + +The following are Log4j committers who accept sponsorship through GitHub. GitHub sponsorship can be used simply as +a way to say thank you for the work that has been done or as a way to encourage specific issues to be worked on. In either +case, while the Apache Logging Services Project thanks you for your support we cannot be responsible for any +promises and/or contributions made by an individual committer as individual commits must be reviewed and accepted +by the project team. While the Logging Services team has accepted the individuals listed below as committers to the +projects, we cannot recommend any particular individual for any specific issue. + +==== Committers who accept GitHub Sponsorship + +* [Gary Gregory](https://github.com/garydgregory) +* [Matt Sicker](https://github.com/jvz) +* [Ralph Goers](https://github.com/rgoers) +* [Volkan Yazıcı](https://github.com/vy) +* [Carter Kozak](https://github.com/carterkozak) diff --git a/src/site/asciidoc/thanks.adoc b/src/site/asciidoc/thanks.adoc new file mode 100644 index 00000000000..64620accccd --- /dev/null +++ b/src/site/asciidoc/thanks.adoc @@ -0,0 +1,52 @@ +//// + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +//// += Project Thanks + +Log4j 2 is a successful project because of the large and diverse +community that contributes to it. + +There are a handful of tools that developers in the community use; some +are open-source and some are commercial. We'd like to extend a special +thanks to these communities/companies: + +|=== +|Who |What + +|image:images/YourKitLogo.png["YourKit", link="https://www.yourkit.com/features/"] +|YourKit supports the Log4j 2 project with its full-featured Java Profiler. +YourKit, LLC is the creator of innovative and intelligent tools for profiling Java and .NET applications. +See https://www.yourkit.com/features/[YourKit's leading software products]. + +|image:images/IntelliJ-IDEA-logo.png["IntelliJ IDEA", link="https://www.jetbrains.com/idea/"] +|JetBrains supports the Log4j 2 project with https://www.jetbrains.com/idea/[IntelliJ IDEA]. + +|image:https://www.jclarity.com/wp-content/uploads/2015/02/JClarity_logo_tagline3.png["jClarity", link="https://www.jclarity.com/"] +|jClarity supports the Log4j 2 project with its https://www.jclarity.com/censum/[Censum] GC log parsing tool. + +|image:https://www.apache.org/images/feather-small.gif["Apache Feather Logo", link="https://www.apache.org/foundation/thanks.html"] +|All of the Platinum/Gold/Silver/Bronze sponsors of the ASF. +|=== + +== Individual Donations to the Apache Software Foundation + +The Apache Software Foundation receives many smaller donations from +individuals via our PayPal and Amazon Payments services (details +https://www.apache.org/foundation/contributing.html[here]). + +We would like to thank all of our individual donors for their support of +our work, and for their willingness to contribute with only this as +recognition for their generosity. diff --git a/src/site/markdown/articles.md b/src/site/markdown/articles.md deleted file mode 100644 index fb04a0384f3..00000000000 --- a/src/site/markdown/articles.md +++ /dev/null @@ -1,202 +0,0 @@ - - - -# Articles and Tutorials - -A collection of external articles and tutorials about Log4j 2. The [Log4j 2 manual](manual/index.html) is the ultimate -guide for up-to-date and detailed information on how to configure and use Log4j 2. - -## Chinese - -* [Log4j 2.6免垃圾收集](http://www.infoq.com/cn/news/2016/06/log4j-garbage-free) -(June 12, 2016) -* [详解log4j2(上) - 从基础到实战](http://blog.csdn.net/autfish/article/details/51203709) -(April 20, 2016) -* [Log4j2使用笔记](http://www.jianshu.com/p/7aec512a003c) -(November 3, 2015) -* [过渡到全新Log4j:Log4j项目管理委员会访谈](http://www.infoq.com/cn/news/2015/09/interview-log4j-pmc) -(September 14, 2015) -* [Log4j版本1生命周期终结](http://www.infoq.com/cn/news/2015/09/log4j-version-1-reaches-eol) -(September 3, 2015) -* [Apache Log4j 2.0值得升级吗](http://www.infoq.com/cn/news/2014/08/apache-log4j2) -(August 5, 2014) -* [Log4j2架构分析与实战](http://www.importnew.com/19467.html) -(January 15, 2013) -* [log4j2 使用详解](http://blog.csdn.net/lrenjun/article/details/8178875) -(November 13, 2012) -* [Log4j2配置文件详解](https://my.oschina.net/xianggao/blog/523401) -(Undated) - -## English - -* [Log4j 2 Best Practices example](https://examples.javacodegeeks.com/enterprise-java/log4j/log4j-2-best-practices-example/) -(November 14, 2017) -* [Intro to Log4j2 - Appenders, Layouts and Filters](http://www.baeldung.com/log4j2-appenders-layouts-filters) -(November 14, 2017) -* [Logging Fundamentals](http://musigma.org/logging/2017/11/06/logging.html) -(November 6, 2017) -* [Allocation free logging with Log4j2](http://www.rationaljava.com/2017/10/allocation-free-logging-with-log4j2.html) -(October 27, 2017) -* [Benchmarking Java logging frameworks](https://www.loggly.com/blog/benchmarking-java-logging-frameworks/) -(October 25, 2017) -* [Log4j 2 and Lambda Expressions](http://www.baeldung.com/log4j-2-lazy-logging) -(August 22, 2017) -* [How to use Log4j 2 with Spring Boot](https://www.callicoder.com/spring-boot-log4j-2-example/) -(August 11, 2017) -* [Log4j 2 - RollingFileAppender example](https://www.boraji.com/log4j-2-rollingfileappender-example) -(July 26, 2017) -* [How Log4J2 Works: 10 Ways to Get the Most Out Of It](https://stackify.com/log4j2-java/) -(June 14, 2017) -* [Intro to Log4j2 – Appenders, Layouts and Filters](http://www.baeldung.com/log4j2-appenders-layouts-filters) -(February 28, 2017) -* [Getting Your Own Log4j2 File for Mule via Spring](https://dzone.com/articles/getting-own-log4j2-file-for-mule-via-spring) -(December 29, 2016) -* [Understanding and working with Log4j2 for logging in Selenium framework (Part B)](https://www.youtube.com/watch?v=-XNvCNHjIKw) -(December 20, 2016) -* [Understanding and working with Log4j2 for logging in Selenium framework (Part A)](https://www.youtube.com/watch?v=RWZ0gsfkkc4) -(December 18, 2016) -* [Loading a Log4j Configuration for a specific EJB](https://garygregory.wordpress.com/2016/11/27/loading-a-log4j-configuration-for-a-specific-ejb/) -(November 27, 2016) -* [Log4j2 Logging: A Primer](https://medium.com/@anishekagarwal/log4j2-logging-a-primer-f10ed18e9de6#.ojlde7jib) -(October 15, 2016) -* [Log4j2 Example Tutorial – Configuration, Levels, Appenders](http://www.journaldev.com/7128/log4j2-example-tutorial-configuration-levels-appenders) -(July 6, 2016) -* [Log4j2 HTMLLayout Configuration Example](http://howtodoinjava.com/log4j2/log4j2-htmllayout-configuration-example/) -(June 27, 2016) -* [Log4j2 java.util.logging (JUL) Adapter Example](http://javaevangelist.blogspot.jp/2016/06/log4j2-javautillogging-jul-adapter.html) -(June 24, 2016) -* [Matt Sicker - Asynchronous Logging in Log4j 2.6 (CJUG Lightning Talk)](https://vimeo.com/169542136) -(June 2, 2016) - * Errata: "ThresholdFilter" should be "BurstFilter" -* [Log4J 2 Configuration: Using the Properties File](https://dzone.com/articles/log4j-2-configuration-using-properties-file) -(May 18, 2016) -* [Using Log4j 2 with Sprint Boot](https://springframework.guru/using-log4j-2-spring-boot/) -(April 7, 2016) -* [Log4j 2.6 Goes Garbage-Free](https://www.infoq.com/news/2016/05/log4j-garbage-free) -(May 30, 2016) -* [What's New in Log4j 2.6](http://musigma.org/java/log4j/2016/05/29/log4j-2.6.html) -(May 29, 2016) -* [Asynchronous Logging With Log4j 2](https://springframework.guru/asynchronous-logging-with-log4j-2/) -(March 31, 2016) -* [Log4J 2 Configuration: Using YAML](https://springframework.guru/log4j-2-configuration-using-yaml/) -(March 26, 2016) -* [Log4J 2 Configuration: Using JSON](https://springframework.guru/log4j-2-configuration-using-json/) -(March 23, 2016) -* [Getting Logstash 2.x Ready for Log4j2](https://qbox.io/blog/getting-logstash-2x-ready-for-log4j2) -(March 10, 2016) -* [Log4J 2 Configuration: Using XML](https://springframework.guru/log4j-2-configuration-using-xml/) -(March 10, 2016) -* [Log4J 2 Configuration: Using Properties File](https://springframework.guru/log4j-2-configuration-using-properties-file/) -(March 8, 2016) -* [Introducing Log4j 2 – Enterprise Class Logging](https://springframework.guru/introducing-log4j-enterprise-class-logging/) -(February 8, 2016) -* [Better Performing Non-Logging Logger Calls in Log4j2](https://www.javacodegeeks.com/2015/10/better-performing-non-logging-logger-calls-in-log4j2.html) -(October 20, 2015) -* [Better Performing Non-Logging Logger Calls in Log4j2](http://marxsoftware.blogspot.com/2015/10/log4j2-non-logging-performance.html) -(October 15, 2015) -* [Nancy M Schorr - Log4j2 with Java and Maven for Logging](https://www.youtube.com/watch?v=Yv0n-4AsOiI) -(October 14, 2015) -* [Easy and Consistent Log4j2 Logger Naming](https://www.javacodegeeks.com/2015/10/easy-and-consistent-log4j2-logger-naming.html) -(October 10, 2015) -* [Writing clean logging code using Java 8 lambdas](https://garygregory.wordpress.com/2015/09/16/a-gentle-introduction-to-the-log4j-api-and-lambda-basics/) -(September 16, 2015) -* [The Art of Test Driven Development: Understanding Logging](https://garygregory.wordpress.com/2015/09/10/the-art-of-test-driven-development-understanding-logging/) -(September 10, 2015) -* [The Art of Test Driven Development: Per-Test Logging](https://garygregory.wordpress.com/2015/09/08/the-art-of-test-driven-development-per-test-logging/) -(September 8, 2015) -* [The Transition to a New Log4j: a Q&A with Log4j's Project Management Committee](http://www.infoq.com/news/2015/09/interview-log4j-pmc) -(September 8, 2015) -* [Log4j Version 1 Reaches End of Life](http://www.infoq.com/news/2015/08/log4j-version-1-reaches-eol) -(August 26, 2015) -* [Apache Logging Services Project Announces Log4j 1 End-Of-Life; Recommends Upgrade to Log4j 2](https://blogs.apache.org/foundation/entry/apache_logging_services_project_announces) -(August 6, 2015) -* [Per request debugging with Log4j 2 filters](https://www.innoq.com/en/blog/per-request-debugging-with-log4j2/) -(May 8, 2015) -* [Log4j 2 configuration depending on environment](https://blog.oio.de/2015/04/27/log4j-2-configuration-depending-environment/) -(April 27, 2015) -* [Ramesh Rajaram - Log4j Key Features](https://www.youtube.com/watch?v=EWftNoRhS_M) -(April 10, 2015) -* [Apache Log4j 2 Tutorial – Configuration, Levels, Appenders, Lookup, Layouts and Filters Example](http://www.journaldev.com/7128/apache-log4j-2-tutorial-configuration-levels-appenders-lookup-layouts-and-filters-example) -(March 16, 2015) -* [Disrupting your Asynchronous Loggers](http://blogs.mulesoft.com/dev/mule-dev/mule-3-6-asynchronous-logging/) -(March 5, 2015) -* [Extending Log4j2 - Creating Custom Log4j2 Plugins](http://andrew-flower.com/blog/Create_Custom_Log4j_Plugins) -(February 20, 2015) -* [Log4j2 - a crash course...](http://andrew-flower.com/blog/Basic_Log4j2_Configuration) -(February 10, 2015) -* [Log4j2 with log4j2.xml Configuration Example](http://memorynotfound.com/log4j2-with-log4j2-xml-configuration-example/) -(February 10, 2015) -* [Logging From Your Java Application Using Log4j2](https://blog.logentries.com/2015/02/logging-from-your-java-application-using-log4j2/?utm_content=11878557&utm_medium=social&utm_source=facebook) -(February 5, 2015) -* [Asynchronous Logging in Mule 3.6](http://blogs.mulesoft.com/dev/mule-dev/mule-3-6-asynchronous-logging/) -(January 20, 2015) -* [Apache Log4j 2.0 - Worth the Upgrade?](http://www.infoq.com/news/2014/07/apache-log4j2) -(July 31, 2014) -* [log4j2 xml configuration example](http://mycuteblog.com/log4j2-xml-configuration-example/) -(July 26, 2014) -* [Log4j 2 in Production – Making it Fly](http://tech.finn.no/2014/07/01/log4j2-in-production-making-it-fly/) -(July 2, 2014) -* [Matt Sicker - Introducing Log4j 2.0](https://www.youtube.com/watch?v=ZzVSs_JEhgs) -(May 6, 2014) -* [Nicholas Williams - Log4j 2 in Web Applications: A Deeper Look at Effective Java EE Logging](https://www.youtube.com/watch?v=HB0r5DuxGPI) -(May 6, 2014) -* [Log4j 2: Performance Close to Insane](http://www.grobmeier.de/log4j-2-performance-close-to-insane-20072013.html) -(July 20, 2013) -* [Hacker News: Asynchronous Loggers for Low-Latency Logging](https://news.ycombinator.com/item?id=5612035) -(April 26, 2013) -* [The New Log4j 2.0](http://www.grobmeier.de/the-new-log4j-2-0-05122012.html) -(December 5, 2012) - -## German - -* [Apache Log4j 2.6 läuft nun auch ohne Müll](https://jaxenter.de/apache-log4j-2-6-laeuft-nun-auch-ohne-muell-41098) -(May 31, 2016) -* [Logging konsolidieren und Performance gewinnen](https://www.innoq.com/en/articles/2015/01/logging-konsolidieren-log4j2/) -(January 23, 2015) - -## Japanese - -* [中年プログラマーの息抜き](http://tm-b.hatenablog.com/entry/2016/08/18/200715) -(August 18, 2016) -* [【log4j2】ThreadContextを利用してすべてのログに追加情報を出力する](http://minor.hatenablog.com/entry/2016/05/22/193556) -(May 22, 2016) -* [Log4j 2でログ出力をテストするサンプルソース](http://qiita.com/kazurof/items/abbd42f11bfc125f3190) -(February 22, 2016) -* [新Log4jへの移行: Log4jプロジェクト管理グループとのQ&A](https://www.infoq.com/jp/news/2015/09/interview-log4j-pmc) -(September 27, 2015) -* [Log4jバージョン1のサポートが終了](https://www.infoq.com/jp/news/2015/09/log4j-version-1-reaches-eol) -(September 23, 2015) -* [log4j2の設定ファイル(XML)](http://qiita.com/pica/items/f801c74848f748f76b58) -(July 27, 2015) -* [Apache log4j2によるロギング機能の基本サンプル](http://japanengineers.seesaa.net/article/412195201.html) -(January 12, 2015) -* [Log4j2の使い方めも](http://yamashiro0110.hatenadiary.jp/entry/2014/08/24/093336) -(August 24, 2014) -* [Apache Log4j 2.0 - アップグレードする価値はあるか?](https://www.infoq.com/jp/news/2014/08/apache-log4j2) -(August 17, 2014) -* [Log4j2を試してみる](http://d.hatena.ne.jp/Kazuhira/20140628/1403959552) -(June 28, 2014) -* [log4j2にログを集める](http://nabedge.blogspot.jp/2013/10/log4j2.html) -(October 26, 2013) - -## Korean - -* [Log4j 2 설정하기](http://dveamer.github.io/java/Log4j2.html) -(January 24, 2016) -* [Log4j 2 환경설정 [설정 파일 사용 시]](http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte3:fdl:%EC%84%A4%EC%A0%95_%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94_%EB%B0%A9%EB%B2%95) -(May 14, 2014) diff --git a/src/site/markdown/build.md b/src/site/markdown/build.md deleted file mode 100644 index 43e5c3559ec..00000000000 --- a/src/site/markdown/build.md +++ /dev/null @@ -1,69 +0,0 @@ - - - -# Building and Installing Log4j - -*The information below is for developers who want to modify Log4j or contribute -to Log4j. If your goal is to add logging to your application you don't need to -build from the source code, you can [download](download.html) the pre-built -binaries instead.* - -Log4j 2 is hosted in the Apache Software Foundation's Git repository. Details on obtaining the -most current source code can be found at -[Log4j Source Repository](source-repository.html). The source from the latest release may be -obtained by downloading it using the instructions at [Log4j Downloads](download.html). - -Log4j 2.x uses Maven 3 as its build tool. Log4j 2.x uses the Java 9 compiler in addition to -the Java version installed in the path. This is accomplished by using Maven's toolchains support. -Log4j 2 provides sample toolchains XML files in the root folder. This may be used by -modifying it and installing the file as toolchains.xml in the .m2 folder or by using the -following when invoking Maven. - -``` -[Macintosh] -t ./toolchains-sample-mac.xml -[Windows] -t ./toolchains-sample-win.xml -[Linux] -t ./toolchains-sample-linux.xml -``` - -To build and install Log4j in your local Maven cache, from the parent project directory, and -using Java 7 or 8, run: `mvn install` - -Note that if your `/etc/hosts` file does not include an entry for your computer's hostname, then -many unit tests may execute slowly due to DNS lookups to translate your hostname to an IP address in -InetAddress::getLocalHost. -To remedy this, you can execute the following: - -``` -printf '127.0.0.1 %s\n::1 %s\n' `hostname` `hostname` | sudo tee -a /etc/hosts -``` - -Then to build the full site, you must use a local staging directory: - -``` -mvn site -[Windows] mvn site:stage-deploy -DstagingSiteURL=file:///%HOME%/log4j -[Unix] mvn site:stage-deploy -DstagingSiteURL=file:///$HOME/log4j -``` - -To rebuild only what's changed and execute the tests, run: `mvn test` - -To rebuild from scratch, add "clean", for example: `mvn clean test` - -## Releasing Log4j - -Please see the wiki [Log4j2ReleaseGuide](https://wiki.apache.org/logging/Log4j2ReleaseGuide). diff --git a/src/site/markdown/changelog.md b/src/site/markdown/changelog.md deleted file mode 100644 index d021ed19654..00000000000 --- a/src/site/markdown/changelog.md +++ /dev/null @@ -1,34 +0,0 @@ - - - -# Release Changelog - -[JIRA-generated changelog](jira-report.html) - -[Manual change log](changes-report.html) - -Apache Log4j 2 is not compatible with the previous versions. Please have the following in mind when upgrading to -Log4j 2 in your project: - -* Log4j 2.4 and greater requires Java 7, versions 2.0-alpha1 to 2.3 required Java 6. -* The XML configuration has been simplified and is not compatible with Log4j 1.x. -* Configuration via property files is supported from version 2.4, but is not compatible with Log4j 1.x. -* Configuration via JSON or YAML is supported, but these formats require -[additional runtime dependencies](runtime-dependencies.html). -* Although Log4j 2 is not directly compatible with Log4j 1.x a compatibility bridge has been provided to reduce the -need to make coding changes. diff --git a/src/site/markdown/download.md.vm b/src/site/markdown/download.md.vm deleted file mode 100644 index 3ed0fe67526..00000000000 --- a/src/site/markdown/download.md.vm +++ /dev/null @@ -1,101 +0,0 @@ - - -#set($h1='#') -#set($h2='##') - -$h1 Download Apache Log4j 2 - -Apache Log4j 2 is distributed under the [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). - -The link in the Mirrors column should display a list of available mirrors with a default selection based on your -inferred location. If you do not see that page, try a different browser. The checksum and signature are links to -the originals on the main distribution server. - -| Distribution | Mirrors | Checksum | Signature | -| ------------------------------ | -------------------------------------------------------- | --------------------------------------------------------------- | --------------------------------------------------------------- | -| Apache Log4j 2 binary (tar.gz) | [apache-log4j-${Log4jReleaseVersion}-bin.tar.gz][bintar] | [apache-log4j-${Log4jReleaseVersion}-bin.tar.gz.md5][bintarmd5] | [apache-log4j-${Log4jReleaseVersion}-bin.tar.gz.asc][bintarasc] | -| Apache Log4j 2 binary (zip) | [apache-log4j-${Log4jReleaseVersion}-bin.zip][binzip] | [apache-log4j-${Log4jReleaseVersion}-bin.zip.md5][binzipmd5] | [apache-log4j-${Log4jReleaseVersion}-bin.zip.asc][binzipasc] | -| Apache Log4j 2 source (tar.gz) | [apache-log4j-${Log4jReleaseVersion}-src.tar.gz][srctar] | [apache-log4j-${Log4jReleaseVersion}-src.tar.gz.md5][srctarmd5] | [apache-log4j-${Log4jReleaseVersion}-src.tar.gz.asc][srctarasc] | -| Apache Log4j 2 source (zip) | [apache-log4j-${Log4jReleaseVersion}-src.zip][srczip] | [apache-log4j-${Log4jReleaseVersion}-src.zip.md5][srczipmd5] | [apache-log4j-${Log4jReleaseVersion}-src.zip.asc][srczipasc] | - -[bintar]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.tar.gz -[bintarmd5]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.tar.gz.md5 -[bintarasc]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.tar.gz.asc -[binzip]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.zip -[binzipmd5]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.zip.md5 -[binzipasc]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.zip.asc -[srctar]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.tar.gz -[srctarmd5]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.tar.gz.md5 -[srctarasc]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.tar.gz.asc -[srczip]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.zip -[srczipmd5]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.zip.md5 -[srczipasc]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.zip.asc - -It is essential that you verify the integrity of the downloaded files using the PGP or MD5 signatures. -Please read [Verifying Apache HTTP Server Releases](https://httpd.apache.org/dev/verification.html) for more -information on why you should verify our releases. - -The PGP signatures can be verified using PGP or GPG. First download the -[KEYS](https://www.apache.org/dist/logging/KEYS) as well as the asc signature file for the relevant distribution. -Make sure you get these files from the [main distribution directory](https://www.apache.org/dist/logging/), rather -than from a mirror. Then verify the signatures using - - gpg --import KEYS - gpg --verify apache-log4j-${Log4jReleaseVersion}-bin.tar.gz.asc - -Apache Log4j ${Log4jReleaseVersion} is signed by ${Log4jReleaseManager} (${Log4jReleaseKey}) - -Alternatively, you can verify the MD5 signature on the files. A unix program called md5 or md5sum is included -in many unix distributions. - -$h2 Previous Releases - -Log4j 2.3 was the last 2.x release to support Java 6. Those artifacts can be found at: - -| Distribution | Mirrors | Checksum | Signature | -| ------------------------------ | ------------------------------------------ | ------------------------------------------------- | ------------------------------------------------- | -| Apache Log4j 2 binary (tar.gz) | [apache-log4j-2.3-bin.tar.gz][bintar-jdk6] | [apache-log4j-2.3-bin.tar.gz.md5][bintarmd5-jdk6] | [apache-log4j-2.3-bin.tar.gz.asc][bintarasc-jdk6] | -| Apache Log4j 2 binary (zip) | [apache-log4j-2.3-bin.zip][binzip-jdk6] | [apache-log4j-2.3-bin.zip.md5][binzipmd5-jdk6] | [apache-log4j-2.3-bin.zip.asc][binzipasc-jdk6] | -| Apache Log4j 2 source (tar.gz) | [apache-log4j-2.3-src.tar.gz][srctar-jdk6] | [apache-log4j-2.3-src.tar.gz.md5][srctarmd5-jdk6] | [apache-log4j-2.3-src.tar.gz.asc][srctarasc-jdk6] | -| Apache Log4j 2 source (zip) | [apache-log4j-2.3-src.zip][srczip-jdk6] | [apache-log4j-2.3-src.zip.md5][srczipmd5-jdk6] | [apache-log4j-2.3-src.zip.asc][srczipasc-jdk6] | - -[bintar-jdk6]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.tar.gz -[bintarmd5-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.tar.gz.md5 -[bintarasc-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.tar.gz.asc -[binzip-jdk6]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.zip -[binzipmd5-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.zip.md5 -[binzipasc-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-bin.zip.asc -[srctar-jdk6]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.tar.gz -[srctarmd5-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.tar.gz.md5 -[srctarasc-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.tar.gz.asc -[srczip-jdk6]: https://www.apache.org/dyn/closer.lua/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.zip -[srczipmd5-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.zip.md5 -[srczipasc-jdk6]: https://www.apache.org/dist/logging/log4j/${Log4jReleaseVersion}/apache-log4j-${Log4jReleaseVersion}-src.zip.asc - -All previous releases of Apache log4j can be found in the -[archive repository](https://archive.apache.org/dist/logging/log4j/). - -$h2 Using Log4j on your classpath - -To use Log4j 2 in your application make sure that both the API and Core jars are in the application's classpath. Add -the dependencies listed below to your classpath. - - log4j-api-${Log4jReleaseVersion}.jar - log4j-core-${Log4jReleaseVersion}.jar - -You can do this from the command line or a manifest file. diff --git a/src/site/markdown/faq.md.vm b/src/site/markdown/faq.md.vm deleted file mode 100644 index 132e6fe7d0d..00000000000 --- a/src/site/markdown/faq.md.vm +++ /dev/null @@ -1,480 +0,0 @@ - - -#set($dollar = '$') -#set($h1='#') -#set($h4='####') - -$h1 Frequently Asked Questions - -* [I'm seeing this error "Unable to locate a logging implementation, using SimpleLogger". What is wrong?](#missing_core) -* [Which JAR files do I need?](#which_jars) -* [How do I exclude conflicting dependencies?](#exclusions) -* [How do I specify the configuration file location?](#config_location) -* [How do I configure log4j2 in code without a configuration file?](#config_from_code) -* [How do I reconfigure log4j2 in code with a specific configuration file?](#reconfig_from_code) -* [How do I shut down log4j2 in code?](#shutdown) -* [How do I send log messages with different levels to different appenders?](#config_sep_appender_level) -* [How do I debug my configuration?](#troubleshooting) -* [How do I dynamically write to separate log files?](#separate_log_files) -* [How do I set a logger's level programmatically?](#reconfig_level_from_code) -* [How do I set my log archive retention policy? How do I delete old log archives?](#retention) -* [What are the trade-offs of using the Log4j 2 API versus the SLF4J API?](#api-tradeoffs) -* [Is Log4j 2 still garbage-free when I use the SLF4J API?](#gc-free-slf4j) -* [How do I log my domain object without creating garbage?](#gc-free-domain-object) -* [How do I create a custom logger wrapper that shows the correct class, method and line number?](#logger-wrapper) - - -$h4 I'm seeing this error "Unable to locate a logging implementation, using SimpleLogger". What is wrong? - -You have the log4j-api-2.x jar file in your classpath but you still need to add the log4j-core-2.x jar to the -classpath. (Also, it looks like you are using an old version of Log4j 2. You may want to upgrade.) - - -$h4 Which JAR files do I need? - -You need at least the log4j-api-2.x and the log4j-core-2.x jar files. - -The other jars are necessary if your application calls the API -of another logging framework and you want to route logging calls to the Log4j 2 implementation. - -![Diagram showing which JARs correspond to which systems](images/whichjar-2.x.png) - - -You can use the log4j-to-slf4j adapter jar when your application calls the Log4j 2 API and you -want to route logging calls to a SLF4J implementation. - -![Diagram showing the dependency flow to use Log4j 2 API with SLF4J](images/whichjar-slf4j-2.x.png) - -Some of the Log4j components have features with optional dependencies. -The component page will have more detail. For example, the -[log4j-core component page](log4j-core/index.html) -has an outline of which log4j-core features have external dependencies. - - -$h4 How do I exclude conflicting dependencies? - -There are several scenarios where you may end up with conflicting dependencies, especially transitively -included ones. The following table shows for each Log4j dependency on the left (implicit groupId of -`org.apache.logging.log4j`), the following dependencies on the right can be safely excluded -(given in the format `groupId:artifactId`). - -
    java.lang.NullPointerException: test"; - - private static final String multiLine = "First line
    Second line
    HtmlLayoutTest.java:")); - } - for (final Appender app : appenders.values()) { - root.addAppender(app); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java deleted file mode 100644 index dba5cf36b77..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.BasicConfigurationFactory; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; -import org.apache.logging.log4j.core.lookup.JavaLookup; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.message.ObjectMessage; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.spi.AbstractLogger; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.Strings; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; - -/** - * Tests the JsonLayout class. - */ -@Category(Layouts.Json.class) -public class JsonLayoutTest { - static ConfigurationFactory cf = new BasicConfigurationFactory(); - - private static final String DQUOTE = "\""; - - @AfterClass - public static void cleanupClass() { - ConfigurationFactory.removeConfigurationFactory(cf); - ThreadContext.clearAll(); - } - - @BeforeClass - public static void setupClass() { - ThreadContext.clearAll(); - ConfigurationFactory.setConfigurationFactory(cf); - final LoggerContext ctx = LoggerContext.getContext(); - ctx.reconfigure(); - } - - LoggerContext ctx = LoggerContext.getContext(); - - Logger rootLogger = this.ctx.getRootLogger(); - - private void checkAt(final String expected, final int lineIndex, final List list) { - final String trimedLine = list.get(lineIndex).trim(); - assertTrue("Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine), trimedLine.equals(expected)); - } - - private void checkContains(final String expected, final List list) { - for (final String string : list) { - final String trimedLine = string.trim(); - if (trimedLine.equals(expected)) { - return; - } - } - Assert.fail("Cannot find " + expected + " in " + list); - } - - private void checkMapEntry(final String key, final String value, final boolean compact, final String str, - final boolean contextMapAslist) { - this.toPropertySeparator(compact); - if (contextMapAslist) { - // {"key":"KEY", "value":"VALUE"} - final String expected = String.format("{\"key\":\"%s\",\"value\":\"%s\"}", key, value); - assertTrue("Cannot find contextMapAslist " + expected + " in " + str, str.contains(expected)); - } else { - // "KEY":"VALUE" - final String expected = String.format("\"%s\":\"%s\"", key, value); - assertTrue("Cannot find contextMap " + expected + " in " + str, str.contains(expected)); - } - } - - private void checkProperty(final String key, final String value, final boolean compact, final String str) { - final String propSep = this.toPropertySeparator(compact); - // {"key":"MDC.B","value":"B_Value"} - final String expected = String.format("\"%s\"%s\"%s\"", key, propSep, value); - assertTrue("Cannot find " + expected + " in " + str, str.contains(expected)); - } - - private void checkPropertyName(final String name, final boolean compact, final String str) { - final String propSep = this.toPropertySeparator(compact); - assertTrue(str, str.contains(DQUOTE + name + DQUOTE + propSep)); - } - - private void checkPropertyNameAbsent(final String name, final boolean compact, final String str) { - final String propSep = this.toPropertySeparator(compact); - assertFalse(str, str.contains(DQUOTE + name + DQUOTE + propSep)); - } - - private void testAllFeatures(final boolean locationInfo, final boolean compact, final boolean eventEol, - final boolean includeContext, final boolean contextMapAslist, final boolean includeStacktrace) - throws Exception { - final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); - // @formatter:off - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setLocationInfo(locationInfo) - .setProperties(includeContext) - .setPropertiesAsList(contextMapAslist) - .setComplete(false) - .setCompact(compact) - .setEventEol(eventEol) - .setCharset(StandardCharsets.UTF_8) - .setIncludeStacktrace(includeStacktrace) - .build(); - // @formatter:off - final String str = layout.toSerializable(expected); - this.toPropertySeparator(compact); - // Just check for \n since \r might or might not be there. - assertEquals(str, !compact || eventEol, str.contains("\n")); - assertEquals(str, locationInfo, str.contains("source")); - assertEquals(str, includeContext, str.contains("contextMap")); - final Log4jLogEvent actual = new Log4jJsonObjectMapper(contextMapAslist, includeStacktrace, false, false).readValue(str, Log4jLogEvent.class); - LogEventFixtures.assertEqualLogEvents(expected, actual, locationInfo, includeContext, includeStacktrace); - if (includeContext) { - this.checkMapEntry("MDC.A", "A_Value", compact, str, contextMapAslist); - this.checkMapEntry("MDC.B", "B_Value", compact, str, contextMapAslist); - } - // - assertNull(actual.getThrown()); - // make sure the names we want are used - this.checkPropertyName("instant", compact, str); - this.checkPropertyName("thread", compact, str); // and not threadName - this.checkPropertyName("level", compact, str); - this.checkPropertyName("loggerName", compact, str); - this.checkPropertyName("marker", compact, str); - this.checkPropertyName("name", compact, str); - this.checkPropertyName("parents", compact, str); - this.checkPropertyName("message", compact, str); - this.checkPropertyName("thrown", compact, str); - this.checkPropertyName("cause", compact, str); - this.checkPropertyName("commonElementCount", compact, str); - this.checkPropertyName("localizedMessage", compact, str); - if (includeStacktrace) { - this.checkPropertyName("extendedStackTrace", compact, str); - this.checkPropertyName("class", compact, str); - this.checkPropertyName("method", compact, str); - this.checkPropertyName("file", compact, str); - this.checkPropertyName("line", compact, str); - this.checkPropertyName("exact", compact, str); - this.checkPropertyName("location", compact, str); - this.checkPropertyName("version", compact, str); - } else { - this.checkPropertyNameAbsent("extendedStackTrace", compact, str); - } - this.checkPropertyName("suppressed", compact, str); - this.checkPropertyName("loggerFqcn", compact, str); - this.checkPropertyName("endOfBatch", compact, str); - if (includeContext) { - this.checkPropertyName("contextMap", compact, str); - } else { - this.checkPropertyNameAbsent("contextMap", compact, str); - } - this.checkPropertyName("contextStack", compact, str); - if (locationInfo) { - this.checkPropertyName("source", compact, str); - } else { - this.checkPropertyNameAbsent("source", compact, str); - } - // check some attrs - this.checkProperty("loggerFqcn", "f.q.c.n", compact, str); - this.checkProperty("loggerName", "a.B", compact, str); - } - - @Test - public void testContentType() { - final AbstractJacksonLayout layout = JsonLayout.createDefaultLayout(); - assertEquals("application/json; charset=UTF-8", layout.getContentType()); - } - - @Test - public void testDefaultCharset() { - final AbstractJacksonLayout layout = JsonLayout.createDefaultLayout(); - assertEquals(StandardCharsets.UTF_8, layout.getCharset()); - } - - @Test - public void testEscapeLayout() throws Exception { - final Map appenders = this.rootLogger.getAppenders(); - for (final Appender appender : appenders.values()) { - this.rootLogger.removeAppender(appender); - } - final Configuration configuration = rootLogger.getContext().getConfiguration(); - // set up appender - final boolean propertiesAsList = false; - // @formatter:off - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setConfiguration(configuration) - .setLocationInfo(true) - .setProperties(true) - .setPropertiesAsList(propertiesAsList) - .setComplete(true) - .setCompact(false) - .setEventEol(false) - .setIncludeStacktrace(true) - .build(); - // @formatter:on - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - // set appender on root and set level to debug - this.rootLogger.addAppender(appender); - this.rootLogger.setLevel(Level.DEBUG); - - // output starting message - this.rootLogger.debug("Here is a quote ' and then a double quote \""); - - appender.stop(); - - final List list = appender.getMessages(); - - this.checkAt("[", 0, list); - this.checkAt("{", 1, list); - this.checkContains("\"level\" : \"DEBUG\",", list); - this.checkContains("\"message\" : \"Here is a quote ' and then a double quote \\\"\",", list); - this.checkContains("\"loggerFqcn\" : \"" + AbstractLogger.class.getName() + "\",", list); - for (final Appender app : appenders.values()) { - this.rootLogger.addAppender(app); - } - } - - /** - * Test case for MDC conversion pattern. - */ - @Test - public void testLayout() throws Exception { - final Map appenders = this.rootLogger.getAppenders(); - for (final Appender appender : appenders.values()) { - this.rootLogger.removeAppender(appender); - } - final Configuration configuration = rootLogger.getContext().getConfiguration(); - // set up appender - // Use [[ and ]] to test header and footer (instead of [ and ]) - final boolean propertiesAsList = false; - // @formatter:off - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setConfiguration(configuration) - .setLocationInfo(true) - .setProperties(true) - .setPropertiesAsList(propertiesAsList) - .setComplete(true) - .setCompact(false) - .setEventEol(false) - .setHeader("[[".getBytes(Charset.defaultCharset())) - .setFooter("]]".getBytes(Charset.defaultCharset())) - .setIncludeStacktrace(true) - .build(); - // @formatter:on - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - // set appender on root and set level to debug - this.rootLogger.addAppender(appender); - this.rootLogger.setLevel(Level.DEBUG); - - // output starting message - this.rootLogger.debug("starting mdc pattern test"); - - this.rootLogger.debug("empty mdc"); - - ThreadContext.put("key1", "value1"); - ThreadContext.put("key2", "value2"); - - this.rootLogger.debug("filled mdc"); - - ThreadContext.remove("key1"); - ThreadContext.remove("key2"); - - this.rootLogger.error("finished mdc pattern test", new NullPointerException("test")); - - appender.stop(); - - final List list = appender.getMessages(); - - this.checkAt("[[", 0, list); - this.checkAt("{", 1, list); - this.checkContains("\"loggerFqcn\" : \"" + AbstractLogger.class.getName() + "\",", list); - this.checkContains("\"level\" : \"DEBUG\",", list); - this.checkContains("\"message\" : \"starting mdc pattern test\",", list); - for (final Appender app : appenders.values()) { - this.rootLogger.addAppender(app); - } - } - - @Test - public void testLayoutLoggerName() throws Exception { - final boolean propertiesAsList = false; - // @formatter:off - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setLocationInfo(false) - .setProperties(false) - .setPropertiesAsList(propertiesAsList) - .setComplete(false) - .setCompact(true) - .setEventEol(false) - .setCharset(StandardCharsets.UTF_8) - .setIncludeStacktrace(true) - .build(); - // @formatter:on - // @formatter:off - final Log4jLogEvent expected = Log4jLogEvent.newBuilder() - .setLoggerName("a.B") - .setLoggerFqcn("f.q.c.n") - .setLevel(Level.DEBUG) - .setMessage(new SimpleMessage("M")) - .setThreadName("threadName") - .setTimeMillis(1).build(); - // @formatter:on - final String str = layout.toSerializable(expected); - assertTrue(str, str.contains("\"loggerName\":\"a.B\"")); - final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true, false, false).readValue(str, Log4jLogEvent.class); - assertEquals(expected.getLoggerName(), actual.getLoggerName()); - assertEquals(expected, actual); - } - - @Test - public void testAdditionalFields() throws Exception { - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setLocationInfo(false) - .setProperties(false) - .setComplete(false) - .setCompact(true) - .setEventEol(false) - .setIncludeStacktrace(false) - .setAdditionalFields(new KeyValuePair[] { - new KeyValuePair("KEY1", "VALUE1"), - new KeyValuePair("KEY2", "${java:runtime}"), }) - .setCharset(StandardCharsets.UTF_8) - .setConfiguration(ctx.getConfiguration()) - .build(); - final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); - assertTrue(str, str.contains("\"KEY1\":\"VALUE1\"")); - assertTrue(str, str.contains("\"KEY2\":\"" + new JavaLookup().getRuntime() + "\"")); - } - - @Test - public void testLocationOffCompactOffMdcOff() throws Exception { - this.testAllFeatures(false, false, false, false, false, true); - } - - @Test - public void testLocationOnCompactOnMdcOn() throws Exception { - this.testAllFeatures(true, true, false, true, false, true); - } - - @Test - public void testLocationOnCompactOnEventEolOnMdcOn() throws Exception { - this.testAllFeatures(true, true, true, true, false, true); - } - - @Test - public void testLocationOnCompactOnEventEolOnMdcOnMdcAsList() throws Exception { - this.testAllFeatures(true, true, true, true, true, true); - } - - @Test - public void testExcludeStacktrace() throws Exception { - this.testAllFeatures(false, false, false, false, false, false); - } - - @Test - public void testStacktraceAsString() throws Exception { - final String str = prepareJSONForStacktraceTests(true); - assertTrue(str, str.contains("\"extendedStackTrace\":\"java.lang.NullPointerException")); - } - - @Test - public void testStacktraceAsNonString() throws Exception { - final String str = prepareJSONForStacktraceTests(false); - assertTrue(str, str.contains("\"extendedStackTrace\":[")); - } - - private String prepareJSONForStacktraceTests(final boolean stacktraceAsString) { - final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); - // @formatter:off - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setCompact(true) - .setIncludeStacktrace(true) - .setStacktraceAsString(stacktraceAsString) - .build(); - // @formatter:off - return layout.toSerializable(expected); - } - - @Test - public void testObjectMessageAsJsonString() { - final String str = prepareJSONForObjectMessageAsJsonObjectTests(1234, false); - assertTrue(str, str.contains("\"message\":\"" + this.getClass().getCanonicalName() + "$TestClass@")); - } - - @Test - public void testObjectMessageAsJsonObject() { - final String str = prepareJSONForObjectMessageAsJsonObjectTests(1234, true); - assertTrue(str, str.contains("\"message\":{\"value\":1234}")); - } - - private String prepareJSONForObjectMessageAsJsonObjectTests(final int value, final boolean objectMessageAsJsonObject) { - final TestClass testClass = new TestClass(); - testClass.setValue(value); - // @formatter:off - final Log4jLogEvent expected = Log4jLogEvent.newBuilder() - .setLoggerName("a.B") - .setLoggerFqcn("f.q.c.n") - .setLevel(Level.DEBUG) - .setMessage(new ObjectMessage(testClass)) - .setThreadName("threadName") - .setTimeMillis(1).build(); - // @formatter:off - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setCompact(true) - .setObjectMessageAsJsonObject(objectMessageAsJsonObject) - .build(); - // @formatter:off - return layout.toSerializable(expected); - } - - @Test - public void testIncludeNullDelimiterTrue() throws Exception { - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setCompact(true) - .setIncludeNullDelimiter(true) - .build(); - final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); - assertTrue(str.endsWith("\0")); - } - - @Test - public void testIncludeNullDelimiterFalse() throws Exception { - final AbstractJacksonLayout layout = JsonLayout.newBuilder() - .setCompact(true) - .setIncludeNullDelimiter(false) - .build(); - final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); - assertFalse(str.endsWith("\0")); - } - - private String toPropertySeparator(final boolean compact) { - return compact ? ":" : " : "; - } - - private static class TestClass { - private int value; - - public int getValue() { - return value; - } - - public void setValue(int value) { - this.value = value; - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java deleted file mode 100644 index 958e38aaf76..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_2195_Test.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.layout; - -import java.io.Serializable; -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Test; - -public class Log4j2_2195_Test { - - @ClassRule - public static final LoggerContextRule loggerContextRule = new LoggerContextRule( - "src/test/resources/LOG4J-2195/log4j2.xml"); - - private static final Logger logger = LogManager.getLogger(Log4j2_2195_Test.class); - - @Test - public void test() { - logger.info("This is a test.", new Exception("Test exception!")); - ListAppender listAppender = loggerContextRule.getListAppender("ListAppender"); - Assert.assertNotNull(listAppender); - List events = listAppender.getMessages(); - Assert.assertNotNull(events); - Assert.assertEquals(1, events.size()); - String logEvent = events.get(0); - Assert.assertNotNull(logEvent); - Assert.assertFalse("\"org.junit\" should not be here", logEvent.contains("org.junit")); - Assert.assertFalse("\"org.eclipse\" should not be here", logEvent.contains("org.eclipse")); - // - Layout layout = listAppender.getLayout(); - PatternLayout pLayout = (PatternLayout) layout; - Assert.assertNotNull(pLayout); - Serializer eventSerializer = pLayout.getEventSerializer(); - Assert.assertNotNull(eventSerializer); - // - Assert.assertTrue("Missing \"|\"", logEvent.contains("|")); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java deleted file mode 100644 index 402ea1b989d..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutLookupDateTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; - -/** - * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel. - * - * This shows the behavior this user wants to disable. - */ -public class PatternLayoutLookupDateTest { - - @Rule - public final LoggerContextRule context = new LoggerContextRule("log4j-list.xml"); - - @Test - public void testDateLookupInMessage() { - final String template = "${date:YYYY-MM-dd}"; - context.getLogger(PatternLayoutLookupDateTest.class.getName()).info(template); - final ListAppender listAppender = context.getListAppender("List"); - final String string = listAppender.getMessages().get(0); - Assert.assertFalse(string, string.contains(template)); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java deleted file mode 100644 index a2881dfc337..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.layout; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.appender.FileAppender; -import org.apache.logging.log4j.core.lookup.MainMapLookup; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Test; - -/** - * Tests LOG4j2-962. - */ -public class PatternLayoutMainMapLookupTest { - - static { - // Must be set before Log4j writes the header to the appenders. - MainMapLookup.setMainArguments("value0", "value1", "value2"); - } - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule("log4j2-962.xml"); - - @Test - public void testFileName() { - final FileAppender fileApp = (FileAppender) context.getRequiredAppender("File"); - final String name = fileApp.getFileName(); - Assert.assertEquals("target/value0.log", name); - } - - @Test - public void testHeader() { - final ListAppender listApp = context.getListAppender("List"); - final Logger logger = context.getLogger(this.getClass().getName()); - logger.info("Hello World"); - final List messages = listApp.getMessages(); - Assert.assertFalse(messages.isEmpty()); - final String messagesStr = messages.toString(); - Assert.assertEquals(messagesStr, "Header: value0", messages.get(0)); - listApp.stop(); - Assert.assertEquals(messagesStr, "Footer: value1", messages.get(2)); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java deleted file mode 100644 index 4dcd942ba3e..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutNoLookupDateTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; - -/** - * See (LOG4J2-905) Ability to disable (date) lookup completely, compatibility issues with other libraries like camel. - */ -public class PatternLayoutNoLookupDateTest { - - @Rule - public final LoggerContextRule context = new LoggerContextRule("log4j-list-nolookups.xml"); - - @Test - public void testDateLookupInMessage() { - final String template = "${date:YYYY-MM-dd}"; - context.getLogger(PatternLayoutNoLookupDateTest.class.getName()).info(template); - final ListAppender listAppender = context.getListAppender("List"); - final String string = listAppender.getMessages().get(0); - Assert.assertTrue(string, string.contains(template)); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java deleted file mode 100644 index 152c605146b..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import static org.junit.Assert.assertTrue; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.MarkerManager; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Test; - -public class PatternSelectorTest { - - public class FauxLogger { - public String formatEvent(final LogEvent event, final Layout layout) { - return new String(layout.toByteArray(event)); - } - } - - LoggerContext ctx = LoggerContext.getContext(); - - @Test - public void testPatternSelector() throws Exception { - final PatternMatch[] patterns = new PatternMatch[1]; - patterns[0] = new PatternMatch("FLOW", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n"); - final PatternSelector selector = MarkerPatternSelector.createSelector(patterns, "%d %-5p [%t]: %m%n", true, true, ctx.getConfiguration()); - final PatternLayout layout = PatternLayout.newBuilder().withPatternSelector(selector) - .withConfiguration(ctx.getConfiguration()).build(); - final LogEvent event1 = Log4jLogEvent.newBuilder() // - .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternSelectorTest$FauxLogger") - .setMarker(MarkerManager.getMarker("FLOW")) - .setLevel(Level.TRACE) // - .setIncludeLocation(true) - .setMessage(new SimpleMessage("entry")).build(); - final String result1 = new FauxLogger().formatEvent(event1, layout); - final String expectSuffix1 = String.format("====== PatternSelectorTest.testPatternSelector:53 entry ======%n"); - assertTrue("Unexpected result: " + result1, result1.endsWith(expectSuffix1)); - final LogEvent event2 = Log4jLogEvent.newBuilder() // - .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") // - .setLevel(Level.INFO) // - .setMessage(new SimpleMessage("Hello, world 1!")).build(); - final String result2 = new String(layout.toByteArray(event2)); - final String expectSuffix2 = String.format("Hello, world 1!%n"); - assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2)); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java deleted file mode 100644 index db700753bc2..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java +++ /dev/null @@ -1,528 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.MarkerManager; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.BasicConfigurationFactory; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.net.Facility; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.junit.ThreadContextRule; -import org.apache.logging.log4j.message.StructuredDataCollectionMessage; -import org.apache.logging.log4j.message.StructuredDataMessage; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.ProcessIdUtil; -import org.apache.logging.log4j.util.Strings; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class Rfc5424LayoutTest { - LoggerContext ctx = LoggerContext.getContext(); - Logger root = ctx.getRootLogger(); - - private static final String PROCESSID = ProcessIdUtil.getProcessId(); - private static final String line1 = String.format("ATM %s - [RequestContext@3692 loginId=\"JohnDoe\"] starting mdc pattern test", PROCESSID); - private static final String line2 = String.format("ATM %s - [RequestContext@3692 loginId=\"JohnDoe\"] empty mdc", PROCESSID); - private static final String line3 = String.format("ATM %s - [RequestContext@3692 loginId=\"JohnDoe\"] filled mdc", PROCESSID); - private static final String line4 = - String.format("ATM %s Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"]" + - "[RequestContext@3692 ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete", PROCESSID); - private static final String lineEscaped3 = - String.format("ATM %s - [RequestContext@3692 escaped=\"Testing escaping #012 \\\" \\] \\\"\" loginId=\"JohnDoe\"] filled mdc", PROCESSID); - private static final String lineEscaped4 = - String.format("ATM %s Audit [Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"]" + - "[RequestContext@3692 escaped=\"Testing escaping #012 \\\" \\] \\\"\" ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"] Transfer Complete", - PROCESSID); - private static final String collectionLine1 = "[Transfer@18060 Amount=\"200.00\" FromAccount=\"123457\" " + - "ToAccount=\"123456\"]"; - private static final String collectionLine2 = "[Extra@18060 Item1=\"Hello\" Item2=\"World\"]"; - private static final String collectionLine3 = "[RequestContext@3692 ipAddress=\"192.168.0.120\" loginId=\"JohnDoe\"]"; - private static final String collectionEndOfLine = "Transfer Complete"; - - - static ConfigurationFactory cf = new BasicConfigurationFactory(); - - @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); - - @BeforeClass - public static void setupClass() { - StatusLogger.getLogger().setLevel(Level.OFF); - ConfigurationFactory.setConfigurationFactory(cf); - final LoggerContext ctx = LoggerContext.getContext(); - ctx.reconfigure(); - } - - @AfterClass - public static void cleanupClass() { - ConfigurationFactory.removeConfigurationFactory(cf); - } - - /** - * Test case for MDC conversion pattern. - */ - @Test - public void testLayout() throws Exception { - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - // set up appender - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - ThreadContext.put("loginId", "JohnDoe"); - - // output starting message - root.debug("starting mdc pattern test"); - - root.debug("empty mdc"); - - ThreadContext.put("key1", "value1"); - ThreadContext.put("key2", "value2"); - - root.debug("filled mdc"); - - ThreadContext.put("ipAddress", "192.168.0.120"); - ThreadContext.put("locale", Locale.US.getDisplayName()); - try { - final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit"); - msg.put("ToAccount", "123456"); - msg.put("FromAccount", "123457"); - msg.put("Amount", "200.00"); - root.info(MarkerManager.getMarker("EVENT"), msg); - - List list = appender.getMessages(); - - assertTrue("Expected line 1 to end with: " + line1 + " Actual " + list.get(0), list.get(0).endsWith(line1)); - assertTrue("Expected line 2 to end with: " + line2 + " Actual " + list.get(1), list.get(1).endsWith(line2)); - assertTrue("Expected line 3 to end with: " + line3 + " Actual " + list.get(2), list.get(2).endsWith(line3)); - assertTrue("Expected line 4 to end with: " + line4 + " Actual " + list.get(3), list.get(3).endsWith(line4)); - - for (final String frame : list) { - int length = -1; - final int frameLength = frame.length(); - final int firstSpacePosition = frame.indexOf(' '); - final String messageLength = frame.substring(0, firstSpacePosition); - try { - length = Integer.parseInt(messageLength); - // the ListAppender removes the ending newline, so we expect one less size - assertEquals(frameLength, messageLength.length() + length); - } - catch (final NumberFormatException e) { - assertTrue("Not a valid RFC 5425 frame", false); - } - } - - appender.clear(); - - ThreadContext.remove("loginId"); - - root.debug("This is a test"); - - list = appender.getMessages(); - assertTrue("No messages expected, found " + list.size(), list.isEmpty()); - } finally { - ThreadContext.clearMap(); - root.removeAppender(appender); - appender.stop(); - } - } - - /** - * Test case for MDC conversion pattern. - */ - @Test - public void testCollection() throws Exception { - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - // set up appender - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - ThreadContext.put("loginId", "JohnDoe"); - ThreadContext.put("ipAddress", "192.168.0.120"); - ThreadContext.put("locale", Locale.US.getDisplayName()); - try { - final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit"); - msg.put("ToAccount", "123456"); - msg.put("FromAccount", "123457"); - msg.put("Amount", "200.00"); - final StructuredDataMessage msg2 = new StructuredDataMessage("Extra@18060", null, "Audit"); - msg2.put("Item1", "Hello"); - msg2.put("Item2", "World"); - List messages = new ArrayList<>(); - messages.add(msg); - messages.add(msg2); - final StructuredDataCollectionMessage collectionMessage = new StructuredDataCollectionMessage(messages); - - root.info(MarkerManager.getMarker("EVENT"), collectionMessage); - - List list = appender.getMessages(); - String result = list.get(0); - assertTrue("Expected line to contain " + collectionLine1 + ", Actual " + result, - result.contains(collectionLine1)); - assertTrue("Expected line to contain " + collectionLine2 + ", Actual " + result, - result.contains(collectionLine2)); - assertTrue("Expected line to contain " + collectionLine3 + ", Actual " + result, - result.contains(collectionLine3)); - assertTrue("Expected line to end with: " + collectionEndOfLine + " Actual " + result, - result.endsWith(collectionEndOfLine)); - - for (final String frame : list) { - int length = -1; - final int frameLength = frame.length(); - final int firstSpacePosition = frame.indexOf(' '); - final String messageLength = frame.substring(0, firstSpacePosition); - try { - length = Integer.parseInt(messageLength); - // the ListAppender removes the ending newline, so we expect one less size - assertEquals(frameLength, messageLength.length() + length); - } - catch (final NumberFormatException e) { - assertTrue("Not a valid RFC 5425 frame", false); - } - } - - appender.clear(); - } finally { - ThreadContext.clearMap(); - root.removeAppender(appender); - appender.stop(); - } - } - - /** - * Test case for escaping newlines and other SD PARAM-NAME special characters. - */ - @Test - public void testEscape() throws Exception { - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - // set up layout/appender - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, "#012", "ATM", null, "key1, key2, locale", null, "loginId", null, true, null, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - ThreadContext.put("loginId", "JohnDoe"); - - // output starting message - root.debug("starting mdc pattern test"); - - root.debug("empty mdc"); - - ThreadContext.put("escaped", "Testing escaping \n \" ] \""); - - root.debug("filled mdc"); - - ThreadContext.put("ipAddress", "192.168.0.120"); - ThreadContext.put("locale", Locale.US.getDisplayName()); - try { - final StructuredDataMessage msg = new StructuredDataMessage("Transfer@18060", "Transfer Complete", "Audit"); - msg.put("ToAccount", "123456"); - msg.put("FromAccount", "123457"); - msg.put("Amount", "200.00"); - root.info(MarkerManager.getMarker("EVENT"), msg); - - List list = appender.getMessages(); - - assertTrue("Expected line 1 to end with: " + line1 + " Actual " + list.get(0), list.get(0).endsWith(line1)); - assertTrue("Expected line 2 to end with: " + line2 + " Actual " + list.get(1), list.get(1).endsWith(line2)); - assertTrue("Expected line 3 to end with: " + lineEscaped3 + " Actual " + list.get(2), list.get(2).endsWith(lineEscaped3)); - assertTrue("Expected line 4 to end with: " + lineEscaped4 + " Actual " + list.get(3), list.get(3).endsWith(lineEscaped4)); - - appender.clear(); - - ThreadContext.remove("loginId"); - - root.debug("This is a test"); - - list = appender.getMessages(); - assertTrue("No messages expected, found " + list.size(), list.isEmpty()); - } finally { - root.removeAppender(appender); - appender.stop(); - } - } - - /** - * Test case for MDC exception conversion pattern. - */ - @Test - public void testException() throws Exception { - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - // set up layout/appender - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, "loginId", "%xEx", true, null, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - ThreadContext.put("loginId", "JohnDoe"); - - // output starting message - root.debug("starting mdc pattern test", new IllegalArgumentException("Test")); - - try { - - final List list = appender.getMessages(); - - assertTrue("Not enough list entries", list.size() > 1); - final String string = list.get(1); - assertTrue("No Exception in " + string, string.contains("IllegalArgumentException")); - - appender.clear(); - } finally { - root.removeAppender(appender); - appender.stop(); - } - } - - /** - * Test case for MDC logger field inclusion. - */ - @Test - public void testMDCLoggerFields() throws Exception { - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - - final LoggerFields[] loggerFields = new LoggerFields[] { - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source", "%C.%M")}, null, null, false), - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source2", "%C.%M")}, null, null, false) - }; - - // set up layout/appender - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, loggerFields, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - // output starting message - root.info("starting logger fields test"); - - try { - - final List list = appender.getMessages(); - assertTrue("Not enough list entries", list.size() > 0); - assertTrue("No class/method", list.get(0).contains("Rfc5424LayoutTest.testMDCLoggerFields")); - - appender.clear(); - } finally { - root.removeAppender(appender); - appender.stop(); - } - } - - @Test - public void testLoggerFields() { - final String[] fields = new String[] { - "[BAZ@32473 baz=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]", - "[RequestContext@3692 bar=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]", - "[SD-ID@32473 source=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]" - }; - final List expectedToContain = Arrays.asList(fields); - - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - - final LoggerFields[] loggerFields = new LoggerFields[] { - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("source", "%C.%M")}, "SD-ID", - "32473", false), - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("baz", "%C.%M"), - new KeyValuePair("baz", "%C.%M") }, "BAZ", "32473", false), - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("bar", "%C.%M")}, null, null, false) - }; - - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - root.info("starting logger fields test"); - - try { - - final List list = appender.getMessages(); - assertTrue("Not enough list entries", list.size() > 0); - final String message = list.get(0); - assertTrue("No class/method", message.contains("Rfc5424LayoutTest.testLoggerFields")); - for (final String value : expectedToContain) { - Assert.assertTrue("Message expected to contain " + value + " but did not", message.contains(value)); - } - appender.clear(); - } finally { - root.removeAppender(appender); - appender.stop(); - } - } - - @Test - public void testDiscardEmptyLoggerFields() { - final String mdcId = "RequestContext"; - - Arrays.asList( - "[BAZ@32473 baz=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]" + - "[RequestContext@3692 bar=\"org.apache.logging.log4j.core.layout.Rfc5424LayoutTest.testLoggerFields\"]" - ); - - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - - final LoggerFields[] loggerFields = new LoggerFields[] { - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("dummy", Strings.EMPTY), - new KeyValuePair("empty", Strings.EMPTY)}, "SD-ID", "32473", true), - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("baz", "%C.%M"), - new KeyValuePair("baz", "%C.%M") }, "BAZ", "32473", false), - LoggerFields.createLoggerFields(new KeyValuePair[] { new KeyValuePair("bar", "%C.%M")}, null, null, false) - }; - - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, mdcId, - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, false, loggerFields, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - root.info("starting logger fields test"); - - try { - - final List list = appender.getMessages(); - assertTrue("Not enough list entries", list.size() > 0); - final String message = list.get(0); - Assert.assertTrue("SD-ID should have been discarded", !message.contains("SD-ID")); - Assert.assertTrue("BAZ should have been included", message.contains("BAZ")); - Assert.assertTrue(mdcId + "should have been included", message.contains(mdcId)); - appender.clear(); - } finally { - root.removeAppender(appender); - appender.stop(); - } - } - - @Test - public void testSubstituteStructuredData() { - final String mdcId = "RequestContext"; - - final String expectedToContain = String.format("ATM %s MSG-ID - Message", PROCESSID); - - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, false, mdcId, - null, null, true, null, "ATM", "MSG-ID", "key1, key2, locale", null, null, null, false, null, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - root.info("Message"); - - try { - final List list = appender.getMessages(); - assertTrue("Not enough list entries", list.size() > 0); - final String message = list.get(0); - Assert.assertTrue("Not the expected message received", message.contains(expectedToContain)); - appender.clear(); - } finally { - root.removeAppender(appender); - appender.stop(); - } - } - - @Test - public void testParameterizedMessage() { - for (final Appender appender : root.getAppenders().values()) { - root.removeAppender(appender); - } - // set up appender - final AbstractStringLayout layout = Rfc5424Layout.createLayout(Facility.LOCAL0, "Event", 3692, true, "RequestContext", - null, null, true, null, "ATM", null, "key1, key2, locale", null, null, null, true, null, null); - final ListAppender appender = new ListAppender("List", null, layout, true, false); - - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - root.info("Hello {}", "World"); - try { - final List list = appender.getMessages(); - assertTrue("Not enough list entries", list.size() > 0); - final String message = list.get(0); - assertTrue("Incorrect message. Expected - Hello World, Actual - " + message, message.contains("Hello World")); - } finally { - root.removeAppender(appender); - appender.stop(); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java deleted file mode 100644 index c01c82e2952..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.List; -import java.util.Map; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LoggingException; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.BasicConfigurationFactory; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.junit.ThreadContextRule; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.FilteredObjectInputStream; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class SerializedLayoutTest { - private static final String DAT_PATH = "target/test-classes/serializedEvent.dat"; - LoggerContext ctx = LoggerContext.getContext(); - Logger root = ctx.getRootLogger(); - - static ConfigurationFactory cf = new BasicConfigurationFactory(); - - static boolean useObjectInputStream = false; - - @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); - - @BeforeClass - public static void setupClass() { - try { - Class.forName("java.io.ObjectInputFilter"); - useObjectInputStream = true; - } catch (ClassNotFoundException ex) { - // Ignore the exception - } - ConfigurationFactory.setConfigurationFactory(cf); - final LoggerContext ctx = LoggerContext.getContext(); - ctx.reconfigure(); - } - - @AfterClass - public static void cleanupClass() { - ConfigurationFactory.removeConfigurationFactory(cf); - } - - private static final String body = - "\r"; - - private static final String[] expected = { - "Logger=root Level=DEBUG Message=starting mdc pattern test", - "Logger=root Level=DEBUG Message=empty mdc", - "Logger=root Level=DEBUG Message=filled mdc", - "Logger=root Level=ERROR Message=finished mdc pattern test", - "Logger=root Level=ERROR Message=Throwing an exception" - }; - - - /** - * Test case for MDC conversion pattern. - */ - @Test - public void testLayout() throws Exception { - final Map appenders = root.getAppenders(); - for (final Appender appender : appenders.values()) { - root.removeAppender(appender); - } - // set up appender - final SerializedLayout layout = SerializedLayout.createLayout(); - final ListAppender appender = new ListAppender("List", null, layout, false, true); - appender.start(); - - // set appender on root and set level to debug - root.addAppender(appender); - root.setLevel(Level.DEBUG); - - // output starting message - root.debug("starting mdc pattern test"); - - root.debug("empty mdc"); - - ThreadContext.put("key1", "value1"); - ThreadContext.put("key2", "value2"); - - root.debug("filled mdc"); - - ThreadContext.remove("key1"); - ThreadContext.remove("key2"); - - root.error("finished mdc pattern test", new NullPointerException("test")); - - final Exception parent = new IllegalStateException("Test"); - final Throwable child = new LoggingException("This is a test", parent); - - root.error("Throwing an exception", child); - - appender.stop(); - - final List data = appender.getData(); - assertTrue(data.size() > 0); - int i = 0; - for (final byte[] item : data) { - final ByteArrayInputStream bais = new ByteArrayInputStream(item); - final ObjectInputStream ois = useObjectInputStream ? new ObjectInputStream(bais) : - new FilteredObjectInputStream(bais); - LogEvent event; - try { - event = (LogEvent) ois.readObject(); - } catch (final IOException ioe) { - System.err.println("Exception processing item " + i); - throw ioe; - } - assertTrue("Incorrect event", event.toString().equals(expected[i])); - ++i; - } - for (final Appender app : appenders.values()) { - root.addAppender(app); - } - } - - @Test - public void testSerialization() throws Exception { - final SerializedLayout layout = SerializedLayout.createLayout(); - final Throwable throwable = new LoggingException("Test"); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName(this.getClass().getName()) // - .setLoggerFqcn("org.apache.logging.log4j.core.Logger") // - .setLevel(Level.INFO) // - .setMessage(new SimpleMessage("Hello, world!")) // - .setThrown(throwable) // - .build(); - final byte[] result = layout.toByteArray(event); - assertNotNull(result); - final FileOutputStream fos = new FileOutputStream(DAT_PATH); - fos.write(layout.getHeader()); - fos.write(result); - fos.close(); - } - - @Test - public void testDeserialization() throws Exception { - testSerialization(); - final File file = new File(DAT_PATH); - final FileInputStream fis = new FileInputStream(file); - final ObjectInputStream ois = useObjectInputStream ? new ObjectInputStream(fis) : - new FilteredObjectInputStream(fis); - try { - final LogEvent event = (LogEvent) ois.readObject(); - assertNotNull(event); - } finally { - ois.close(); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java deleted file mode 100644 index 51faed93cc7..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java +++ /dev/null @@ -1,310 +0,0 @@ -package org.apache.logging.log4j.core.layout;/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Tests the {@code TextEncoderHelper} class. - */ -public class StringBuilderEncoderTest { - - @Test - public void testEncodeText_TextFitCharBuff_BytesFitByteBuff() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024); - final StringBuilder text = createText(15); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17); - helper.encode(text, destination); - - assertEquals("drained", 0, destination.drainPoints.size()); - assertEquals("destination.buf.pos", text.length(), destination.buffer.position()); - - for (int i = 0; i < text.length(); i++) { - assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(i)); - } - } - - @Test - public void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024); - final StringBuilder text = createText(15); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(14, 15); - helper.encode(text, destination); - - assertEquals("drained", 1, destination.drainPoints.size()); - assertEquals("drained[0].from", 0, destination.drainPoints.get(0).position); - assertEquals("drained[0].to", destination.buffer.capacity(), destination.drainPoints.get(0).limit); - assertEquals("drained[0].length", destination.buffer.capacity(), destination.drainPoints.get(0).length()); - assertEquals("destination.buf.pos", text.length() - destination.buffer.capacity(), - destination.buffer.position()); - - for (int i = 0; i < destination.buffer.capacity(); i++) { - assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i)); - } - for (int i = destination.buffer.capacity(); i < text.length(); i++) { - final int bufIx = i - destination.buffer.capacity(); - assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(bufIx)); - } - } - - @Test - public void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff_MultiplePasses() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024); - final StringBuilder text = createText(15); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(4, 20); - helper.encode(text, destination); - - assertEquals("drained", 3, destination.drainPoints.size()); - assertEquals("drained[0].from", 0, destination.drainPoints.get(0).position); - assertEquals("drained[0].to", destination.buffer.capacity(), destination.drainPoints.get(0).limit); - assertEquals("drained[0].length", destination.buffer.capacity(), destination.drainPoints.get(0).length()); - assertEquals("drained[1].from", 0, destination.drainPoints.get(1).position); - assertEquals("drained[1].to", destination.buffer.capacity(), destination.drainPoints.get(1).limit); - assertEquals("drained[1].length", destination.buffer.capacity(), destination.drainPoints.get(1).length()); - assertEquals("drained[2].from", 0, destination.drainPoints.get(2).position); - assertEquals("drained[2].to", destination.buffer.capacity(), destination.drainPoints.get(2).limit); - assertEquals("drained[2].length", destination.buffer.capacity(), destination.drainPoints.get(2).length()); - assertEquals("destination.buf.pos", text.length() - 3 * destination.buffer.capacity(), - destination.buffer.position()); - - for (int i = 0; i < 3 * destination.buffer.capacity(); i++) { - assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i)); - } - for (int i = 3 * destination.buffer.capacity(); i < text.length(); i++) { - final int bufIx = i - 3 * destination.buffer.capacity(); - assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(bufIx)); - } - } - - @Test - public void testEncodeText_TextDoesntFitCharBuff_BytesFitByteBuff() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); - final StringBuilder text = createText(15); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17); - helper.encode(text, destination); - - assertEquals("drained", 0, destination.drainPoints.size()); - assertEquals("destination.buf.pos", text.length(), destination.buffer.position()); - - for (int i = 0; i < text.length(); i++) { - assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(i)); - } - } - - @Test - public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesFitByteBuff() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); - final StringBuilder text = new StringBuilder( // 日本語テスト文章 - "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50); - helper.encode(text, destination); - - assertEquals("drained", 0, destination.drainPoints.size()); - destination.drain(destination.getByteBuffer()); - - final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8); - for (int i = 0; i < utf8.length; i++) { - assertEquals("byte at " + i, utf8[i], destination.drained.get(i)); - } - } - - @Test - public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesFitByteBuff() throws Exception { - final Charset SHIFT_JIS = Charset.forName("Shift_JIS"); - final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 8 * 1024); - final StringBuilder text = new StringBuilder( // 日本語テスト文章 - "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50); - helper.encode(text, destination); - - assertEquals("drained", 0, destination.drainPoints.size()); - destination.drain(destination.getByteBuffer()); - - final byte[] bytes = text.toString().getBytes(SHIFT_JIS); - for (int i = 0; i < bytes.length; i++) { - assertEquals("byte at " + i, bytes[i], destination.drained.get(i)); - } - } - - @Test - public void testEncodeText_TextDoesntFitCharBuff_BytesDontFitByteBuff() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); - final StringBuilder text = createText(15); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 17); - helper.encode(text, destination); - - assertEquals("drained", 4, destination.drainPoints.size()); - assertEquals("destination.buf.pos", 3, destination.buffer.position()); - - for (int i = 0; i < text.length() - 3; i++) { - assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i)); - } - for (int i = 0; i < 3; i++) { - assertEquals("char at " + (12 + i), (byte) text.charAt(12 + i), destination.buffer.get(i)); - } - } - - @Test - public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesDontFitByteBuff() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024); - final StringBuilder text = new StringBuilder( // 日本語テスト文章 - "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); - helper.encode(text, destination); - - assertEquals("drained", 7, destination.drainPoints.size()); - destination.drain(destination.getByteBuffer()); - - final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8); - for (int i = 0; i < utf8.length; i++) { - assertEquals("byte at " + i, utf8[i], destination.drained.get(i)); - } - } - - @Test - public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 5); - final StringBuilder text = new StringBuilder( // 日本語テスト文章日本語テスト文章 - "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); - helper.encode(text, destination); - - assertEquals("drained", 15, destination.drainPoints.size()); - destination.drain(destination.getByteBuffer()); - - final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8); - for (int i = 0; i < utf8.length; i++) { - assertEquals("byte at " + i, utf8[i], destination.drained.get(i)); - } - } - - @Test - public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesDontFitByteBuff() throws Exception { - final Charset SHIFT_JIS = Charset.forName("Shift_JIS"); - final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 8 * 1024); - final StringBuilder text = new StringBuilder( // 日本語テスト文章 - "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); - helper.encode(text, destination); - - destination.drain(destination.getByteBuffer()); - - final byte[] bytes = text.toString().getBytes(SHIFT_JIS); - for (int i = 0; i < bytes.length; i++) { - assertEquals("byte at " + i, bytes[i], destination.drained.get(i)); - } - } - - @Test - public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() throws Exception { - final Charset SHIFT_JIS = Charset.forName("Shift_JIS"); - final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 5); - final StringBuilder text = new StringBuilder( // 日本語テスト文章日本語テスト文章 - "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0"); - final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50); - helper.encode(text, destination); - - destination.drain(destination.getByteBuffer()); - - final byte[] bytes = text.toString().getBytes(SHIFT_JIS); - for (int i = 0; i < bytes.length; i++) { - assertEquals("byte at " + i, bytes[i], destination.drained.get(i)); - } - } - - @Test - public void testCopyCopiesAllDataIfSuffientRemainingSpace() throws Exception { - final CharBuffer buff = CharBuffer.wrap(new char[16]); - final StringBuilder text = createText(15); - final int length = TextEncoderHelper.copy(text, 0, buff); - assertEquals("everything fits", text.length(), length); - for (int i = 0; i < length; i++) { - assertEquals("char at " + i, text.charAt(i), buff.get(i)); - } - assertEquals("position moved by length", text.length(), buff.position()); - } - - @Test - public void testCopyUpToRemainingSpace() throws Exception { - final CharBuffer buff = CharBuffer.wrap(new char[3]); - final StringBuilder text = createText(15); - final int length = TextEncoderHelper.copy(text, 0, buff); - assertEquals("partial copy", buff.capacity(), length); - for (int i = 0; i < length; i++) { - assertEquals("char at " + i, text.charAt(i), buff.get(i)); - } - assertEquals("no space remaining", 0, buff.remaining()); - assertEquals("position at end", buff.capacity(), buff.position()); - } - - @Test - public void testCopyDoesNotWriteBeyondStringText() throws Exception { - final CharBuffer buff = CharBuffer.wrap(new char[5]); - assertEquals("initial buffer position", 0, buff.position()); - final StringBuilder text = createText(2); - final int length = TextEncoderHelper.copy(text, 0, buff); - assertEquals("full copy", text.length(), length); - for (int i = 0; i < length; i++) { - assertEquals("char at " + i, text.charAt(i), buff.get(i)); - } - assertEquals("resulting buffer position", text.length(), buff.position()); - for (int i = length; i < buff.capacity(); i++) { - assertEquals("unset char at " + i, 0, buff.get(i)); - } - } - - @Test - public void testCopyStartsAtBufferPosition() throws Exception { - final CharBuffer buff = CharBuffer.wrap(new char[10]); - final int START_POSITION = 5; - buff.position(START_POSITION); // set start position - final StringBuilder text = createText(15); - final int length = TextEncoderHelper.copy(text, 0, buff); - assertEquals("partial copy", buff.capacity() - START_POSITION, length); - for (int i = 0; i < length; i++) { - assertEquals("char at " + i, text.charAt(i), buff.get(START_POSITION + i)); - } - assertEquals("buffer position at end", buff.capacity(), buff.position()); - } - - @Test - public void testEncode_ALotWithoutErrors() throws Exception { - final StringBuilderEncoder helper = new StringBuilderEncoder(Charset.defaultCharset()); - final StringBuilder text = new StringBuilder("2016-04-13 21:07:47,487 DEBUG [org.apache.logging.log4j.perf.jmh.FileAppenderBenchmark.log4j2ParameterizedString-jmh-worker-1] FileAppenderBenchmark - This is a debug [2383178] message\r\n"); - final int DESTINATION_SIZE = 1024 * 1024; - final SpyByteBufferDestination destination = new SpyByteBufferDestination(256 * 1024, DESTINATION_SIZE); - - final int max = DESTINATION_SIZE / text.length(); - for (int i = 0; i < max; i++) { - helper.encode(text, destination); - } - // no error - } - - private StringBuilder createText(final int length) { - final StringBuilder result = new StringBuilder(length); - for (int i = 0; i < length; i++) { - result.append((char) (' ' + i)); // space=0x20 - } - return result; - } -} \ No newline at end of file diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java deleted file mode 100644 index 4ee8f1eac65..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.layout; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.categories.Layouts; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.BasicConfigurationFactory; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.ConfigurationFactory; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.jackson.Log4jXmlObjectMapper; -import org.apache.logging.log4j.core.lookup.JavaLookup; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.junit.ThreadContextRule; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.spi.AbstractLogger; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import org.junit.experimental.categories.Category; - -import static org.junit.Assert.*; - -/** - * Tests {@link XmlLayout}. - */ -@Category(Layouts.Xml.class) -public class XmlLayoutTest { - private static final String body = "empty mdc"; - static ConfigurationFactory cf = new BasicConfigurationFactory(); - private static final String markerTag = ""; - - @Rule - public final ThreadContextRule threadContextRule = new ThreadContextRule(); - - @AfterClass - public static void cleanupClass() { - ConfigurationFactory.removeConfigurationFactory(cf); - } - - @BeforeClass - public static void setupClass() { - ConfigurationFactory.setConfigurationFactory(cf); - final LoggerContext ctx = LoggerContext.getContext(); - ctx.reconfigure(); - } - - LoggerContext ctx = LoggerContext.getContext(); - - Logger rootLogger = this.ctx.getRootLogger(); - - private void checkAttribute(final String name, final String value, final boolean compact, final String str) { - Assert.assertTrue(str, str.contains(name + "=\"" + value + "\"")); - } - - private void checkAttributeName(final String name, final boolean compact, final String str) { - Assert.assertTrue(str, str.contains(name + "=\"")); - } - - private void checkContains(final String expected, final List list) { - for (final String string : list) { - final String trimedLine = string.trim(); - if (trimedLine.contains(expected)) { - return; - } - } - Assert.fail("Cannot find " + expected + " in " + list); - } - - private void checkElement(final String key, final String value, final boolean compact, final String str) { - // - assertTrue(str, str.contains(String.format("", key, value))); - } - - private void checkElementName(final String name, final boolean compact, final String str, final boolean withAttributes, - final boolean withChildren) { - // simple checks, don't try to be too smart here, we're just looking for the names and basic shape. - // start - final String startStr = withAttributes ? "<" + name + " " : "<" + name + ">"; - final int startPos = str.indexOf(startStr); - Assert.assertTrue(str, startPos >= 0); - // end - final String endStr = withChildren ? "" : "/>"; - final int endPos = str.indexOf(endStr, startPos + startStr.length()); - Assert.assertTrue(str, endPos >= 0); - } - - private void checkElementNameAbsent(final String name, final boolean compact, final String str) { - Assert.assertFalse(str.contains("<" + name)); - } - - /** - * @param includeSource TODO - * @param compact - * @param includeContext TODO - * @throws IOException - * @throws JsonParseException - * @throws JsonMappingException - */ - private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean includeContext, final boolean includeStacktrace) throws IOException, - JsonParseException, JsonMappingException { - final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); - final XmlLayout layout = XmlLayout.newBuilder() - .setLocationInfo(includeSource) - .setProperties(includeContext) - .setComplete(false) - .setCompact(compact) - .setIncludeStacktrace(includeStacktrace) - .setCharset(StandardCharsets.UTF_8) - .build(); - final String str = layout.toSerializable(expected); - // System.out.println(str); - assertEquals(str, !compact, str.contains("\n")); - assertEquals(str, includeSource, str.contains("Source")); - assertEquals(str, includeContext, str.contains("ContextMap")); - final Log4jLogEvent actual = new Log4jXmlObjectMapper().readValue(str, Log4jLogEvent.class); - LogEventFixtures.assertEqualLogEvents(expected, actual, includeSource, includeContext, includeStacktrace); - if (includeContext) { - this.checkElement("MDC.A", "A_Value", compact, str); - this.checkElement("MDC.B", "B_Value", compact, str); - } - - // - assertNull(actual.getThrown()); - // check some attrs - assertTrue(str, str.contains("loggerFqcn=\"f.q.c.n\"")); - assertTrue(str, str.contains("loggerName=\"a.B\"")); - // make sure short names are used - assertTrue(str, str.contains(" appenders = this.rootLogger.getAppenders(); - for (final Appender appender : appenders.values()) { - this.rootLogger.removeAppender(appender); - } - // set up appender - final XmlLayout layout = XmlLayout.newBuilder() - .setLocationInfo(true) - .setProperties(true) - .setComplete(true) - .setCompact(false) - .setIncludeStacktrace(true) - .build(); - - final ListAppender appender = new ListAppender("List", null, layout, true, false); - appender.start(); - - // set appender on root and set level to debug - this.rootLogger.addAppender(appender); - this.rootLogger.setLevel(Level.DEBUG); - - // output starting message - this.rootLogger.debug("starting mdc pattern test"); - - this.rootLogger.debug("empty mdc"); - - ThreadContext.put("key1", "value1"); - ThreadContext.put("key2", "value2"); - - this.rootLogger.debug("filled mdc"); - - ThreadContext.remove("key1"); - ThreadContext.remove("key2"); - - this.rootLogger.error("finished mdc pattern test", new NullPointerException("test")); - - final Marker marker = MarkerManager.getMarker("EVENT"); - this.rootLogger.error(marker, "marker test"); - - appender.stop(); - - final List list = appender.getMessages(); - - final String string = list.get(0); - assertTrue("Incorrect header: " + string, string.equals("")); - assertTrue("Incorrect footer", list.get(list.size() - 1).equals("")); - this.checkContains("loggerFqcn=\"" + AbstractLogger.class.getName() + "\"", list); - this.checkContains("level=\"DEBUG\"", list); - this.checkContains(">starting mdc pattern test", list); - // this.checkContains("starting mdc pattern test", list); - - // - this.checkContains("", list); - - for (final Appender app : appenders.values()) { - this.rootLogger.addAppender(app); - } - } - - @Test - public void testLayoutLoggerName() { - final XmlLayout layout = XmlLayout.newBuilder() - .setLocationInfo(false) - .setProperties(true) - .setComplete(true) - .setCompact(false) - .setIncludeStacktrace(true) - .build(); - - final Log4jLogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("a.B") // - .setLoggerFqcn("f.q.c.n") // - .setLevel(Level.DEBUG) // - .setMessage(new SimpleMessage("M")) // - .setThreadName("threadName") // - .setTimeMillis(1).build(); - final String str = layout.toSerializable(event); - assertTrue(str, str.contains("loggerName=\"a.B\"")); - } - - @Test - public void testAdditionalFields() throws Exception { - final AbstractJacksonLayout layout = XmlLayout.newBuilder() - .setLocationInfo(false) - .setProperties(false) - .setIncludeStacktrace(false) - .setAdditionalFields(new KeyValuePair[] { - new KeyValuePair("KEY1", "VALUE1"), - new KeyValuePair("KEY2", "${java:runtime}"), }) - .setCharset(StandardCharsets.UTF_8) - .setConfiguration(ctx.getConfiguration()) - .build(); - final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); - assertTrue(str, str.contains("VALUE1")); - assertTrue(str, str.contains("" + new JavaLookup().getRuntime() + "")); - } - - @Test - public void testLocationOffCompactOffMdcOff() throws Exception { - this.testAllFeatures(false, false, false, true); - } - - @Test - public void testLocationOnCompactOnMdcOn() throws Exception { - this.testAllFeatures(true, true, true, true); - } - - @Test - public void testExcludeStacktrace() throws Exception { - this.testAllFeatures(false, false, false, false); - } - - @Test - public void testStacktraceAsString() throws Exception { - final String str = prepareXMLForStacktraceTests(true); - assertTrue(str, str.contains("java.lang.NullPointerException")); - } - - @Test - public void testStacktraceAsNonString() throws Exception { - final String str = prepareXMLForStacktraceTests(false); - assertTrue(str, str.contains("before the Logger instance is obtained. This use of ThreadContext and the associated - * ContextMapLookup can be used in many other ways in a config file. - */ - @Test - public void testFileLog() throws Exception { - final Logger logger = LogManager.getLogger(); - logger.info("Hello from testFileLog!"); - final File logFile = new File("target", this.getClass().getName() + ".testFileLog.log"); - assertTrue(logFile.exists()); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java deleted file mode 100644 index 09794e21a8e..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.lookup; - -import java.util.Calendar; - -import org.apache.logging.log4j.core.AbstractLogEvent; -import org.apache.logging.log4j.core.LogEvent; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class DateLookupTest { - - - @Test - public void testLookup() { - final StrLookup lookup = new DateLookup(); - final LogEvent event = new MyLogEvent(); - final String value = lookup.lookup(event, "MM/dd/yyyy"); - assertNotNull(value); - assertEquals("12/30/2011", value); - } - - private class MyLogEvent extends AbstractLogEvent { - /** - * Generated serial version ID. - */ - private static final long serialVersionUID = -2663819677970643109L; - - @Override - public long getTimeMillis() { - final Calendar cal = Calendar.getInstance(); - cal.set(2011, 11, 30, 10, 56, 35); - return cal.getTimeInMillis(); - } - - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java deleted file mode 100644 index 9e555636302..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/InterpolatorTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.lookup; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.junit.JndiRule; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.ExternalResource; -import org.junit.rules.RuleChain; - -import static org.junit.Assert.*; - -/** - * - */ -public class InterpolatorTest { - - private static final String TESTKEY = "TestKey"; - private static final String TESTKEY2 = "TestKey2"; - private static final String TESTVAL = "TestValue"; - - private static final String TEST_CONTEXT_RESOURCE_NAME = "logging/context-name"; - private static final String TEST_CONTEXT_NAME = "app-1"; - - @ClassRule - public static RuleChain rules = RuleChain.outerRule(new ExternalResource() { - @Override - protected void before() throws Throwable { - System.setProperty(TESTKEY, TESTVAL); - System.setProperty(TESTKEY2, TESTVAL); - } - - @Override - protected void after() { - System.clearProperty(TESTKEY); - System.clearProperty(TESTKEY2); - } - }).around(new JndiRule( - JndiLookup.CONTAINER_JNDI_RESOURCE_PATH_PREFIX + TEST_CONTEXT_RESOURCE_NAME, TEST_CONTEXT_NAME)); - - @Test - public void testLookup() { - final Map map = new HashMap<>(); - map.put(TESTKEY, TESTVAL); - final StrLookup lookup = new Interpolator(new MapLookup(map)); - ThreadContext.put(TESTKEY, TESTVAL); - String value = lookup.lookup(TESTKEY); - assertEquals(TESTVAL, value); - value = lookup.lookup("ctx:" + TESTKEY); - assertEquals(TESTVAL, value); - value = lookup.lookup("sys:" + TESTKEY); - assertEquals(TESTVAL, value); - value = lookup.lookup("SYS:" + TESTKEY2); - assertEquals(TESTVAL, value); - value = lookup.lookup("BadKey"); - assertNull(value); - ThreadContext.clearMap(); - value = lookup.lookup("ctx:" + TESTKEY); - assertEquals(TESTVAL, value); - value = lookup.lookup("jndi:" + TEST_CONTEXT_RESOURCE_NAME); - assertEquals(TEST_CONTEXT_NAME, value); - } - - private void assertLookupNotEmpty(final StrLookup lookup, final String key) { - final String value = lookup.lookup(key); - assertNotNull(value); - assertFalse(value.isEmpty()); - System.out.println(key + " = " + value); - } - - @Test - public void testLookupWithDefaultInterpolator() { - final StrLookup lookup = new Interpolator(); - String value = lookup.lookup("sys:" + TESTKEY); - assertEquals(TESTVAL, value); - value = lookup.lookup("env:PATH"); - assertNotNull(value); - value = lookup.lookup("jndi:" + TEST_CONTEXT_RESOURCE_NAME); - assertEquals(TEST_CONTEXT_NAME, value); - value = lookup.lookup("date:yyyy-MM-dd"); - assertNotNull("No Date", value); - final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); - final String today = format.format(new Date()); - assertEquals(value, today); - assertLookupNotEmpty(lookup, "java:version"); - assertLookupNotEmpty(lookup, "java:runtime"); - assertLookupNotEmpty(lookup, "java:vm"); - assertLookupNotEmpty(lookup, "java:os"); - assertLookupNotEmpty(lookup, "java:locale"); - assertLookupNotEmpty(lookup, "java:hw"); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupTest.java deleted file mode 100644 index 6150afd467b..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.lookup; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configurator; - -/** - * Tests {@link org.apache.logging.log4j.core.lookup.MainMapLookup#MAIN_SINGLETON} from the command line, not a real - * JUnit test. - * - * From an IDE or CLI: --file foo.txt - * - * @since 2.4 - */ -public class MainInputArgumentsLookupTest { - - public static void main(final String[] args) { - MainMapLookup.setMainArguments(args); - try (final LoggerContext ctx = Configurator.initialize(MainInputArgumentsLookupTest.class.getName(), - "target/test-classes/log4j-lookup-main.xml")) { - LogManager.getLogger().error("this is an error message"); - } - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java deleted file mode 100644 index 61d81ecbc8f..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.lookup; - -import static org.junit.Assert.assertEquals; - -import java.util.HashMap; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.StringMapMessage; -import org.junit.Test; - -/** - * Tests {@link MapLookup}. - */ -public class MapLookupTest { - - @Test - public void testEmptyMap() { - final MapLookup lookup = new MapLookup(new HashMap()); - assertEquals(null, lookup.lookup(null)); - assertEquals(null, lookup.lookup("X")); - } - - @Test - public void testMap() { - final HashMap map = new HashMap<>(); - map.put("A", "B"); - final MapLookup lookup = new MapLookup(map); - assertEquals(null, lookup.lookup(null)); - assertEquals("B", lookup.lookup("A")); - } - - @Test - public void testNullMap() { - final MapLookup lookup = new MapLookup(); - assertEquals(null, lookup.lookup(null)); - assertEquals(null, lookup.lookup("X")); - } - - @Test - public void testMainMap() { - MapLookup.setMainArguments(new String[] { - "--file", - "foo.txt" }); - final MapLookup lookup = MainMapLookup.MAIN_SINGLETON; - assertEquals(null, lookup.lookup(null)); - assertEquals(null, lookup.lookup("X")); - assertEquals("--file", lookup.lookup("0")); - assertEquals("foo.txt", lookup.lookup("1")); - assertEquals("foo.txt", lookup.lookup("--file")); - assertEquals(null, lookup.lookup("foo.txt")); - } - - @Test - public void testEventMapMessage() { - final HashMap map = new HashMap<>(); - map.put("A", "B"); - final HashMap eventMap = new HashMap<>(); - eventMap.put("A1", "B1"); - final StringMapMessage message = new StringMapMessage(eventMap); - final LogEvent event = Log4jLogEvent.newBuilder() - .setMessage(message) - .build(); - final MapLookup lookup = new MapLookup(map); - assertEquals("B", lookup.lookup(event, "A")); - assertEquals("B1", lookup.lookup(event, "A1")); - } - - @Test - public void testNullEvent() { - final HashMap map = new HashMap<>(); - map.put("A", "B"); - final MapLookup lookup = new MapLookup(map); - assertEquals("B", lookup.lookup(null, "A")); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupConfigTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupConfigTest.java deleted file mode 100644 index 62f88976691..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupConfigTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.lookup; - -import java.io.File; -import java.io.IOException; - -import org.apache.commons.io.FileUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Test; - -/** - * Tests {@link MarkerLookup} with a configuration file. - * - * @since 2.4 - */ -public class MarkerLookupConfigTest { - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule("log4j-marker-lookup.yaml"); - public static final Marker PAYLOAD = MarkerManager.getMarker("PAYLOAD"); - private static final String PAYLOAD_LOG = "Message in payload.log"; - - public static final Marker PERFORMANCE = MarkerManager.getMarker("PERFORMANCE"); - - private static final String PERFORMANCE_LOG = "Message in performance.log"; - public static final Marker SQL = MarkerManager.getMarker("SQL"); - private static final String SQL_LOG = "Message in sql.log"; - - @Test - public void test() throws IOException { - final Logger logger = LogManager.getLogger(); - logger.info(SQL, SQL_LOG); - logger.info(PAYLOAD, PAYLOAD_LOG); - logger.info(PERFORMANCE, PERFORMANCE_LOG); - { - final String log = FileUtils.readFileToString(new File("target/logs/sql.log")); - Assert.assertTrue(log.contains(SQL_LOG)); - Assert.assertFalse(log.contains(PAYLOAD_LOG)); - Assert.assertFalse(log.contains(PERFORMANCE_LOG)); - } - { - final String log = FileUtils.readFileToString(new File("target/logs/payload.log")); - Assert.assertFalse(log.contains(SQL_LOG)); - Assert.assertTrue(log.contains(PAYLOAD_LOG)); - Assert.assertFalse(log.contains(PERFORMANCE_LOG)); - } - { - final String log = FileUtils.readFileToString(new File("target/logs/performance.log")); - Assert.assertFalse(log.contains(SQL_LOG)); - Assert.assertFalse(log.contains(PAYLOAD_LOG)); - Assert.assertTrue(log.contains(PERFORMANCE_LOG)); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookupTest.java deleted file mode 100644 index f1aa6bfa204..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookupTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.lookup; - -import org.junit.Assert; -import org.junit.Test; - -/** - * - */ -public class ResourceBundleLookupTest { - - @Test - public void testLookup() { - final StrLookup lookup = new ResourceBundleLookup(); - lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle_en:KeyA"); - Assert.assertEquals("ValueA", lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyA")); - } - - @Test - public void testLookupWithLocale() { - final StrLookup lookup = new ResourceBundleLookup(); - lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyA"); - Assert.assertEquals("ValueA", lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyA")); - } - - public void testMissingKey() { - final StrLookup lookup = new ResourceBundleLookup(); - Assert.assertNull(lookup.lookup("org.apache.logging.log4j.core.lookup.resource-bundle:KeyUnkown")); - } - - @Test - public void testBadFormatBundleOnly() { - final StrLookup lookup = new ResourceBundleLookup(); - Assert.assertNull(lookup.lookup("X")); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java deleted file mode 100644 index 41b7979b486..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/StrSubstitutorTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.lookup; - -import java.util.HashMap; -import java.util.Map; - -import org.apache.logging.log4j.ThreadContext; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class StrSubstitutorTest { - - private static final String TESTKEY = "TestKey"; - private static final String TESTVAL = "TestValue"; - - - @BeforeClass - public static void before() { - System.setProperty(TESTKEY, TESTVAL); - } - - @AfterClass - public static void after() { - System.clearProperty(TESTKEY); - } - - - @Test - public void testLookup() { - final Map map = new HashMap<>(); - map.put(TESTKEY, TESTVAL); - final StrLookup lookup = new Interpolator(new MapLookup(map)); - final StrSubstitutor subst = new StrSubstitutor(lookup); - ThreadContext.put(TESTKEY, TESTVAL); - String value = subst.replace("${TestKey}-${ctx:TestKey}-${sys:TestKey}"); - assertEquals("TestValue-TestValue-TestValue", value); - value = subst.replace("${BadKey}"); - assertEquals("${BadKey}", value); - - value = subst.replace("${BadKey:-Unknown}-${ctx:BadKey:-Unknown}-${sys:BadKey:-Unknown}"); - assertEquals("Unknown-Unknown-Unknown", value); - value = subst.replace("${BadKey:-Unknown}-${ctx:BadKey}-${sys:BadKey:-Unknown}"); - assertEquals("Unknown-${ctx:BadKey}-Unknown", value); - value = subst.replace("${BadKey:-Unknown}-${ctx:BadKey:-}-${sys:BadKey:-Unknown}"); - assertEquals("Unknown--Unknown", value); - } - - @Test - public void testDefault() { - final Map map = new HashMap<>(); - map.put(TESTKEY, TESTVAL); - final StrLookup lookup = new Interpolator(new MapLookup(map)); - final StrSubstitutor subst = new StrSubstitutor(lookup); - ThreadContext.put(TESTKEY, TESTVAL); - //String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}"); - final String value = subst.replace("${sys:TestKey1:-${ctx:TestKey}}"); - assertEquals("TestValue", value); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketMessageLossTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketMessageLossTest.java deleted file mode 100644 index 70ab1222fe5..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketMessageLossTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.net; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.AvailablePortFinder; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.*; - -@Ignore("Currently needs better port choosing support") -public class SocketMessageLossTest { - private static final int SOCKET_PORT = AvailablePortFinder.getNextAvailable(); - - private static final String CONFIG = "log4j-socket2.xml"; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); - - @Test - public void testSocket() throws Exception { - TestSocketServer testServer; - ExecutorService executor = null; - Future futureIn; - - try { - executor = Executors.newSingleThreadExecutor(); - System.err.println("Initializing server"); - testServer = new TestSocketServer(); - futureIn = executor.submit(testServer); - - //System.err.println("Initializing logger"); - final Logger logger = context.getLogger(); - - String message = "Log #1"; - logger.error(message); - - final BufferedReader reader = new BufferedReader(new InputStreamReader(futureIn.get())); - assertEquals(message, reader.readLine()); - - //System.err.println("Closing server"); - closeQuietly(testServer); - assertTrue("Server not shutdown", testServer.server.isClosed()); - - //System.err.println("Sleeping to ensure no race conditions"); - Thread.sleep(1000); - - message = "Log #2"; - try { - logger.error(message); - fail("Expected exception not thrown"); - } catch (final AppenderLoggingException e) { - // An exception is expected. - } - - message = "Log #3"; - try { - logger.error(message); - fail("Expected exception not thrown"); - } catch (final AppenderLoggingException e) { - // An exception is expected. - } - } finally { - closeQuietly(executor); - } - } - - - private static class TestSocketServer implements Callable { - private final ServerSocket server; - private Socket client; - - public TestSocketServer() throws Exception { - server = new ServerSocket(SOCKET_PORT); - } - - @Override - public InputStream call() throws Exception { - client = server.accept(); - return client.getInputStream(); - } - - public void close() { - closeQuietly(client); - closeQuietly(server); - } - - private static void closeQuietly(final ServerSocket socket) { - if (null != socket) { - try { - socket.close(); - } catch (final IOException ignore) { - } - } - } - - private static void closeQuietly(final Socket socket) { - if (null != socket) { - try { - socket.setSoLinger(true, 0); - socket.close(); - } catch (final IOException ignore) { - } - } - } - } - - private static void closeQuietly(final ExecutorService executor) { - if (null != executor) { - executor.shutdownNow(); - } - } - - private static void closeQuietly(final TestSocketServer testServer) { - if (null != testServer) { - testServer.close(); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java deleted file mode 100644 index 151d4442e7b..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.net; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.ArrayList; -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.AvailablePortFinder; -import org.apache.logging.log4j.util.Strings; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.*; - -@Ignore("Currently needs better port choosing support") -public class SocketReconnectTest { - private static final int SOCKET_PORT = AvailablePortFinder.getNextAvailable(); - - private static final String CONFIG = "log4j-socket.xml"; - - private static final String SHUTDOWN = "Shutdown" + Strings.LINE_SEPARATOR + - "................................................................" + Strings.LINE_SEPARATOR + - "................................................................" + Strings.LINE_SEPARATOR + - "................................................................" + Strings.LINE_SEPARATOR + - "................................................................" + Strings.LINE_SEPARATOR; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); - - @Test - public void testReconnect() throws Exception { - - final List list = new ArrayList<>(); - TestSocketServer server = new TestSocketServer(list); - server.start(); - Thread.sleep(300); - - //System.err.println("Initializing logger"); - final Logger logger = context.getLogger(); - - String message = "Log #1"; - logger.error(message); - final String expectedHeader = "Header"; - - String msg = null; - String header = null; - for (int i = 0; i < 5; ++i) { - Thread.sleep(100); - if (list.size() > 1) { - header = list.get(0); - msg = list.get(1); - break; - } - } - assertNotNull("No header", header); - assertEquals(expectedHeader, header); - assertNotNull("No message", msg); - assertEquals(message, msg); - - logger.error(SHUTDOWN); - server.join(); - - list.clear(); - - message = "Log #2"; - boolean exceptionCaught = false; - - for (int i = 0; i < 100; ++i) { - try { - logger.error(message); - } catch (final AppenderLoggingException e) { - exceptionCaught = true; - break; - // System.err.println("Caught expected exception"); - } - } - assertTrue("No Exception thrown", exceptionCaught); - message = "Log #3"; - - - server = new TestSocketServer(list); - server.start(); - Thread.sleep(300); - - msg = null; - header = null; - logger.error(message); - for (int i = 0; i < 5; ++i) { - Thread.sleep(100); - if (list.size() > 1) { - header = list.get(0); - msg = list.get(1); - break; - } - } - assertNotNull("No header", header); - assertEquals(expectedHeader, header); - assertNotNull("No message", msg); - assertEquals(message, msg); - logger.error(SHUTDOWN); - server.join(); - } - - - private static class TestSocketServer extends Thread { - private volatile boolean shutdown = false; - private final List list; - private Socket client; - - public TestSocketServer(final List list) { - this.list = list; - } - - @Override - public void run() { - ServerSocket server = null; - client = null; - try { - server = new ServerSocket(SOCKET_PORT); - client = server.accept(); - while (!shutdown) { - final BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream())); - final String line = reader.readLine(); - if (line.equals("Shutdown")) { - shutdown = true; - } else { - list.add(line); - } - } - } catch (final Exception ex) { - ex.printStackTrace(); - } finally { - if (client != null) { - try { - client.close(); - } catch (final Exception ex) { - System.out.println("Unable to close socket " + ex.getMessage()); - } - } - if (server != null) { - try { - server.close(); - } catch (final Exception ex) { - System.out.println("Unable to close server socket " + ex.getMessage()); - } - } - } - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketTest.java deleted file mode 100644 index bcc207c55bb..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.net; - -import java.io.IOException; -import java.io.InputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.concurrent.Callable; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.appender.AppenderLoggingException; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.AvailablePortFinder; -import org.junit.ClassRule; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.*; - -@Ignore("Currently needs better port choosing support") -public class SocketTest { - private static final int SOCKET_PORT = AvailablePortFinder.getNextAvailable(); - - private static final String CONFIG = "log4j-socket.xml"; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); - - @Test - public void testConnect() throws Exception { - // TODO: there's a JUnit rule that simplifies this (matt) - System.err.println("Initializing logger"); - Logger logger = null; - try { - logger = context.getLogger(); - } catch (final NullPointerException e) { - fail("Unexpected exception; should not occur until first logging statement " + e.getMessage()); - } - - final String message = "Log #1"; - try { - logger.error(message); - fail("Expected exception not thrown"); - } catch (final AppenderLoggingException e) { - //System.err.println("Expected exception here, but already errored out when initializing logger"); - } - } - - private static class TestSocketServer implements Callable { - private ServerSocket server; - private Socket client; - - @Override - public InputStream call() throws Exception { - server = new ServerSocket(SOCKET_PORT); - client = server.accept(); - return client.getInputStream(); - } - - public void close() { - closeQuietly(client); - closeQuietly(server); - } - - private static void closeQuietly(final ServerSocket socket) { - if (null != socket) { - try { - socket.close(); - } catch (final IOException ignore) { - } - } - } - - private static void closeQuietly(final Socket socket) { - if (null != socket) { - try { - socket.close(); - } catch (final IOException ignore) { - } - } - } - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java deleted file mode 100644 index d5761ac6bfa..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfigurationTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.net.ssl; - -import java.security.KeyStore; - -import org.junit.Assert; -import org.junit.Test; - -public class KeyStoreConfigurationTest { - @Test(expected = StoreConfigurationException.class) - public void loadEmptyConfigurationDeprecated() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(null, TestConstants.NULL_PWD, null, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertTrue(ks == null); - } - @Test(expected = StoreConfigurationException.class) - public void loadEmptyConfiguration() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(null, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertTrue(ks == null); - } - - @Test - public void loadNotEmptyConfigurationDeprecated() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), - TestConstants.KEYSTORE_TYPE, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertTrue(ks != null); - } - - @Test - public void loadNotEmptyConfiguration() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), - TestConstants.KEYSTORE_TYPE, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertTrue(ks != null); - } - - @Test - public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), - TestConstants.KEYSTORE_TYPE, null); - final KeyStore ks = ksc.getKeyStore(); - final KeyStore ks2 = ksc.getKeyStore(); - Assert.assertTrue(ks == ks2); - } - - @Test - public void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), - TestConstants.KEYSTORE_TYPE, null); - final KeyStore ks = ksc.getKeyStore(); - final KeyStore ks2 = ksc.getKeyStore(); - Assert.assertTrue(ks == ks2); - } - - @Test(expected = StoreConfigurationException.class) - public void wrongPasswordDeprecated() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, "wrongPassword!", null, null); - ksc.getKeyStore(); - } - - @Test(expected = StoreConfigurationException.class) - public void wrongPassword() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, new MemoryPasswordProvider("wrongPassword!".toCharArray()), null, null); - ksc.getKeyStore(); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java deleted file mode 100644 index 41349a00391..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.net.ssl; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - -import org.junit.Assert; -import org.junit.Test; - -public class SslConfigurationTest { - - private static final String TLS_TEST_HOST = "login.yahoo.com"; - private static final int TLS_TEST_PORT = 443; - - public static SslConfiguration createTestSslConfigurationResourcesDeprecated() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE_RESOURCE, - TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null); - final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE_RESOURCE, - TestConstants.TRUSTSTORE_PWD(), null, null); - return SslConfiguration.createSSLConfiguration(null, ksc, tsc); - } - - public static SslConfiguration createTestSslConfigurationResources() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE_RESOURCE, - new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), TestConstants.KEYSTORE_TYPE, null); - final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE_RESOURCE, - new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); - return SslConfiguration.createSSLConfiguration(null, ksc, tsc); - } - - public static SslConfiguration createTestSslConfigurationFilesDeprecated() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, - TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null); - final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, - TestConstants.TRUSTSTORE_PWD(), null, null); - return SslConfiguration.createSSLConfiguration(null, ksc, tsc); - } - - public static SslConfiguration createTestSslConfigurationFiles() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, - new MemoryPasswordProvider(TestConstants.KEYSTORE_PWD()), TestConstants.KEYSTORE_TYPE, null); - final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, - new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); - return SslConfiguration.createSSLConfiguration(null, ksc, tsc); - } - - @Test - public void testGettersFromScratchFiles() throws StoreConfigurationException { - Assert.assertNotNull(createTestSslConfigurationFiles().getProtocol()); - Assert.assertNotNull(createTestSslConfigurationFiles().getKeyStoreConfig()); - Assert.assertNotNull(createTestSslConfigurationFiles().getSslContext()); - Assert.assertNotNull(createTestSslConfigurationFiles().getSslSocketFactory()); - Assert.assertNotNull(createTestSslConfigurationFiles().getTrustStoreConfig()); - } - - @Test - public void testGettersFromScratchResources() throws StoreConfigurationException { - Assert.assertNotNull(createTestSslConfigurationResources().getProtocol()); - Assert.assertNotNull(createTestSslConfigurationResources().getKeyStoreConfig()); - Assert.assertNotNull(createTestSslConfigurationResources().getSslContext()); - Assert.assertNotNull(createTestSslConfigurationResources().getSslSocketFactory()); - Assert.assertNotNull(createTestSslConfigurationResources().getTrustStoreConfig()); - } - - @Test - public void equals() { - Assert.assertEquals(SslConfiguration.createSSLConfiguration(null, null, null), SslConfiguration.createSSLConfiguration(null, null, null)); - } - - @Test - public void emptyConfigurationDoesntCauseNullSSLSocketFactory() { - final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); - final SSLSocketFactory factory = sc.getSslSocketFactory(); - Assert.assertNotNull(factory); - } - - @Test - public void emptyConfigurationHasDefaultTrustStore() throws IOException { - final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, null); - final SSLSocketFactory factory = sc.getSslSocketFactory(); - try { - try (final SSLSocket clientSocket = (SSLSocket) factory.createSocket(TLS_TEST_HOST, TLS_TEST_PORT)) { - Assert.assertNotNull(clientSocket); - clientSocket.close(); - } - } catch (final UnknownHostException offline) { - // this exception is thrown on Windows when offline - } - } - - @Test - public void connectionFailsWithoutValidServerCertificate() throws IOException, StoreConfigurationException { - final TrustStoreConfiguration tsc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, - new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); - final SslConfiguration sc = SslConfiguration.createSSLConfiguration(null, null, tsc); - final SSLSocketFactory factory = sc.getSslSocketFactory(); - try { - try (final SSLSocket clientSocket = (SSLSocket) factory.createSocket(TLS_TEST_HOST, TLS_TEST_PORT)) { - try (final OutputStream os = clientSocket.getOutputStream()) { - os.write("GET config/login_verify2?".getBytes()); - Assert.fail("Expected IOException"); - } catch (final IOException e) { - // Expected, do nothing. - } - } - } catch (final UnknownHostException offline) { - // this exception is thrown on Windows when offline - } - } - - @Test - public void loadKeyStoreWithoutPassword() throws StoreConfigurationException { - final KeyStoreConfiguration ksc = new KeyStoreConfiguration(TestConstants.KEYSTORE_FILE, - new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); - final SslConfiguration sslConf = SslConfiguration.createSSLConfiguration(null, ksc, null); - final SSLSocketFactory factory = sslConf.getSslSocketFactory(); - Assert.assertNotNull(factory); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TestConstants.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TestConstants.java deleted file mode 100644 index 90b3bed7c40..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TestConstants.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.net.ssl; - -public class TestConstants { - - public static final String SOURCE_FOLDER = "src/test/resources/"; - public static final String RESOURCE_ROOT = "org/apache/logging/log4j/core/net/ssl/"; - - public static final String PATH = SOURCE_FOLDER + RESOURCE_ROOT; - public static final String TRUSTSTORE_PATH = PATH; - public static final String TRUSTSTORE_RESOURCE = RESOURCE_ROOT; - public static final String TRUSTSTORE_FILE = TRUSTSTORE_PATH + "truststore.jks"; - public static final String TRUSTSTORE_FILE_RESOURCE = TRUSTSTORE_RESOURCE + "truststore.jks"; - public static final char[] TRUSTSTORE_PWD() { return "changeit".toCharArray(); } - public static final String TRUSTSTORE_TYPE = "JKS"; - - public static final String KEYSTORE_PATH = PATH; - public static final String KEYSTORE_RESOURCE = RESOURCE_ROOT; - public static final String KEYSTORE_FILE = KEYSTORE_PATH + "client.log4j2-keystore.jks"; - public static final String KEYSTORE_FILE_RESOURCE = KEYSTORE_RESOURCE + "client.log4j2-keystore.jks"; - public static final char[] KEYSTORE_PWD() { return "changeit".toCharArray(); } - public static final String KEYSTORE_TYPE = "JKS"; - - public static final char[] NULL_PWD = null; -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java deleted file mode 100644 index d27a1fdbb14..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfigurationTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.net.ssl; - -import java.security.KeyStore; - -import org.junit.Assert; -import org.junit.Test; - -public class TrustStoreConfigurationTest { - @Test(expected = StoreConfigurationException.class) - public void loadEmptyConfigurationDeprecated() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(null, TestConstants.NULL_PWD, null, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertTrue(ks == null); - } - @Test(expected = StoreConfigurationException.class) - public void loadEmptyConfiguration() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(null, new MemoryPasswordProvider(TestConstants.NULL_PWD), null, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertTrue(ks == null); - } - - @Test - public void loadConfigurationDeprecated() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertNotNull(ks); - } - - @Test - public void loadConfiguration() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); - final KeyStore ks = ksc.getKeyStore(); - Assert.assertNotNull(ks); - } - - @Test - public void returnTheSameKeyStoreAfterMultipleLoadsDeprecated() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null, null); - final KeyStore ks = ksc.getKeyStore(); - final KeyStore ks2 = ksc.getKeyStore(); - Assert.assertTrue(ks == ks2); - } - - @Test - public void returnTheSameKeyStoreAfterMultipleLoads() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider(TestConstants.TRUSTSTORE_PWD()), null, null); - final KeyStore ks = ksc.getKeyStore(); - final KeyStore ks2 = ksc.getKeyStore(); - Assert.assertTrue(ks == ks2); - } - - @Test(expected = StoreConfigurationException.class) - public void wrongPasswordDeprecated() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, "wrongPassword!".toCharArray(), null, null); - ksc.getKeyStore(); - Assert.assertTrue(false); - } - - @Test(expected = StoreConfigurationException.class) - public void wrongPassword() throws StoreConfigurationException { - final TrustStoreConfiguration ksc = new TrustStoreConfiguration(TestConstants.TRUSTSTORE_FILE, new MemoryPasswordProvider("wrongPassword!".toCharArray()), null, null); - ksc.getKeyStore(); - Assert.assertTrue(false); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java deleted file mode 100644 index cdb3bef3359..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/parser/LogEventParserTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.parser; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.LogEvent; - -import java.util.Arrays; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -public abstract class LogEventParserTest { - protected void assertLogEvent(final LogEvent logEvent) { - assertThat(logEvent, is(notNullValue())); - assertThat(logEvent.getInstant().getEpochMillisecond(), equalTo(1493121664118L)); - assertThat(logEvent.getThreadName(), equalTo("main")); - assertThat(logEvent.getThreadId(), equalTo(1L)); - assertThat(logEvent.getThreadPriority(), equalTo(5)); - assertThat(logEvent.getLevel(), equalTo(Level.INFO)); - assertThat(logEvent.getLoggerName(), equalTo("HelloWorld")); - assertThat(logEvent.getMarker().getName(), equalTo("child")); - assertThat(logEvent.getMarker().getParents()[0].getName(), equalTo("parent")); - assertThat(logEvent.getMarker().getParents()[0].getParents()[0].getName(), - equalTo("grandparent")); - assertThat(logEvent.getMessage().getFormattedMessage(), equalTo("Hello, world!")); - assertThat(logEvent.getThrown(), is(nullValue())); - assertThat(logEvent.getThrownProxy().getMessage(), equalTo("error message")); - assertThat(logEvent.getThrownProxy().getName(), equalTo("java.lang.RuntimeException")); - assertThat(logEvent.getThrownProxy().getExtendedStackTrace()[0].getClassName(), - equalTo("logtest.Main")); - assertThat(logEvent.getLoggerFqcn(), equalTo("org.apache.logging.log4j.spi.AbstractLogger")); - assertThat(logEvent.getContextStack().asList(), equalTo(Arrays.asList("one", "two"))); - assertThat((String) logEvent.getContextData().getValue("foo"), equalTo("FOO")); - assertThat((String) logEvent.getContextData().getValue("bar"), equalTo("BAR")); - assertThat(logEvent.getSource().getClassName(), equalTo("logtest.Main")); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/CallerInformationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/CallerInformationTest.java deleted file mode 100644 index 835ab4e43e1..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/CallerInformationTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.ClassRule; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class CallerInformationTest { - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule("log4j2-calling-class.xml"); - - @Test - public void testClassLogger() throws Exception { - final ListAppender app = context.getListAppender("Class").clear(); - final Logger logger = context.getLogger("ClassLogger"); - logger.info("Ignored message contents."); - logger.warn("Verifying the caller class is still correct."); - logger.error("Hopefully nobody breaks me!"); - final List messages = app.getMessages(); - assertEquals("Incorrect number of messages.", 3, messages.size()); - for (final String message : messages) { - assertEquals("Incorrect caller class name.", this.getClass().getName(), message); - } - } - - @Test - public void testMethodLogger() throws Exception { - final ListAppender app = context.getListAppender("Method").clear(); - final Logger logger = context.getLogger("MethodLogger"); - logger.info("More messages."); - logger.warn("CATASTROPHE INCOMING!"); - logger.error("ZOMBIES!!!"); - logger.fatal("brains~~~"); - logger.info("Itchy. Tasty."); - final List messages = app.getMessages(); - assertEquals("Incorrect number of messages.", 5, messages.size()); - for (final String message : messages) { - assertEquals("Incorrect caller method name.", "testMethodLogger", message); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java deleted file mode 100644 index 728254a827a..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collection; -import java.util.Date; -import java.util.TimeZone; - -import org.apache.logging.log4j.core.AbstractLogEvent; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.util.Constants; -import org.apache.logging.log4j.core.time.Instant; -import org.apache.logging.log4j.core.time.MutableInstant; -import org.apache.logging.log4j.core.util.datetime.FixedDateFormat; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import static org.junit.Assert.*; - -@RunWith(Parameterized.class) -public class DatePatternConverterTest { - - /** - * SimpleTimePattern for DEFAULT. - */ - private static final String DEFAULT_PATTERN = FixedDateFormat.FixedFormat.DEFAULT.getPattern(); - - /** - * ISO8601 string literal. - */ - private static final String ISO8601_FORMAT = FixedDateFormat.FixedFormat.ISO8601.name(); - - private static final String[] ISO8601_FORMAT_OPTIONS = {ISO8601_FORMAT}; - - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList(new Object[][]{{Boolean.TRUE}, {Boolean.FALSE}}); - } - - public DatePatternConverterTest(final Boolean threadLocalEnabled) throws Exception { - // Setting the system property does not work: the Constant field has already been initialized... - //System.setProperty("log4j2.enable.threadlocals", threadLocalEnabled.toString()); - - final Field field = Constants.class.getDeclaredField("ENABLE_THREADLOCALS"); - field.setAccessible(true); // make non-private - - final Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); // make non-final - - field.setBoolean(null, threadLocalEnabled.booleanValue()); - } - - @Test - public void testNewInstanceAllowsNullParameter() { - DatePatternConverter.newInstance(null); // no errors - } - - @Test - public void testFormatLogEventStringBuilderDefaultPattern() { - final LogEvent event = new MyLogEvent(); - final DatePatternConverter converter = DatePatternConverter.newInstance(null); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - - final String expected = "2011-12-30 10:56:35,987"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatLogEventStringBuilderIso8601() { - final LogEvent event = new MyLogEvent(); - final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - - final String expected = "2011-12-30T10:56:35,987"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatLogEventStringBuilderIso8601TimezoneUTC() { - final LogEvent event = new MyLogEvent(); - final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {"ISO8601", "UTC"}); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - - final TimeZone tz = TimeZone.getTimeZone("UTC"); - final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern()); - sdf.setTimeZone(tz); - final long adjusted = event.getTimeMillis() + tz.getDSTSavings(); - final String expected = sdf.format(new Date(adjusted)); - // final String expected = "2011-12-30T09:56:35,987"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatLogEventStringBuilderIso8601TimezoneJST() { - final LogEvent event = new MyLogEvent(); - final String[] optionsWithTimezone = {ISO8601_FORMAT, "JST"}; - final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - - // JST=Japan Standard Time: UTC+9:00 - final TimeZone tz = TimeZone.getTimeZone("JST"); - final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern()); - sdf.setTimeZone(tz); - final long adjusted = event.getTimeMillis() + tz.getDSTSavings(); - final String expected = sdf.format(new Date(adjusted)); - // final String expected = "2011-12-30T18:56:35,987"; // in CET (Central Eastern Time: Amsterdam) - assertEquals(expected, sb.toString()); - } - - @Test - public void testPredefinedFormatWithTimezone() { - for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { - final String[] options = {format.name(), "PDT"}; // Pacific Daylight Time=UTC-8:00 - final DatePatternConverter converter = DatePatternConverter.newInstance(options); - assertEquals(format.getPattern(), converter.getPattern()); - } - } - - @Test - public void testPredefinedFormatWithoutTimezone() { - for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { - final String[] options = {format.name()}; - final DatePatternConverter converter = DatePatternConverter.newInstance(options); - assertEquals(format.getPattern(), converter.getPattern()); - } - } - - private String precisePattern(final String pattern, int precision) { - String seconds = pattern.substring(0, pattern.indexOf("SSS")); - return seconds + "nnnnnnnnn".substring(0, precision); - } - - // test with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision) - @Test - public void testPredefinedFormatWithAnyValidNanoPrecision() { - final StringBuilder precise = new StringBuilder(); - final StringBuilder milli = new StringBuilder(); - final LogEvent event = new MyLogEvent(); - - for (String timeZone : new String[]{"PDT", null}) { // Pacific Daylight Time=UTC-8:00 - for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { - for (int i = 1; i <= 9; i++) { - if (format.getPattern().endsWith("n")) { - continue; // ignore patterns that already have precise time formats - } - precise.setLength(0); - milli.setLength(0); - - final String[] preciseOptions = {precisePattern(format.getPattern(), i), timeZone}; - final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions); - preciseConverter.format(event, precise); - - final String[] milliOptions = {format.getPattern(), timeZone}; - DatePatternConverter.newInstance(milliOptions).format(event, milli); - milli.setLength(milli.length() - 3); // truncate millis - String expected = milli.append("987123456".substring(0, i)).toString(); - - assertEquals(expected, precise.toString()); - //System.out.println(preciseOptions[0] + ": " + precise); - } - } - } - } - - @Test - public void testInvalidLongPatternIgnoresExcessiveDigits() { - final StringBuilder precise = new StringBuilder(); - final StringBuilder milli = new StringBuilder(); - final LogEvent event = new MyLogEvent(); - - for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) { - if (format.getPattern().endsWith("n")) { - continue; // ignore patterns that already have precise time formats - } - precise.setLength(0); - milli.setLength(0); - - final String pattern = format.getPattern().substring(0, format.getPattern().indexOf("SSS")); - final String[] preciseOptions = {pattern + "nnnnnnnnn" + "n"}; // too long - final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions); - preciseConverter.format(event, precise); - - final String[] milliOptions = {format.getPattern()}; - DatePatternConverter.newInstance(milliOptions).format(event, milli); - milli.setLength(milli.length() - 3); // truncate millis - String expected = milli.append("987123456").toString(); - - assertEquals(expected, precise.toString()); - //System.out.println(preciseOptions[0] + ": " + precise); - } - } - - private class MyLogEvent extends AbstractLogEvent { - private static final long serialVersionUID = 0; - - @Override - public long getTimeMillis() { - final Calendar cal = Calendar.getInstance(); - cal.set(2011, Calendar.DECEMBER, 30, 10, 56, 35); - cal.set(Calendar.MILLISECOND, 987); - return cal.getTimeInMillis(); - } - - @Override - public Instant getInstant() { - MutableInstant result = new MutableInstant(); - result.initFromEpochMilli(getTimeMillis(), 123456); - return result; - } - } - - @Test - public void testFormatObjectStringBuilderDefaultPattern() { - final DatePatternConverter converter = DatePatternConverter.newInstance(null); - final StringBuilder sb = new StringBuilder(); - converter.format("nondate", sb); - - final String expected = ""; // only process dates - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatDateStringBuilderDefaultPattern() { - final DatePatternConverter converter = DatePatternConverter.newInstance(null); - final StringBuilder sb = new StringBuilder(); - converter.format(date(2001, 1, 1), sb); - - final String expected = "2001-02-01 14:15:16,123"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatDateStringBuilderIso8601() { - final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS); - final StringBuilder sb = new StringBuilder(); - converter.format(date(2001, 1, 1), sb); - - final String expected = "2001-02-01T14:15:16,123"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatDateStringBuilderIso8601WithPeriod() { - final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD.name()}; - final DatePatternConverter converter = DatePatternConverter.newInstance(pattern); - final StringBuilder sb = new StringBuilder(); - converter.format(date(2001, 1, 1), sb); - - final String expected = "2001-02-01T14:15:16.123"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatDateStringBuilderIso8601BasicWithPeriod() { - final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_BASIC_PERIOD.name()}; - final DatePatternConverter converter = DatePatternConverter.newInstance(pattern); - final StringBuilder sb = new StringBuilder(); - converter.format(date(2001, 1, 1), sb); - - final String expected = "20010201T141516.123"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatDateStringBuilderOriginalPattern() { - final String[] pattern = {"yyyy/MM/dd HH-mm-ss.SSS"}; - final DatePatternConverter converter = DatePatternConverter.newInstance(pattern); - final StringBuilder sb = new StringBuilder(); - converter.format(date(2001, 1, 1), sb); - - final String expected = "2001/02/01 14-15-16.123"; - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatStringBuilderObjectArrayDefaultPattern() { - final DatePatternConverter converter = DatePatternConverter.newInstance(null); - final StringBuilder sb = new StringBuilder(); - converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3)); - - final String expected = "2001-02-01 14:15:16,123"; // only process first date - assertEquals(expected, sb.toString()); - } - - @Test - public void testFormatStringBuilderObjectArrayIso8601() { - final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS); - final StringBuilder sb = new StringBuilder(); - converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3)); - - final String expected = "2001-02-01T14:15:16,123"; // only process first date - assertEquals(expected, sb.toString()); - } - - private Date date(final int year, final int month, final int date) { - final Calendar cal = Calendar.getInstance(); - cal.set(year, month, date, 14, 15, 16); - cal.set(Calendar.MILLISECOND, 123); - return cal.getTime(); - } - - @Test - public void testGetPatternReturnsDefaultForNullOptions() { - assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(null).getPattern()); - } - - @Test - public void testGetPatternReturnsDefaultForEmptyOptionsArray() { - assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[0]).getPattern()); - } - - @Test - public void testGetPatternReturnsDefaultForSingleNullElementOptionsArray() { - assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[1]).getPattern()); - } - - @Test - public void testGetPatternReturnsDefaultForTwoNullElementsOptionsArray() { - assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[2]).getPattern()); - } - - @Test - public void testGetPatternReturnsDefaultForInvalidPattern() { - final String[] invalid = {"ABC I am not a valid date pattern"}; - assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern()); - } - - @Test - public void testGetPatternReturnsNullForUnix() { - final String[] options = {"UNIX"}; - assertNull(DatePatternConverter.newInstance(options).getPattern()); - } - - @Test - public void testGetPatternReturnsNullForUnixMillis() { - final String[] options = {"UNIX_MILLIS"}; - assertNull(DatePatternConverter.newInstance(options).getPattern()); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DisableAnsiTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DisableAnsiTest.java deleted file mode 100644 index 7fea6fec7e4..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DisableAnsiTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.Strings; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.*; - -public class DisableAnsiTest { - - private static final String EXPECTED = - "ERROR LoggerTest o.a.l.l.c.p.DisableAnsiTest org.apache.logging.log4j.core.pattern.DisableAnsiTest" - + Strings.LINE_SEPARATOR; - - @Rule - public LoggerContextRule init = new LoggerContextRule("log4j2-console-disableAnsi.xml"); - - private Logger logger; - private ListAppender app; - - @Before - public void setUp() throws Exception { - this.logger = this.init.getLogger("LoggerTest"); - this.app = this.init.getListAppender("List").clear(); - } - - @Test - public void testReplacement() { - logger.error(this.getClass().getName()); - - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0), msgs.get(0).endsWith(EXPECTED)); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverterTest.java deleted file mode 100644 index 445b8937214..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverterTest.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.List; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.impl.ThrowableFormatOptions; -import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.apache.logging.log4j.message.SimpleMessage; -import org.apache.logging.log4j.util.Strings; -import org.junit.Test; - -/** - * - */ -public class ExtendedThrowablePatternConverterTest { - - @Test - public void testSuffixFromNormalPattern() { - final String suffix = "suffix(%mdc{key})"; - ThreadContext.put("key", "test suffix "); - final String[] options = {suffix}; - final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("testLogger") // - .setLoggerFqcn(this.getClass().getName()) // - .setLevel(Level.DEBUG) // - .setMessage(new SimpleMessage("test exception")) // - .setThrown(parent).build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final String result = sb.toString(); - assertTrue("No suffix", result.contains("test suffix")); - } - - @Test - public void testSuffix() { - final String suffix = "suffix(test suffix)"; - final String[] options = {suffix}; - final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("testLogger") // - .setLoggerFqcn(this.getClass().getName()) // - .setLevel(Level.DEBUG) // - .setMessage(new SimpleMessage("test exception")) // - .setThrown(parent).build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final String result = sb.toString(); - assertTrue("No suffix", result.contains("test suffix")); - } - - @Test - public void testSuffixWillIgnoreThrowablePattern() { - final String suffix = "suffix(%xEx{suffix(inner suffix)})"; - final String[] options = {suffix}; - final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("testLogger") // - .setLoggerFqcn(this.getClass().getName()) // - .setLevel(Level.DEBUG) // - .setMessage(new SimpleMessage("test exception")) // - .setThrown(parent).build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final String result = sb.toString(); - assertFalse("Has unexpected suffix", result.contains("inner suffix")); - } - - @Test - public void testDeserializedLogEventWithThrowableProxyButNoThrowable() { - final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, null); - final Throwable originalThrowable = new Exception("something bad happened"); - final ThrowableProxy throwableProxy = new ThrowableProxy(originalThrowable); - final Throwable deserializedThrowable = null; - final Log4jLogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("testLogger") // - .setLoggerFqcn(this.getClass().getName()) // - .setLevel(Level.DEBUG) // - .setMessage(new SimpleMessage("")) // - .setThrown(deserializedThrowable) // - .setThrownProxy(throwableProxy) // - .setTimeMillis(0).build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final String result = sb.toString(); - assertTrue(result, result.contains(originalThrowable.getMessage())); - assertTrue(result, result.contains(originalThrowable.getStackTrace()[0].getMethodName())); - } - - @Test - public void testFiltering() { - final String packages = "filters(org.junit, org.apache.maven, sun.reflect, java.lang.reflect)"; - final String[] options = {packages}; - final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, options); - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("testLogger") // - .setLoggerFqcn(this.getClass().getName()) // - .setLevel(Level.DEBUG) // - .setMessage(new SimpleMessage("test exception")) // - .setThrown(parent).build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final String result = sb.toString(); - assertTrue("No suppressed lines", result.contains(" suppressed ")); - } - - @Test - public void testFull() { - final ExtendedThrowablePatternConverter converter = ExtendedThrowablePatternConverter.newInstance(null, null); - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("testLogger") // - .setLoggerFqcn(this.getClass().getName()) // - .setLevel(Level.DEBUG) // - .setMessage(new SimpleMessage("test exception")) // - .setThrown(parent).build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw); - parent.printStackTrace(pw); - String result = sb.toString(); - result = result.replaceAll(" ~?\\[.*\\]", Strings.EMPTY); - final String expected = sw.toString(); //.replaceAll("\r", Strings.EMPTY); - assertEquals(expected, result); - } - - @Test - public void testFiltersAndSeparator() { - final ExtendedThrowablePatternConverter exConverter = ExtendedThrowablePatternConverter.newInstance(null, - new String[] { "full", "filters(org.junit,org.eclipse)", "separator(|)" }); - final ThrowableFormatOptions options = exConverter.getOptions(); - final List ignorePackages = options.getIgnorePackages(); - assertNotNull(ignorePackages); - final String ignorePackagesString = ignorePackages.toString(); - assertTrue(ignorePackagesString, ignorePackages.contains("org.junit")); - assertTrue(ignorePackagesString, ignorePackages.contains("org.eclipse")); - assertEquals("|", options.getSeparator()); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowableTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowableTest.java deleted file mode 100644 index affeae41463..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ExtendedThrowableTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class ExtendedThrowableTest { - private ListAppender app; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule("log4j-throwablefilter.xml"); - - @Before - public void setUp() throws Exception { - this.app = context.getListAppender("List").clear(); - } - - @Test - public void testException() { - final Logger logger = context.getLogger("LoggerTest"); - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - logger.error("Exception", parent); - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("No suppressed lines", msgs.get(0).contains("suppressed")); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/FormattingInfoTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/FormattingInfoTest.java deleted file mode 100644 index 370db65427e..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/FormattingInfoTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -/** - * Testing FormattingInfo. - */ -public class FormattingInfoTest { - - @Test - public void testFormatTruncateFromBeginning() { - final StringBuilder message = new StringBuilder("Hello, world"); - - final FormattingInfo formattingInfo = new FormattingInfo(false, 0, 5, true); - formattingInfo.format(0, message); - - assertEquals("world", message.toString()); - } - - @Test - public void testFormatTruncateFromEnd() { - final StringBuilder message = new StringBuilder("Hello, world"); - - final FormattingInfo formattingInfo = new FormattingInfo(false, 0, 5, false); - formattingInfo.format(0, message); - - assertEquals("Hello", message.toString()); - } - - @Test - public void testFormatTruncateFromEndGivenFieldStart() { - final StringBuilder message = new StringBuilder("2015-03-09 11:49:28,295; INFO org.apache.logging.log4j.PatternParserTest"); - - final FormattingInfo formattingInfo = new FormattingInfo(false, 0, 5, false); - formattingInfo.format(31, message); - - assertEquals("2015-03-09 11:49:28,295; INFO org.a", message.toString()); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java deleted file mode 100644 index c3b1ba69783..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.apache.logging.log4j.core.pattern;/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.Assert; -import org.junit.Test; - - -import static org.junit.Assert.*; - -/** - * Tests the HighlightConverter. - */ -public class HighlightConverterTest { - - @Test - public void testAnsiEmpty() { - final String[] options = {"", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false"}; - final HighlightConverter converter = HighlightConverter.newInstance(null, options); - - final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( - new SimpleMessage("message in a bottle")).build(); - final StringBuilder buffer = new StringBuilder(); - converter.format(event, buffer); - assertEquals("", buffer.toString()); - } - - @Test - public void testAnsiNonEmpty() { - final String[] options = {"%-5level: %msg", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false"}; - final HighlightConverter converter = HighlightConverter.newInstance(null, options); - - final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( - new SimpleMessage("message in a bottle")).build(); - final StringBuilder buffer = new StringBuilder(); - converter.format(event, buffer); - assertEquals("\u001B[32mINFO : message in a bottle\u001B[m", buffer.toString()); - } - - @Test - public void testLevelNamesBad() { - String colorName = "red"; - final String[] options = { "%-5level: %msg", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " - + PatternParser.DISABLE_ANSI + "=false, " + "BAD_LEVEL_A=" + colorName + ", BAD_LEVEL_B=" + colorName }; - final HighlightConverter converter = HighlightConverter.newInstance(null, options); - Assert.assertNotNull(converter); - Assert.assertNotNull(converter.getLevelStyle(Level.TRACE)); - Assert.assertNotNull(converter.getLevelStyle(Level.DEBUG)); - } - - @Test - public void testLevelNamesGood() { - String colorName = "red"; - final String[] options = { "%-5level: %msg", PatternParser.NO_CONSOLE_NO_ANSI + "=false, " - + PatternParser.DISABLE_ANSI + "=false, " + "DEBUG=" + colorName + ", TRACE=" + colorName }; - final HighlightConverter converter = HighlightConverter.newInstance(null, options); - Assert.assertNotNull(converter); - Assert.assertEquals(AnsiEscape.createSequence(colorName), converter.getLevelStyle(Level.TRACE)); - Assert.assertEquals(AnsiEscape.createSequence(colorName), converter.getLevelStyle(Level.DEBUG)); - } - - @Test - public void testLevelNamesNone() { - final String[] options = { "%-5level: %msg", - PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false" }; - final HighlightConverter converter = HighlightConverter.newInstance(null, options); - Assert.assertNotNull(converter); - Assert.assertNotNull(converter.getLevelStyle(Level.TRACE)); - Assert.assertNotNull(converter.getLevelStyle(Level.DEBUG)); - } - - @Test - public void testNoAnsiEmpty() { - final String[] options = {"", PatternParser.DISABLE_ANSI + "=true"}; - final HighlightConverter converter = HighlightConverter.newInstance(null, options); - - final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( - new SimpleMessage("message in a bottle")).build(); - final StringBuilder buffer = new StringBuilder(); - converter.format(event, buffer); - assertEquals("", buffer.toString()); - } - - @Test - public void testNoAnsiNonEmpty() { - final String[] options = {"%-5level: %msg", PatternParser.DISABLE_ANSI + "=true"}; - final HighlightConverter converter = HighlightConverter.newInstance(null, options); - - final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.INFO).setLoggerName("a.b.c").setMessage( - new SimpleMessage("message in a bottle")).build(); - final StringBuilder buffer = new StringBuilder(); - converter.format(event, buffer); - assertEquals("INFO : message in a bottle", buffer.toString()); - } -} \ No newline at end of file diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java deleted file mode 100644 index 811924a0939..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.message.StringMapMessage; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class MapPatternConverterTest { - - @Test - public void testConverter() { - - final StringMapMessage msg = new StringMapMessage(); - msg.put("subject", "I"); - msg.put("verb", "love"); - msg.put("object", "Log4j"); - final MapPatternConverter converter = MapPatternConverter.newInstance(null); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("MyLogger") // - .setLevel(Level.DEBUG) // - .setMessage(msg) // - .build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final String str = sb.toString(); - String expected = "subject=I"; - assertTrue("Missing or incorrect subject. Expected " + expected + ", actual " + str, str.contains(expected)); - expected = "verb=love"; - assertTrue("Missing or incorrect verb", str.contains(expected)); - expected = "object=Log4j"; - assertTrue("Missing or incorrect object", str.contains(expected)); - - assertEquals("{object=Log4j, subject=I, verb=love}", str); - } - - @Test - public void testConverterWithKey() { - - final StringMapMessage msg = new StringMapMessage(); - msg.put("subject", "I"); - msg.put("verb", "love"); - msg.put("object", "Log4j"); - final MapPatternConverter converter = MapPatternConverter.newInstance(new String[] {"object"}); - final LogEvent event = Log4jLogEvent.newBuilder() // - .setLoggerName("MyLogger") // - .setLevel(Level.DEBUG) // - .setMessage(msg) // - .build(); - final StringBuilder sb = new StringBuilder(); - converter.format(event, sb); - final String str = sb.toString(); - final String expected = "Log4j"; - assertEquals(expected, str); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java deleted file mode 100644 index 0134dac4794..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessageJansiConverterTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.Strings; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class MessageJansiConverterTest { - - private static final String EXPECTED = "\u001B[31;1mWarning!\u001B[m Pants on \u001B[31mfire!\u001B[m" - + Strings.LINE_SEPARATOR; - - @Rule - public LoggerContextRule init = new LoggerContextRule("log4j-message-ansi.xml"); - - private Logger logger; - private ListAppender app; - - @Before - public void setUp() throws Exception { - this.logger = this.init.getLogger("LoggerTest"); - this.app = this.init.getListAppender("List").clear(); - } - - @Test - public void testReplacement() { - // See org.fusesource.jansi.AnsiRenderer - logger.error("@|red,bold Warning!|@ Pants on @|red fire!|@"); - - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0), - msgs.get(0).endsWith(EXPECTED)); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java deleted file mode 100644 index ade443251f3..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MessageStyledConverterTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.Strings; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class MessageStyledConverterTest { - - private static final String EXPECTED = "\u001B[31;1mWarning!\u001B[m Pants on \u001B[31;1mfire!\u001B[m" - + Strings.LINE_SEPARATOR; - - @Rule - public LoggerContextRule init = new LoggerContextRule("log4j-message-styled.xml"); - - private Logger logger; - private ListAppender app; - - @Before - public void setUp() throws Exception { - this.logger = this.init.getLogger("LoggerTest"); - this.app = this.init.getListAppender("List").clear(); - } - - @Test - public void testReplacement() { - // See org.fusesource.jansi.AnsiRenderer - logger.error("@|WarningStyle Warning!|@ Pants on @|WarningStyle fire!|@"); - - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0), - msgs.get(0).endsWith(EXPECTED)); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java deleted file mode 100644 index 4b03076d5b9..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.Arrays; -import java.util.Collection; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import static org.junit.Assert.*; - -/** - * - */ -@RunWith(Parameterized.class) -public class NameAbbreviatorTest { - - private final String pattern; - private final String expected; - - public NameAbbreviatorTest(final String pattern, final String expected) { - this.pattern = pattern; - this.expected = expected; - } - - @Parameterized.Parameters(name = "pattern=\"{0}\", expected={1}") - public static Collection data() { - return Arrays.asList( - new Object[][]{ - // { pattern, expected } - { "0", "NameAbbreviatorTest" }, - { "1", "NameAbbreviatorTest" }, - { "2", "pattern.NameAbbreviatorTest" }, - { "3", "core.pattern.NameAbbreviatorTest" }, - { "1.", "o.a.l.l.c.p.NameAbbreviatorTest" }, - { "1.1.~", "o.a.~.~.~.~.NameAbbreviatorTest" }, - { ".", "......NameAbbreviatorTest" } - } - ); - } - - @Test - public void testAbbreviatorPatterns() throws Exception { - final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(this.pattern); - final StringBuilder destination = new StringBuilder(); - abbreviator.abbreviate(this.getClass().getName(), destination); - final String actual = destination.toString(); - assertEquals(expected, actual); - } - - @Test - public void testAbbreviatorPatternsAppend() throws Exception { - final NameAbbreviator abbreviator = NameAbbreviator.getAbbreviator(this.pattern); - final String PREFIX = "some random text"; - final StringBuilder destination = new StringBuilder(PREFIX); - abbreviator.abbreviate(this.getClass().getName(), destination); - final String actual = destination.toString(); - assertEquals(PREFIX + expected, actual); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NoConsoleNoAnsiTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NoConsoleNoAnsiTest.java deleted file mode 100644 index cc9ac03cb9d..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NoConsoleNoAnsiTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.Strings; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -public class NoConsoleNoAnsiTest { - - private static final String EXPECTED = - "ERROR LoggerTest o.a.l.l.c.p.NoConsoleNoAnsiTest org.apache.logging.log4j.core.pattern.NoConsoleNoAnsiTest" - + Strings.LINE_SEPARATOR; - - @Rule - public LoggerContextRule init = new LoggerContextRule("log4j2-console-noConsoleNoAnsi.xml"); - - private Logger logger; - private ListAppender app; - - @Before - public void setUp() throws Exception { - this.logger = this.init.getLogger("LoggerTest"); - this.app = this.init.getListAppender("List").clear(); - } - - @Test - public void testReplacement() { - logger.error(this.getClass().getName()); - - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0), msgs.get(0).endsWith(EXPECTED)); - } - -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java deleted file mode 100644 index f3571119736..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RegexReplacementTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.util.List; - -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.junit.ThreadContextMapRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.Strings; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; - -/** - * - */ -public class RegexReplacementTest { - private static final String CONFIG = "log4j-replace.xml"; - private static ListAppender app; - private static ListAppender app2; - - private static final String EXPECTED = "/RegexReplacementTest" + Strings.LINE_SEPARATOR; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); - - @Rule - public final ThreadContextMapRule threadContextRule = new ThreadContextMapRule(); - - @Before - public void setUp() throws Exception { - app = context.getListAppender("List").clear(); - app2 = context.getListAppender("List2").clear(); - } - - org.apache.logging.log4j.Logger logger = context.getLogger("LoggerTest"); - org.apache.logging.log4j.Logger logger2 = context.getLogger("ReplacementTest"); - - @Test - public void testReplacement() { - logger.error(this.getClass().getName()); - List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("Replacement failed - expected ending " + EXPECTED + " Actual " + msgs.get(0), - msgs.get(0).endsWith(EXPECTED)); - app.clear(); - ThreadContext.put("MyKey", "Apache"); - logger.error("This is a test for ${ctx:MyKey}"); - msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertEquals("LoggerTest This is a test for Apache" + Strings.LINE_SEPARATOR, msgs.get(0)); - } - @Test - public void testConverter() { - logger2.error(this.getClass().getName()); - final List msgs = app2.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("Replacement failed - expected ending " + EXPECTED + " Actual " + msgs.get(0), - msgs.get(0).endsWith(EXPECTED)); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowableTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowableTest.java deleted file mode 100644 index 9e165a64a04..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/RootThrowableTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class RootThrowableTest { - private static final String CONFIG = "log4j-rootthrowablefilter.xml"; - private static ListAppender app; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); - - @Before - public void setUp() throws Exception { - app = context.getListAppender("List").clear(); - } - - @Test - public void testException() { - final Logger logger = context.getLogger("LoggerTest"); - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - logger.error("Exception", parent); - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("No suppressed lines", msgs.get(0).contains("suppressed")); - app.clear(); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java deleted file mode 100644 index b3fe206ba3d..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.ClassRule; -import org.junit.Test; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -/** - * - */ -public class SequenceNumberPatternConverterTest { - - @ClassRule - public static LoggerContextRule ctx = new LoggerContextRule("SequenceNumberPatternConverterTest.yml"); - - @Test - public void testSequenceIncreases() throws Exception { - final Logger logger = ctx.getLogger(); - logger.info("Message 1"); - logger.info("Message 2"); - logger.info("Message 3"); - logger.info("Message 4"); - logger.info("Message 5"); - - final ListAppender app = ctx.getListAppender("List"); - final List messages = app.getMessages(); - assertThat(messages, contains("1", "2", "3", "4", "5")); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java deleted file mode 100644 index 248fdc3d747..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/StyleConverterTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.apache.logging.log4j.util.Strings; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * - */ -public class StyleConverterTest { - - private static final String EXPECTED = - "\u001B[1;31mERROR\u001B[m \u001B[1;36mLoggerTest\u001B[m o.a.l.l.c.p.StyleConverterTest org.apache.logging.log4j.core.pattern.StyleConverterTest" - + Strings.LINE_SEPARATOR; - - @BeforeClass - public static void beforeClass() { - System.setProperty("log4j.skipJansi", "false"); // LOG4J2-2087: explicitly enable - } - - @Rule - public LoggerContextRule init = new LoggerContextRule("log4j-style.xml"); - - private Logger logger; - private ListAppender app; - - @Before - public void setUp() throws Exception { - this.logger = this.init.getLogger("LoggerTest"); - this.app = this.init.getListAppender("List").clear(); - } - - @Test - public void testReplacement() { - logger.error(this.getClass().getName()); - - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertTrue("Replacement failed - expected ending " + EXPECTED + ", actual " + msgs.get(0), msgs.get(0).endsWith(EXPECTED)); - } - - @Test - public void testNull() { - Assert.assertNull(StyleConverter.newInstance(null, null)); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThrowableTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThrowableTest.java deleted file mode 100644 index 80a7ed8e7e4..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ThrowableTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.pattern; - -import java.util.List; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Unit tests for {@code throwable} pattern. - */ -public class ThrowableTest { - private static final String CONFIG = "log4j-throwable.xml"; - private static ListAppender app; - - @ClassRule - public static LoggerContextRule context = new LoggerContextRule(CONFIG); - - @Before - public void setUp() throws Exception { - app = context.getListAppender("List").clear(); - } - - Logger logger = context.getLogger("LoggerTest"); - - @Test - public void testException() { - final Throwable cause = new NullPointerException("null pointer"); - final Throwable parent = new IllegalArgumentException("IllegalArgument", cause); - logger.error("Exception", parent); - final List msgs = app.getMessages(); - assertNotNull(msgs); - assertEquals("Incorrect number of messages. Should be 1 is " + msgs.size(), 1, msgs.size()); - assertFalse("No suppressed lines", msgs.get(0).contains("suppressed")); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java deleted file mode 100644 index 63d3138227c..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.time; - -import org.apache.logging.log4j.core.time.internal.FixedPreciseClock; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -public class MutableInstantTest { - - @Test - public void testGetEpochSecond() { - MutableInstant instant = new MutableInstant(); - assertEquals("initial", 0, instant.getEpochSecond()); - - instant.initFromEpochSecond(123, 456); - assertEquals("returns directly set value", 123, instant.getEpochSecond()); - - instant.initFromEpochMilli(123456, 789012); - assertEquals("returns converted value when initialized from milllis", 123, instant.getEpochSecond()); - - MutableInstant other = new MutableInstant(); - other.initFromEpochSecond(788, 456); - instant.initFrom(other); - - assertEquals("returns ref value when initialized from instant", 788, instant.getEpochSecond()); - } - - @Test - public void testGetNanoOfSecond() { - MutableInstant instant = new MutableInstant(); - assertEquals("initial", 0, instant.getNanoOfSecond()); - - instant.initFromEpochSecond(123, 456); - assertEquals("returns directly set value", 456, instant.getNanoOfSecond()); - - instant.initFromEpochMilli(123456, 789012); - assertEquals("returns converted value when initialized from milllis", 456789012, instant.getNanoOfSecond()); - - MutableInstant other = new MutableInstant(); - other.initFromEpochSecond(788, 456); - instant.initFrom(other); - - assertEquals("returns ref value when initialized from instant", 456, instant.getNanoOfSecond()); - } - - @Test - public void testGetEpochMillisecond() { - MutableInstant instant = new MutableInstant(); - assertEquals("initial", 0, instant.getEpochMillisecond()); - - instant.initFromEpochMilli(123, 456); - assertEquals("returns directly set value", 123, instant.getEpochMillisecond()); - - instant.initFromEpochSecond(123, 456789012); - assertEquals("returns converted value when initialized from seconds", 123456, instant.getEpochMillisecond()); - - MutableInstant other = new MutableInstant(); - other.initFromEpochMilli(788, 456); - instant.initFrom(other); - - assertEquals("returns ref value when initialized from instant", 788, instant.getEpochMillisecond()); - } - - @Test - public void getGetNanoOfMillisecond() { - MutableInstant instant = new MutableInstant(); - assertEquals("initial", 0, instant.getNanoOfMillisecond()); - - instant.initFromEpochMilli(123, 456); - assertEquals("returns directly set value", 456, instant.getNanoOfMillisecond()); - - instant.initFromEpochSecond(123, 456789012); - assertEquals("returns converted value when initialized from milllis", 789012, instant.getNanoOfMillisecond()); - - MutableInstant other = new MutableInstant(); - other.initFromEpochMilli(788, 456); - instant.initFrom(other); - - assertEquals("returns ref value when initialized from instant", 456, instant.getNanoOfMillisecond()); - } - - @Test(expected = NullPointerException.class) - public void testInitFromInstantRejectsNull() { - new MutableInstant().initFrom((Instant) null); - } - - @Test - public void testInitFromInstantCopiesValues() { - MutableInstant other = new MutableInstant(); - other.initFromEpochSecond(788, 456); - assertEquals("epochSec", 788, other.getEpochSecond()); - assertEquals("NanosOfSec", 456, other.getNanoOfSecond()); - - MutableInstant instant = new MutableInstant(); - instant.initFrom(other); - - assertEquals("epochSec", 788, instant.getEpochSecond()); - assertEquals("NanoOfSec", 456, instant.getNanoOfSecond()); - } - - @Test - public void testInitFromEpochMillis() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochMilli(123456, 789012); - assertEquals("epochSec", 123, instant.getEpochSecond()); - assertEquals("NanoOfSec", 456789012, instant.getNanoOfSecond()); - assertEquals("epochMilli", 123456, instant.getEpochMillisecond()); - assertEquals("NanoOfMilli", 789012, instant.getNanoOfMillisecond()); - } - - @Test(expected = IllegalArgumentException.class) - public void testInitFromEpochMillisRejectsNegativeNanoOfMilli() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochMilli(123456, -1); - } - - @Test(expected = IllegalArgumentException.class) - public void testInitFromEpochMillisRejectsTooLargeNanoOfMilli() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochMilli(123456, 1000_000); - } - - @Test - public void testInitFromEpochMillisAcceptsTooMaxNanoOfMilli() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochMilli(123456, 999_999); - assertEquals("NanoOfMilli", 999_999, instant.getNanoOfMillisecond()); - } - - @Test - public void testInitFromEpochSecond() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochSecond(123, 456789012); - assertEquals("epochSec", 123, instant.getEpochSecond()); - assertEquals("NanoOfSec", 456789012, instant.getNanoOfSecond()); - assertEquals("epochMilli", 123456, instant.getEpochMillisecond()); - assertEquals("NanoOfMilli", 789012, instant.getNanoOfMillisecond()); - } - - @Test(expected = IllegalArgumentException.class) - public void testInitFromEpochSecondRejectsNegativeNanoOfMilli() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochSecond(123456, -1); - } - - @Test(expected = IllegalArgumentException.class) - public void testInitFromEpochSecondRejectsTooLargeNanoOfMilli() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochSecond(123456, 1000_000_000); - } - - @Test - public void testInitFromEpochSecondAcceptsTooMaxNanoOfMilli() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochSecond(123456, 999_999_999); - assertEquals("NanoOfSec", 999_999_999, instant.getNanoOfSecond()); - } - - @Test - public void testInstantToMillisAndNanos() { - long[] values = new long[2]; - MutableInstant.instantToMillisAndNanos(123456, 999_999_999, values); - assertEquals(123456_999, values[0]); - assertEquals(999_999, values[1]); - } - - @Test - public void testInitFromClock() { - MutableInstant instant = new MutableInstant(); - - PreciseClock clock = new FixedPreciseClock(123456, 789012); - instant.initFrom(clock); - - assertEquals(123456, instant.getEpochMillisecond()); - assertEquals(789012, instant.getNanoOfMillisecond()); - assertEquals(123, instant.getEpochSecond()); - assertEquals(456789012, instant.getNanoOfSecond()); - } - - @Test - public void testEquals() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochSecond(123, 456789012); - - MutableInstant instant2 = new MutableInstant(); - instant2.initFromEpochMilli(123456, 789012); - - assertEquals(instant, instant2); - } - - @Test - public void testHashCode() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochSecond(123, 456789012); - - MutableInstant instant2 = new MutableInstant(); - instant2.initFromEpochMilli(123456, 789012); - - assertEquals(instant.hashCode(), instant2.hashCode()); - - - instant2.initFromEpochMilli(123456, 789013); - assertNotEquals(instant.hashCode(), instant2.hashCode()); - } - - @Test - public void testToString() { - MutableInstant instant = new MutableInstant(); - instant.initFromEpochSecond(123, 456789012); - assertEquals("MutableInstant[epochSecond=123, nano=456789012]", instant.toString()); - - instant.initFromEpochMilli(123456, 789012); - assertEquals("MutableInstant[epochSecond=123, nano=456789012]", instant.toString()); - } -} \ No newline at end of file diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java deleted file mode 100644 index 06422537f85..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/GenerateCustomLoggerTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.tools; - -import java.io.File; -import java.io.FileOutputStream; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -import javax.tools.Diagnostic; -import javax.tools.DiagnosticCollector; -import javax.tools.JavaCompiler; -import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.ToolProvider; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.TestLogger; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.util.MessageSupplier; -import org.apache.logging.log4j.util.Supplier; -import org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class GenerateCustomLoggerTest { - - @BeforeClass - public static void beforeClass() { - System.setProperty("log4j2.loggerContextFactory", "org.apache.logging.log4j.TestLoggerContextFactory"); - } - - @Test - public void testGenerateSource() throws Exception { - final String CLASSNAME = "org.myorg.MyCustomLogger"; - - // generate custom logger source - final List values = Arrays.asList("DEFCON1=350 DEFCON2=450 DEFCON3=550".split(" ")); - final List levels = Generate.LevelInfo.parse(values, Generate.CustomLogger.class); - final String src = Generate.generateSource(CLASSNAME, levels, Generate.Type.CUSTOM); - final File f = new File("target/test-classes/org/myorg/MyCustomLogger.java"); - f.getParentFile().mkdirs(); - try (final FileOutputStream out = new FileOutputStream(f)) { - out.write(src.getBytes(Charset.defaultCharset())); - } - - // set up compiler - final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - final List errors = new ArrayList<>(); - try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { - final Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays - .asList(f)); - - // compile generated source - compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits).call(); - - // check we don't have any compilation errors - for (final Diagnostic diagnostic : diagnostics.getDiagnostics()) { - if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { - errors.add(String.format("Compile error: %s%n", diagnostic.getMessage(Locale.getDefault()))); - } - } - } - assertTrue(errors.toString(), errors.isEmpty()); - - // load the compiled class - final Class cls = Class.forName(CLASSNAME); - - // check that all factory methods exist and are static - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[0]).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[] { Class.class }).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[] { Object.class }).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[] { String.class }).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class, MessageFactory.class).getModifiers())); - assertTrue(Modifier - .isStatic(cls.getDeclaredMethod("create", Object.class, MessageFactory.class).getModifiers())); - assertTrue(Modifier - .isStatic(cls.getDeclaredMethod("create", String.class, MessageFactory.class).getModifiers())); - - // check that all log methods exist - final String[] logMethods = { "defcon1", "defcon2", "defcon3" }; - for (final String name : logMethods) { - cls.getDeclaredMethod(name, Marker.class, Message.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, Object.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, String.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, Message.class); - cls.getDeclaredMethod(name, Marker.class, Object.class); - cls.getDeclaredMethod(name, Marker.class, String.class); - cls.getDeclaredMethod(name, Message.class); - cls.getDeclaredMethod(name, Object.class); - cls.getDeclaredMethod(name, String.class); - cls.getDeclaredMethod(name, Message.class, Throwable.class); - cls.getDeclaredMethod(name, Object.class, Throwable.class); - cls.getDeclaredMethod(name, String.class, Throwable.class); - cls.getDeclaredMethod(name, String.class, Object[].class); - cls.getDeclaredMethod(name, Marker.class, String.class, Object[].class); - - // 2.4 lambda support - cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class); - cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, String.class, Supplier[].class); - cls.getDeclaredMethod(name, Marker.class, Supplier.class); - cls.getDeclaredMethod(name, Marker.class, Supplier.class, Throwable.class); - cls.getDeclaredMethod(name, MessageSupplier.class); - cls.getDeclaredMethod(name, MessageSupplier.class, Throwable.class); - cls.getDeclaredMethod(name, String.class, Supplier[].class); - cls.getDeclaredMethod(name, Supplier.class); - cls.getDeclaredMethod(name, Supplier.class, Throwable.class); - } - - // now see if it actually works... - final Method create = cls.getDeclaredMethod("create", new Class[] { String.class }); - final Object customLogger = create.invoke(null, "X.Y.Z"); - int n = 0; - for (final String name : logMethods) { - final Method method = cls.getDeclaredMethod(name, String.class); - method.invoke(customLogger, "This is message " + n++); - } - - final TestLogger underlying = (TestLogger) LogManager.getLogger("X.Y.Z"); - final List lines = underlying.getEntries(); - for (int i = 0; i < lines.size(); i++) { - assertEquals(" " + levels.get(i).name + " This is message " + i, lines.get(i)); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java deleted file mode 100644 index 914ada8e810..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/GenerateExtendedLoggerTest.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ - -package org.apache.logging.log4j.core.tools; - -import java.io.File; -import java.io.FileOutputStream; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -import javax.tools.Diagnostic; -import javax.tools.DiagnosticCollector; -import javax.tools.JavaCompiler; -import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.ToolProvider; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.TestLogger; -import org.apache.logging.log4j.core.tools.Generate; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.spi.ExtendedLogger; -import org.apache.logging.log4j.util.MessageSupplier; -import org.apache.logging.log4j.util.Supplier; -import org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class GenerateExtendedLoggerTest { - - @BeforeClass - public static void beforeClass() { - System.setProperty("log4j2.loggerContextFactory", "org.apache.logging.log4j.TestLoggerContextFactory"); - } - - @Test - public void testGenerateSource() throws Exception { - final String CLASSNAME = "org.myorg.MyExtendedLogger"; - - // generate custom logger source - final List values = Arrays.asList("DIAG=350 NOTICE=450 VERBOSE=550".split(" ")); - final List levels = Generate.LevelInfo.parse(values, Generate.ExtendedLogger.class); - final String src = Generate.generateSource(CLASSNAME, levels, Generate.Type.EXTEND); - final File f = new File("target/test-classes/org/myorg/MyExtendedLogger.java"); - f.getParentFile().mkdirs(); - try (final FileOutputStream out = new FileOutputStream(f)) { - out.write(src.getBytes(Charset.defaultCharset())); - } - - // set up compiler - final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - final List errors = new ArrayList<>(); - try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { - final Iterable compilationUnits = fileManager - .getJavaFileObjectsFromFiles(Arrays.asList(f)); - - // compile generated source - compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits).call(); - - // check we don't have any compilation errors - for (final Diagnostic diagnostic : diagnostics.getDiagnostics()) { - if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { - errors.add(String.format("Compile error: %s%n", diagnostic.getMessage(Locale.getDefault()))); - } - } - } - assertTrue(errors.toString(), errors.isEmpty()); - - // load the compiled class - final Class cls = Class.forName(CLASSNAME); - - // check that all factory methods exist and are static - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[0]).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[] { Class.class }).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[] { Object.class }).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", new Class[] { String.class }).getModifiers())); - assertTrue(Modifier.isStatic(cls.getDeclaredMethod("create", Class.class, MessageFactory.class).getModifiers())); - assertTrue(Modifier - .isStatic(cls.getDeclaredMethod("create", Object.class, MessageFactory.class).getModifiers())); - assertTrue(Modifier - .isStatic(cls.getDeclaredMethod("create", String.class, MessageFactory.class).getModifiers())); - - // check that the extended log methods exist - final String[] extendedMethods = { "diag", "notice", "verbose" }; - for (final String name : extendedMethods) { - cls.getDeclaredMethod(name, Marker.class, Message.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, Object.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, String.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, Message.class); - cls.getDeclaredMethod(name, Marker.class, Object.class); - cls.getDeclaredMethod(name, Marker.class, String.class); - cls.getDeclaredMethod(name, Message.class); - cls.getDeclaredMethod(name, Object.class); - cls.getDeclaredMethod(name, String.class); - cls.getDeclaredMethod(name, Message.class, Throwable.class); - cls.getDeclaredMethod(name, Object.class, Throwable.class); - cls.getDeclaredMethod(name, String.class, Throwable.class); - cls.getDeclaredMethod(name, String.class, Object[].class); - cls.getDeclaredMethod(name, Marker.class, String.class, Object[].class); - - // 2.4 lambda support - cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class); - cls.getDeclaredMethod(name, Marker.class, MessageSupplier.class, Throwable.class); - cls.getDeclaredMethod(name, Marker.class, String.class, Supplier[].class); - cls.getDeclaredMethod(name, Marker.class, Supplier.class); - cls.getDeclaredMethod(name, Marker.class, Supplier.class, Throwable.class); - cls.getDeclaredMethod(name, MessageSupplier.class); - cls.getDeclaredMethod(name, MessageSupplier.class, Throwable.class); - cls.getDeclaredMethod(name, String.class, Supplier[].class); - cls.getDeclaredMethod(name, Supplier.class); - cls.getDeclaredMethod(name, Supplier.class, Throwable.class); - } - - // now see if it actually works... - final Method create = cls.getDeclaredMethod("create", new Class[] { String.class }); - final Object extendedLogger = create.invoke(null, "X.Y.Z"); - int n = 0; - for (final String name : extendedMethods) { - final Method method = cls.getDeclaredMethod(name, String.class); - method.invoke(extendedLogger, "This is message " + n++); - } - - // This logger extends o.a.l.log4j.spi.ExtendedLogger, - // so all the standard logging methods can be used as well - final ExtendedLogger logger = (ExtendedLogger) extendedLogger; - logger.trace("trace message"); - logger.debug("debug message"); - logger.info("info message"); - logger.warn("warn message"); - logger.error("error message"); - logger.fatal("fatal message"); - - final TestLogger underlying = (TestLogger) LogManager.getLogger("X.Y.Z"); - final List lines = underlying.getEntries(); - for (int i = 0; i < lines.size() - 6; i++) { - assertEquals(" " + levels.get(i).name + " This is message " + i, lines.get(i)); - } - - // test that the standard logging methods still work - int i = lines.size() - 6; - assertEquals(" TRACE trace message", lines.get(i++)); - assertEquals(" DEBUG debug message", lines.get(i++)); - assertEquals(" INFO info message", lines.get(i++)); - assertEquals(" WARN warn message", lines.get(i++)); - assertEquals(" ERROR error message", lines.get(i++)); - assertEquals(" FATAL fatal message", lines.get(i++)); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineArityTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineArityTest.java deleted file mode 100644 index e35b5934e78..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineArityTest.java +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.tools.picocli; - -import java.io.File; -import java.net.InetAddress; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.*; - -import static org.junit.Assert.*; - -public class CommandLineArityTest { - @Before public void setUp() { System.clearProperty("picocli.trace"); } - @After public void tearDown() { System.clearProperty("picocli.trace"); } - - private static void setTraceLevel(String level) { - System.setProperty("picocli.trace", level); - } - @Test - public void testArityConstructor_fixedRange() { - Range arity = new Range(1, 23, false, false, null); - assertEquals("min", 1, arity.min); - assertEquals("max", 23, arity.max); - assertEquals("1..23", arity.toString()); - assertEquals(Range.valueOf("1..23"), arity); - } - @Test - public void testArityConstructor_variableRange() { - Range arity = new Range(1, Integer.MAX_VALUE, true, false, null); - assertEquals("min", 1, arity.min); - assertEquals("max", Integer.MAX_VALUE, arity.max); - assertEquals("1..*", arity.toString()); - assertEquals(Range.valueOf("1..*"), arity); - } - @Test - public void testArityForOption_booleanFieldImplicitArity0() throws Exception { - Range arity = Range.optionArity(CommandLineTest.SupportedTypes.class.getDeclaredField("booleanField")); - assertEquals(Range.valueOf("0"), arity); - assertEquals("0", arity.toString()); - } - @Test - public void testArityForOption_intFieldImplicitArity1() throws Exception { - Range arity = Range.optionArity(CommandLineTest.SupportedTypes.class.getDeclaredField("intField")); - assertEquals(Range.valueOf("1"), arity); - assertEquals("1", arity.toString()); - } - @Test - public void testArityForOption_isExplicitlyDeclaredValue() throws Exception { - class Params { - @Option(names = "-timeUnitList", type = TimeUnit.class, arity = "3") List timeUnitList; - } - Range arity = Range.optionArity(Params.class.getDeclaredField("timeUnitList")); - assertEquals(Range.valueOf("3"), arity); - assertEquals("3", arity.toString()); - } - @Test - public void testArityForOption_listFieldImplicitArity1() throws Exception { - class ImplicitList { @Option(names = "-a") List listIntegers; } - Range arity = Range.optionArity(ImplicitList.class.getDeclaredField("listIntegers")); - assertEquals(Range.valueOf("1"), arity); - assertEquals("1", arity.toString()); - } - @Test - public void testArityForOption_arrayFieldImplicitArity1() throws Exception { - class ImplicitList { @Option(names = "-a") int[] intArray; } - Range arity = Range.optionArity(ImplicitList.class.getDeclaredField("intArray")); - assertEquals(Range.valueOf("1"), arity); - assertEquals("1", arity.toString()); - } - @Test - public void testArityForParameters_booleanFieldImplicitArity1() throws Exception { - class ImplicitBoolField { @Parameters boolean boolSingleValue; } - Range arity = Range.parameterArity(ImplicitBoolField.class.getDeclaredField("boolSingleValue")); - assertEquals(Range.valueOf("1"), arity); - assertEquals("1", arity.toString()); - } - @Test - public void testArityForParameters_intFieldImplicitArity1() throws Exception { - class ImplicitSingleField { @Parameters int intSingleValue; } - Range arity = Range.parameterArity(ImplicitSingleField.class.getDeclaredField("intSingleValue")); - assertEquals(Range.valueOf("1"), arity); - assertEquals("1", arity.toString()); - } - @Test - public void testArityForParameters_listFieldImplicitArity0_1() throws Exception { - class Params { - @Parameters(type = Integer.class) List list; - } - Range arity = Range.parameterArity(Params.class.getDeclaredField("list")); - assertEquals(Range.valueOf("0..1"), arity); - assertEquals("0..1", arity.toString()); - } - @Test - public void testArityForParameters_arrayFieldImplicitArity0_1() throws Exception { - class Args { - @Parameters File[] inputFiles; - } - Range arity = Range.parameterArity(Args.class.getDeclaredField("inputFiles")); - assertEquals(Range.valueOf("0..1"), arity); - assertEquals("0..1", arity.toString()); - } - @Test - public void testArrayOptionsWithArity0_nConsumeAllArguments() { - final double[] DEFAULT_PARAMS = new double[] {1, 2}; - class ArrayOptionsArity0_nAndParameters { - @Parameters double[] doubleParams = DEFAULT_PARAMS; - @Option(names = "-doubles", arity = "0..*") double[] doubleOptions; - } - ArrayOptionsArity0_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionsArity0_nAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); - assertArrayEquals(DEFAULT_PARAMS, params.doubleParams, 0.000001); - } - - @Test - public void testArrayOptionsWithArity1_nConsumeAllArguments() { - class ArrayOptionsArity1_nAndParameters { - @Parameters double[] doubleParams; - @Option(names = "-doubles", arity = "1..*") double[] doubleOptions; - } - ArrayOptionsArity1_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionsArity1_nAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); - assertArrayEquals(null, params.doubleParams, 0.000001); - } - - @Test - public void testArrayOptionsWithArity2_nConsumeAllArguments() { - class ArrayOptionsArity2_nAndParameters { - @Parameters double[] doubleParams; - @Option(names = "-doubles", arity = "2..*") double[] doubleOptions; - } - ArrayOptionsArity2_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionsArity2_nAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); - assertArrayEquals(null, params.doubleParams, 0.000001); - } - - @Test - public void testArrayOptionArity2_nConsumesAllArgumentsUpToClusteredOption() { - class ArrayOptionsArity2_nAndParameters { - @Parameters String[] stringParams; - @Option(names = "-s", arity = "2..*") String[] stringOptions; - @Option(names = "-v") boolean verbose; - @Option(names = "-f") File file; - } - ArrayOptionsArity2_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionsArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 -vfFILE 5.5".split(" ")); - assertArrayEquals(Arrays.toString(params.stringOptions), - new String[] {"1.1", "2.2", "3.3", "4.4"}, params.stringOptions); - assertTrue(params.verbose); - assertEquals(new File("FILE"), params.file); - assertArrayEquals(new String[] {"5.5"}, params.stringParams); - } - - @Test - public void testArrayOptionArity2_nConsumesAllArgumentIncludingQuotedSimpleOption() { - class ArrayOptionArity2_nAndParameters { - @Parameters String[] stringParams; - @Option(names = "-s", arity = "2..*") String[] stringOptions; - @Option(names = "-v") boolean verbose; - @Option(names = "-f") File file; - } - ArrayOptionArity2_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 \"-v\" \"-f\" \"FILE\" 5.5".split(" ")); - assertArrayEquals(Arrays.toString(params.stringOptions), - new String[] {"1.1", "2.2", "3.3", "4.4", "-v", "-f", "FILE", "5.5"}, params.stringOptions); - assertFalse("verbose", params.verbose); - assertNull("file", params.file); - assertArrayEquals(null, params.stringParams); - } - - @Test - public void testArrayOptionArity2_nConsumesAllArgumentIncludingQuotedClusteredOption() { - class ArrayOptionArity2_nAndParameters { - @Parameters String[] stringParams; - @Option(names = "-s", arity = "2..*") String[] stringOptions; - @Option(names = "-v") boolean verbose; - @Option(names = "-f") File file; - } - ArrayOptionArity2_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 \"-vfFILE\" 5.5".split(" ")); - assertArrayEquals(Arrays.toString(params.stringOptions), - new String[] {"1.1", "2.2", "3.3", "4.4", "-vfFILE", "5.5"}, params.stringOptions); - assertFalse("verbose", params.verbose); - assertNull("file", params.file); - assertArrayEquals(null, params.stringParams); - } - - @Test - public void testArrayOptionArity2_nConsumesAllArgumentsUpToNextSimpleOption() { - class ArrayOptionArity2_nAndParameters { - @Parameters double[] doubleParams; - @Option(names = "-s", arity = "2..*") String[] stringOptions; - @Option(names = "-v") boolean verbose; - @Option(names = "-f") File file; - } - ArrayOptionArity2_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 -v -f=FILE 5.5".split(" ")); - assertArrayEquals(Arrays.toString(params.stringOptions), - new String[] {"1.1", "2.2", "3.3", "4.4"}, params.stringOptions); - assertTrue(params.verbose); - assertEquals(new File("FILE"), params.file); - assertArrayEquals(new double[] {5.5}, params.doubleParams, 0.000001); - } - - @Test - public void testArrayOptionArity2_nConsumesAllArgumentsUpToNextOptionWithAttachment() { - class ArrayOptionArity2_nAndParameters { - @Parameters double[] doubleParams; - @Option(names = "-s", arity = "2..*") String[] stringOptions; - @Option(names = "-v") boolean verbose; - @Option(names = "-f") File file; - } - ArrayOptionArity2_nAndParameters - params = CommandLine.populateCommand(new ArrayOptionArity2_nAndParameters(), "-s 1.1 2.2 3.3 4.4 -f=FILE -v 5.5".split(" ")); - assertArrayEquals(Arrays.toString(params.stringOptions), - new String[] {"1.1", "2.2", "3.3", "4.4"}, params.stringOptions); - assertTrue(params.verbose); - assertEquals(new File("FILE"), params.file); - assertArrayEquals(new double[] {5.5}, params.doubleParams, 0.000001); - } - - @Test - public void testArrayOptionArityNConsumeAllArguments() { - class ArrayOptionArityNAndParameters { - @Parameters char[] charParams; - @Option(names = "-chars", arity = "*") char[] charOptions; - } - ArrayOptionArityNAndParameters - params = CommandLine.populateCommand(new ArrayOptionArityNAndParameters(), "-chars a b c d".split(" ")); - assertArrayEquals(Arrays.toString(params.charOptions), - new char[] {'a', 'b', 'c', 'd'}, params.charOptions); - assertArrayEquals(null, params.charParams); - } - @Test - public void testMissingRequiredParams() { - class Example { - @Parameters(index = "1", arity = "0..1") String optional; - @Parameters(index = "0") String mandatory; - } - try { CommandLine.populateCommand(new Example(), new String[] {"mandatory"}); } - catch (MissingParameterException ex) { fail(); } - - try { - CommandLine.populateCommand(new Example(), new String[0]); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameter: ", ex.getMessage()); - } - } - @Test - public void testMissingRequiredParams1() { - class Tricky1 { - @Parameters(index = "2") String anotherMandatory; - @Parameters(index = "1", arity = "0..1") String optional; - @Parameters(index = "0") String mandatory; - } - try { - CommandLine.populateCommand(new Tricky1(), new String[0]); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameters: , ", ex.getMessage()); - } - try { - CommandLine.populateCommand(new Tricky1(), new String[] {"firstonly"}); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameter: ", ex.getMessage()); - } - } - @Test - public void testMissingRequiredParams2() { - class Tricky2 { - @Parameters(index = "2", arity = "0..1") String anotherOptional; - @Parameters(index = "1", arity = "0..1") String optional; - @Parameters(index = "0") String mandatory; - } - try { CommandLine.populateCommand(new Tricky2(), new String[] {"mandatory"}); } - catch (MissingParameterException ex) { fail(); } - - try { - CommandLine.populateCommand(new Tricky2(), new String[0]); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameter: ", ex.getMessage()); - } - } - @Test - public void testMissingRequiredParamsWithOptions() { - class Tricky3 { - @Option(names="-v") boolean more; - @Option(names="-t") boolean any; - @Parameters(index = "1") String alsoMandatory; - @Parameters(index = "0") String mandatory; - } - try { - CommandLine.populateCommand(new Tricky3(), new String[] {"-t", "-v", "mandatory"}); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameter: ", ex.getMessage()); - } - - try { - CommandLine.populateCommand(new Tricky3(), new String[] { "-t", "-v"}); - fail("Should not accept missing two mandatory parameters"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameters: , ", ex.getMessage()); - } - } - @Test - public void testMissingRequiredParamWithOption() { - class Tricky3 { - @Option(names="-t") boolean any; - @Parameters(index = "0") String mandatory; - } - try { - CommandLine.populateCommand(new Tricky3(), new String[] {"-t"}); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameter: ", ex.getMessage()); - } - } - @Test - public void testNoMissingRequiredParamErrorIfHelpOptionSpecified() { - class App { - @Parameters(hidden = true) // "hidden": don't show this parameter in usage help message - List allParameters; // no "index" attribute: captures _all_ arguments (as Strings) - - @Parameters(index = "0") InetAddress host; - @Parameters(index = "1") int port; - @Parameters(index = "2..*") File[] files; - - @Option(names = "-?", help = true) boolean help; - } - CommandLine.populateCommand(new App(), new String[] {"-?"}); - try { - CommandLine.populateCommand(new App(), new String[0]); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameters: , ", ex.getMessage()); - } - } - @Test - public void testNoMissingRequiredParamErrorWithLabelIfHelpOptionSpecified() { - class App { - @Parameters(hidden = true) // "hidden": don't show this parameter in usage help message - List allParameters; // no "index" attribute: captures _all_ arguments (as Strings) - - @Parameters(index = "0", paramLabel = "HOST") InetAddress host; - @Parameters(index = "1", paramLabel = "PORT") int port; - @Parameters(index = "2..*", paramLabel = "FILES") File[] files; - - @Option(names = "-?", help = true) boolean help; - } - CommandLine.populateCommand(new App(), new String[] {"-?"}); - try { - CommandLine.populateCommand(new App(), new String[0]); - fail("Should not accept missing mandatory parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameters: HOST, PORT", ex.getMessage()); - } - } - - private static class BooleanOptionsArity0_nAndParameters { - @Parameters String[] params; - @Option(names = "-bool", arity = "0..*") boolean bool; - @Option(names = {"-v", "-other"}, arity="0..*") boolean vOrOther; - @Option(names = "-r") boolean rBoolean; - } - @Test - public void testBooleanOptionsArity0_nConsume1ArgumentIfPossible() { // ignores varargs - BooleanOptionsArity0_nAndParameters - params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool false false true".split(" ")); - assertFalse(params.bool); - assertArrayEquals(new String[]{ "false", "true"}, params.params); - } - @Test - public void testBooleanOptionsArity0_nRequiresNoArgument() { // ignores varargs - BooleanOptionsArity0_nAndParameters - params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool".split(" ")); - assertTrue(params.bool); - } - @Test - public void testBooleanOptionsArity0_nConsume0ArgumentsIfNextArgIsOption() { // ignores varargs - BooleanOptionsArity0_nAndParameters - params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool -other".split(" ")); - assertTrue(params.bool); - assertTrue(params.vOrOther); - } - @Test - public void testBooleanOptionsArity0_nConsume0ArgumentsIfNextArgIsParameter() { // ignores varargs - BooleanOptionsArity0_nAndParameters - params = CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool 123 -other".split(" ")); - assertTrue(params.bool); - assertTrue(params.vOrOther); - assertArrayEquals(new String[]{ "123"}, params.params); - } - @Test - public void testBooleanOptionsArity0_nFailsIfAttachedParamNotABoolean() { // ignores varargs - try { - CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-bool=123 -other".split(" ")); - fail("was able to assign 123 to boolean"); - } catch (CommandLine.ParameterException ex) { - assertEquals("'123' is not a boolean for option '-bool'", ex.getMessage()); - } - } - @Test - public void testBooleanOptionsArity0_nShortFormFailsIfAttachedParamNotABoolean() { // ignores varargs - try { - CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-rv234 -bool".split(" ")); - fail("Expected exception"); - } catch (UnmatchedArgumentException ok) { - assertEquals("Unmatched argument [-234]", ok.getMessage()); - } - } - @Test - public void testBooleanOptionsArity0_nShortFormFailsIfAttachedParamNotABooleanWithUnmatchedArgsAllowed() { // ignores varargs - setTraceLevel("OFF"); - CommandLine cmd = new CommandLine(new BooleanOptionsArity0_nAndParameters()).setUnmatchedArgumentsAllowed(true); - cmd.parse("-rv234 -bool".split(" ")); - assertEquals(Arrays.asList("-234"), cmd.getUnmatchedArguments()); - } - @Test - public void testBooleanOptionsArity0_nShortFormFailsIfAttachedWithSepParamNotABoolean() { // ignores varargs - try { - CommandLine.populateCommand(new BooleanOptionsArity0_nAndParameters(), "-rv=234 -bool".split(" ")); - fail("was able to assign 234 to boolean"); - } catch (CommandLine.ParameterException ex) { - assertEquals("'234' is not a boolean for option '-v'", ex.getMessage()); - } - } - - private static class BooleanOptionsArity1_nAndParameters { - @Parameters boolean[] boolParams; - @Option(names = "-bool", arity = "1..*") boolean aBoolean; - } - @Test - public void testBooleanOptionsArity1_nConsume1Argument() { // ignores varargs - BooleanOptionsArity1_nAndParameters - params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool false false true".split(" ")); - assertFalse(params.aBoolean); - assertArrayEquals(new boolean[]{ false, true}, params.boolParams); - - params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool true false true".split(" ")); - assertTrue(params.aBoolean); - assertArrayEquals(new boolean[]{ false, true}, params.boolParams); - } - @Test - public void testBooleanOptionsArity1_nCaseInsensitive() { // ignores varargs - BooleanOptionsArity1_nAndParameters - params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool fAlsE false true".split(" ")); - assertFalse(params.aBoolean); - assertArrayEquals(new boolean[]{ false, true}, params.boolParams); - - params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool FaLsE false true".split(" ")); - assertFalse(params.aBoolean); - assertArrayEquals(new boolean[]{ false, true}, params.boolParams); - - params = CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool tRuE false true".split(" ")); - assertTrue(params.aBoolean); - assertArrayEquals(new boolean[]{ false, true}, params.boolParams); - } - @Test - public void testBooleanOptionsArity1_nErrorIfValueNotTrueOrFalse() { // ignores varargs - try { - CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool abc".split(" ")); - fail("Invalid format abc was accepted for boolean"); - } catch (CommandLine.ParameterException expected) { - assertEquals("'abc' is not a boolean for option '-bool'", expected.getMessage()); - } - } - @Test - public void testBooleanOptionsArity1_nErrorIfValueMissing() { - try { - CommandLine.populateCommand(new BooleanOptionsArity1_nAndParameters(), "-bool".split(" ")); - fail("Missing param was accepted for boolean with arity=1"); - } catch (CommandLine.ParameterException expected) { - assertEquals("Missing required parameter for option '-bool' at index 0 ()", expected.getMessage()); - } - } - - @Test - public void testBooleanOptionArity0Consumes0Arguments() { - class BooleanOptionArity0AndParameters { - @Parameters boolean[] boolParams; - @Option(names = "-bool", arity = "0") boolean aBoolean; - } - BooleanOptionArity0AndParameters - params = CommandLine.populateCommand(new BooleanOptionArity0AndParameters(), "-bool true false true".split(" ")); - assertTrue(params.aBoolean); - assertArrayEquals(new boolean[]{true, false, true}, params.boolParams); - } - @Test(expected = MissingParameterException.class) - public void testSingleValueFieldDefaultMinArityIs1() { - CommandLine.populateCommand(new CommandLineTest.SupportedTypes(), "-Long"); - } - @Test - public void testSingleValueFieldDefaultMinArityIsOne() { - try { - CommandLine.populateCommand(new CommandLineTest.SupportedTypes(), "-Long", "-boolean"); - fail("should fail"); - } catch (CommandLine.ParameterException ex) { - assertEquals("Could not convert '-boolean' to Long for option '-Long'" + - ": java.lang.NumberFormatException: For input string: \"-boolean\"", ex.getMessage()); - } - } - - @Test - public void testIntOptionArity1_nConsumes1Argument() { // ignores varargs - class IntOptionArity1_nAndParameters { - @Parameters int[] intParams; - @Option(names = "-int", arity = "1..*") int anInt; - } - IntOptionArity1_nAndParameters - params = CommandLine.populateCommand(new IntOptionArity1_nAndParameters(), "-int 23 42 7".split(" ")); - assertEquals(23, params.anInt); - assertArrayEquals(new int[]{ 42, 7}, params.intParams); - } - - @Test - public void testArrayOptionsWithArity0Consume0Arguments() { - class OptionsArray0ArityAndParameters { - @Parameters double[] doubleParams; - @Option(names = "-doubles", arity = "0") double[] doubleOptions; - } - OptionsArray0ArityAndParameters - params = CommandLine.populateCommand(new OptionsArray0ArityAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[0], params.doubleOptions, 0.000001); - assertArrayEquals(new double[]{1.1, 2.2, 3.3, 4.4}, params.doubleParams, 0.000001); - } - - @Test - public void testArrayOptionWithArity1Consumes1Argument() { - class Options1ArityAndParameters { - @Parameters double[] doubleParams; - @Option(names = "-doubles", arity = "1") double[] doubleOptions; - } - Options1ArityAndParameters - params = CommandLine.populateCommand(new Options1ArityAndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1}, params.doubleOptions, 0.000001); - assertArrayEquals(new double[]{2.2, 3.3, 4.4}, params.doubleParams, 0.000001); - - // repeated occurrence - params = CommandLine.populateCommand(new Options1ArityAndParameters(), "-doubles 1.1 -doubles 2.2 -doubles 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, 3.3}, params.doubleOptions, 0.000001); - assertArrayEquals(new double[]{4.4}, params.doubleParams, 0.000001); - - } - - private static class ArrayOptionArity2AndParameters { - @Parameters double[] doubleParams; - @Option(names = "-doubles", arity = "2") double[] doubleOptions; - } - @Test - public void testArrayOptionWithArity2Consumes2Arguments() { - ArrayOptionArity2AndParameters - params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles 1.1 2.2 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, }, params.doubleOptions, 0.000001); - assertArrayEquals(new double[]{3.3, 4.4}, params.doubleParams, 0.000001); - - // repeated occurrence - params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles 1.1 2.2 -doubles 3.3 4.4 0".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, 3.3, 4.4 }, params.doubleOptions, 0.000001); - assertArrayEquals(new double[]{ 0.0 }, params.doubleParams, 0.000001); - } - @Test - public void testArrayOptionsWithArity2Consume2ArgumentsEvenIfFirstIsAttached() { - ArrayOptionArity2AndParameters - params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles=1.1 2.2 3.3 4.4".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, }, params.doubleOptions, 0.000001); - assertArrayEquals(new double[]{3.3, 4.4}, params.doubleParams, 0.000001); - - // repeated occurrence - params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles=1.1 2.2 -doubles=3.3 4.4 0".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1.1, 2.2, 3.3, 4.4}, params.doubleOptions, 0.000001); - assertArrayEquals(new double[]{0}, params.doubleParams, 0.000001); - } - /** Arity should not limit the total number of values put in an array or collection #191 */ - @Test - public void testArrayOptionsWithArity2MayContainMoreThan2Values() { - ArrayOptionArity2AndParameters - params = CommandLine.populateCommand(new ArrayOptionArity2AndParameters(), "-doubles=1 2 -doubles 3 4 -doubles 5 6".split(" ")); - assertArrayEquals(Arrays.toString(params.doubleOptions), - new double[] {1, 2, 3, 4, 5, 6 }, params.doubleOptions, 0.000001); - assertArrayEquals(null, params.doubleParams, 0.000001); - } - - @Test - public void testArrayOptionWithoutArityConsumesOneArgument() { // #192 - class OptionsNoArityAndParameters { - @Parameters char[] charParams; - @Option(names = "-chars") char[] charOptions; - } - OptionsNoArityAndParameters - params = CommandLine.populateCommand(new OptionsNoArityAndParameters(), "-chars a b c d".split(" ")); - assertArrayEquals(Arrays.toString(params.charOptions), - new char[] {'a', }, params.charOptions); - assertArrayEquals(Arrays.toString(params.charParams), new char[] {'b', 'c', 'd'}, params.charParams); - - // repeated occurrence - params = CommandLine.populateCommand(new OptionsNoArityAndParameters(), "-chars a -chars b c d".split(" ")); - assertArrayEquals(Arrays.toString(params.charOptions), - new char[] {'a', 'b', }, params.charOptions); - assertArrayEquals(Arrays.toString(params.charParams), new char[] {'c', 'd'}, params.charParams); - - try { - CommandLine.populateCommand(new OptionsNoArityAndParameters(), "-chars".split(" ")); - fail("expected MissingParameterException"); - } catch (MissingParameterException ok) { - assertEquals("Missing required parameter for option '-chars' ()", ok.getMessage()); - } - } - - @Test - public void testArrayParametersWithDefaultArity() { - class ArrayParamsDefaultArity { - @Parameters - List params; - } - ArrayParamsDefaultArity params = CommandLine.populateCommand(new ArrayParamsDefaultArity(), "a", "b", "c"); - assertEquals(Arrays.asList("a", "b", "c"), params.params); - - params = CommandLine.populateCommand(new ArrayParamsDefaultArity(), "a"); - assertEquals(Arrays.asList("a"), params.params); - - params = CommandLine.populateCommand(new ArrayParamsDefaultArity()); - assertEquals(null, params.params); - } - - @Test - public void testArrayParametersWithArityMinusOneToN() { - class ArrayParamsNegativeArity { - @Parameters(arity = "-1..*") - List params; - } - ArrayParamsNegativeArity params = CommandLine.populateCommand(new ArrayParamsNegativeArity(), "a", "b", "c"); - assertEquals(Arrays.asList("a", "b", "c"), params.params); - - params = CommandLine.populateCommand(new ArrayParamsNegativeArity(), "a"); - assertEquals(Arrays.asList("a"), params.params); - - params = CommandLine.populateCommand(new ArrayParamsNegativeArity()); - assertEquals(null, params.params); - } - - @Test - public void testArrayParametersArity0_n() { - class ArrayParamsArity0_n { - @Parameters(arity = "0..*") - List params; - } - ArrayParamsArity0_n params = CommandLine.populateCommand(new ArrayParamsArity0_n(), "a", "b", "c"); - assertEquals(Arrays.asList("a", "b", "c"), params.params); - - params = CommandLine.populateCommand(new ArrayParamsArity0_n(), "a"); - assertEquals(Arrays.asList("a"), params.params); - - params = CommandLine.populateCommand(new ArrayParamsArity0_n()); - assertEquals(null, params.params); - } - - @Test - public void testArrayParametersArity1_n() { - class ArrayParamsArity1_n { - @Parameters(arity = "1..*") - List params; - } - ArrayParamsArity1_n params = CommandLine.populateCommand(new ArrayParamsArity1_n(), "a", "b", "c"); - assertEquals(Arrays.asList("a", "b", "c"), params.params); - - params = CommandLine.populateCommand(new ArrayParamsArity1_n(), "a"); - assertEquals(Arrays.asList("a"), params.params); - - try { - params = CommandLine.populateCommand(new ArrayParamsArity1_n()); - fail("Should not accept input with missing parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameters at positions 0..*: ", ex.getMessage()); - } - } - - @Test - public void testArrayParametersArity2_n() { - class ArrayParamsArity2_n { - @Parameters(arity = "2..*") - List params; - } - ArrayParamsArity2_n params = CommandLine.populateCommand(new ArrayParamsArity2_n(), "a", "b", "c"); - assertEquals(Arrays.asList("a", "b", "c"), params.params); - - try { - params = CommandLine.populateCommand(new ArrayParamsArity2_n(), "a"); - fail("Should not accept input with missing parameter"); - } catch (MissingParameterException ex) { - assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [a]", ex.getMessage()); - } - - try { - params = CommandLine.populateCommand(new ArrayParamsArity2_n()); - fail("Should not accept input with missing parameter"); - } catch (MissingParameterException ex) { - assertEquals("positional parameter at index 0..* () requires at least 2 values, but none were specified.", ex.getMessage()); - } - } - - @Test - public void testNonVarargArrayParametersWithNegativeArityConsumesZeroArguments() { - class NonVarArgArrayParamsNegativeArity { - @Parameters(arity = "-1") - List params; - } - try { - CommandLine.populateCommand(new NonVarArgArrayParamsNegativeArity(), "a", "b", "c"); - fail("Expected UnmatchedArgumentException"); - } catch (UnmatchedArgumentException ex) { - assertEquals("Unmatched arguments [a, b, c]", ex.getMessage()); - } - try { - CommandLine.populateCommand(new NonVarArgArrayParamsNegativeArity(), "a"); - fail("Expected UnmatchedArgumentException"); - } catch (UnmatchedArgumentException ex) { - assertEquals("Unmatched argument [a]", ex.getMessage()); - } - NonVarArgArrayParamsNegativeArity params = CommandLine.populateCommand(new NonVarArgArrayParamsNegativeArity()); - assertEquals(null, params.params); - } - - @Test - public void testNonVarargArrayParametersWithArity0() { - class NonVarArgArrayParamsZeroArity { - @Parameters(arity = "0") - List params; - } - try { - CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity(), "a", "b", "c"); - fail("Expected UnmatchedArgumentException"); - } catch (UnmatchedArgumentException ex) { - assertEquals("Unmatched arguments [a, b, c]", ex.getMessage()); - } - try { - CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity(), "a"); - fail("Expected UnmatchedArgumentException"); - } catch (UnmatchedArgumentException ex) { - assertEquals("Unmatched argument [a]", ex.getMessage()); - } - NonVarArgArrayParamsZeroArity params = CommandLine.populateCommand(new NonVarArgArrayParamsZeroArity()); - assertEquals(null, params.params); - } - - @Test - public void testNonVarargArrayParametersWithArity1() { - class NonVarArgArrayParamsArity1 { - @Parameters(arity = "1") - List params; - } - NonVarArgArrayParamsArity1 actual = CommandLine.populateCommand(new NonVarArgArrayParamsArity1(), "a", "b", "c"); - assertEquals(Arrays.asList("a", "b", "c"), actual.params); - - NonVarArgArrayParamsArity1 params = CommandLine.populateCommand(new NonVarArgArrayParamsArity1(), "a"); - assertEquals(Arrays.asList("a"), params.params); - - try { - params = CommandLine.populateCommand(new NonVarArgArrayParamsArity1()); - fail("Should not accept input with missing parameter"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameter: ", ex.getMessage()); - } - } - - @Test - public void testNonVarargArrayParametersWithArity2() { - class NonVarArgArrayParamsArity2 { - @Parameters(arity = "2") - List params; - } - NonVarArgArrayParamsArity2 params = null; - try { - CommandLine.populateCommand(new NonVarArgArrayParamsArity2(), "a", "b", "c"); - fail("expected MissingParameterException"); - } catch (MissingParameterException ex) { - assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [c]", ex.getMessage()); - } - - try { - params = CommandLine.populateCommand(new NonVarArgArrayParamsArity2(), "a"); - fail("Should not accept input with missing parameter"); - } catch (MissingParameterException ex) { - assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [a]", ex.getMessage()); - } - - try { - params = CommandLine.populateCommand(new NonVarArgArrayParamsArity2()); - fail("Should not accept input with missing parameter"); - } catch (MissingParameterException ex) { - assertEquals("positional parameter at index 0..* () requires at least 2 values, but none were specified.", ex.getMessage()); - } - } - @Test - public void testMixPositionalParamsWithOptions_ParamsUnboundedArity_isGreedy() { - class Arg { - @Parameters(arity = "1..*") List parameters; - @Option(names = "-o") List options; - } - Arg result = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3", "p4"); - assertEquals(Arrays.asList("p1", "p2", "-o", "v2", "p3", "p4"), result.parameters); - assertEquals(Arrays.asList("v1"), result.options); - - Arg result2 = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "-o", "v2", "p3"); - assertEquals(Arrays.asList("p1", "-o", "v2", "p3"), result2.parameters); - assertEquals(Arrays.asList("v1"), result2.options); - - try { - CommandLine.populateCommand(new Arg(), "-o", "v1", "-o", "v2"); - fail("Expected MissingParameterException"); - } catch (MissingParameterException ex) { - assertEquals("Missing required parameters at positions 0..*: ", ex.getMessage()); - } - } - - @Test - public void test130MixPositionalParamsWithOptions() { - @CommandLine.Command(name = "test-command", description = "tests help from a command script") - class Arg { - - @Parameters(description = "some parameters") - List parameters; - - @Option(names = {"-cp", "--codepath"}, description = "the codepath") - List codepath; - } - Arg result = CommandLine.populateCommand(new Arg(), "--codepath", "/usr/x.jar", "placeholder", "-cp", "/bin/y.jar", "another"); - assertEquals(Arrays.asList("/usr/x.jar", "/bin/y.jar"), result.codepath); - assertEquals(Arrays.asList("placeholder", "another"), result.parameters); - } - - @Test - public void test130MixPositionalParamsWithOptions1() { - class Arg { - @Parameters List parameters; - @Option(names = "-o") List options; - } - Arg result = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3"); - assertEquals(Arrays.asList("v1", "v2"), result.options); - assertEquals(Arrays.asList("p1", "p2", "p3"), result.parameters); - } - - @Test - public void test130MixPositionalParamsWithOptionsArity() { - class Arg { - @Parameters(arity = "2") List parameters; - @Option(names = "-o") List options; - } - Arg result = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3", "p4"); - assertEquals(Arrays.asList("v1", "v2"), result.options); - assertEquals(Arrays.asList("p1", "p2", "p3", "p4"), result.parameters); - - Arg result2 = CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "-o", "v2", "p3"); - assertEquals(Arrays.asList("v1"), result2.options); - assertEquals(Arrays.asList("p1", "-o", "v2", "p3"), result2.parameters); - - try { - CommandLine.populateCommand(new Arg(), "-o", "v1", "p1", "p2", "-o", "v2", "p3"); - fail("Expected MissingParameterException"); - } catch (MissingParameterException ex) { - assertEquals("positional parameter at index 0..* () requires at least 2 values, but only 1 were specified: [p3]", ex.getMessage()); - } - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineHelpTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineHelpTest.java deleted file mode 100644 index 3651b221af6..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineHelpTest.java +++ /dev/null @@ -1,2536 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.tools.picocli; - -import org.junit.After; -import org.junit.Ignore; -import org.junit.Test; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.IStyle; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Style; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.Ansi.Text; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.ColorScheme; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Help.TextTable; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Parameters; -import org.apache.logging.log4j.core.tools.picocli.CommandLine.Command; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.lang.String; -import java.lang.reflect.Field; -import java.net.InetAddress; -import java.net.URI; -import java.net.URL; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import static java.lang.String.format; -import static org.junit.Assert.*; - -/** - * Tests for picoCLI's "Usage" help functionality. - */ -public class CommandLineHelpTest { - private static final String LINESEP = System.getProperty("line.separator"); - - @After - public void after() { - System.getProperties().remove("picocli.color.commands"); - System.getProperties().remove("picocli.color.options"); - System.getProperties().remove("picocli.color.parameters"); - System.getProperties().remove("picocli.color.optionParams"); - } - private static String usageString(Object annotatedObject, Help.Ansi ansi) throws UnsupportedEncodingException { - return usageString(new CommandLine(annotatedObject), ansi); - } - private static String usageString(CommandLine commandLine, Help.Ansi ansi) throws UnsupportedEncodingException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - commandLine.usage(new PrintStream(baos, true, "UTF8"), ansi); - String result = baos.toString("UTF8"); - - if (ansi == Help.Ansi.AUTO) { - baos.reset(); - commandLine.usage(new PrintStream(baos, true, "UTF8")); - assertEquals(result, baos.toString("UTF8")); - } else if (ansi == Help.Ansi.ON) { - baos.reset(); - commandLine.usage(new PrintStream(baos, true, "UTF8"), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(result, baos.toString("UTF8")); - } - return result; - } - private static Field field(Class cls, String fieldName) throws NoSuchFieldException { - return cls.getDeclaredField(fieldName); - } - private static Field[] fields(Class cls, String... fieldNames) throws NoSuchFieldException { - Field[] result = new Field[fieldNames.length]; - for (int i = 0; i < fieldNames.length; i++) { - result[i] = cls.getDeclaredField(fieldNames[i]); - } - return result; - } - - @Test - public void testWithoutShowDefaultValues() throws Exception { - @CommandLine.Command() - class Params { - @Option(names = {"-f", "--file"}, required = true, description = "the file to use") File file; - } - String result = usageString(new Params(), Help.Ansi.OFF); - assertEquals(format("" + - "Usage:
    -f=%n" + - " -f, --file= the file to use%n", - ""), result); - } - - @Test - public void testShowDefaultValues() throws Exception { - @CommandLine.Command(showDefaultValues = true) - class Params { - @Option(names = {"-f", "--file"}, required = true, description = "the file to use") - File file = new File("theDefault.txt"); - } - String result = usageString(new Params(), Help.Ansi.OFF); - assertEquals(format("" + - "Usage:
    -f=%n" + - " -f, --file= the file to use%n" + - " Default: theDefault.txt%n"), result); - } - - @Test - public void testShowDefaultValuesArrayField() throws Exception { - @CommandLine.Command(showDefaultValues = true) - class Params { - @Option(names = {"-x", "--array"}, required = true, description = "the array") - int[] array = {1, 5, 11, 23}; - } - String result = usageString(new Params(), Help.Ansi.OFF); - assertEquals(format("" + - "Usage:
    -x= [-x=]...%n" + - " -x, --array= the array%n" + - " Default: [1, 5, 11, 23]%n"), result); - } - - @Test - public void testUsageSeparatorWithoutDefault() throws Exception { - @Command() - class Params { - @Option(names = {"-f", "--file"}, required = true, description = "the file to use") File file = new File("def.txt"); - } - String result = usageString(new Params(), Help.Ansi.OFF); - assertEquals(format("" + - "Usage:
    -f=%n" + - " -f, --file= the file to use%n", - ""), result); - } - - @Test - public void testUsageSeparator() throws Exception { - @Command(showDefaultValues = true) - class Params { - @Option(names = {"-f", "--file"}, required = true, description = "the file to use") File file = new File("def.txt"); - } - String result = usageString(new Params(), Help.Ansi.OFF); - assertEquals(format("" + - "Usage:
    -f=%n" + - " -f, --file= the file to use%n" + - " Default: def.txt%n", - ""), result); - } - - @Test - public void testUsageParamLabels() throws Exception { - @Command() - class ParamLabels { - @Option(names = "-P", paramLabel = "KEY=VALUE", type = {String.class, String.class}, - description = "Project properties (key-value pairs)") Map props; - @Option(names = "-f", paramLabel = "FILE", description = "files") File[] f; - @Option(names = "-n", description = "a number option") int number; - @Parameters(index = "0", paramLabel = "NUM", description = "number param") int n; - @Parameters(index = "1", description = "the host parameter") InetAddress host; - } - String result = usageString(new ParamLabels(), Help.Ansi.OFF); - assertEquals(format("" + - "Usage:
    [-n=] [-f=FILE]... [-P=KEY=VALUE]... NUM %n" + - " NUM number param%n" + - " the host parameter%n" + - " -f= FILE files%n" + - " -n= a number option%n" + - " -P= KEY=VALUE Project properties (key-value pairs)%n", - ""), result); - } - - @Test - public void testUsageParamLabelsWithLongMapOptionName() throws Exception { - @Command() - class ParamLabels { - @Option(names = {"-P", "--properties"}, - paramLabel = "KEY=VALUE", type = {String.class, String.class}, - description = "Project properties (key-value pairs)") Map props; - @Option(names = "-f", paramLabel = "FILE", description = "a file") File f; - @Option(names = "-n", description = "a number option") int number; - @Parameters(index = "0", paramLabel = "NUM", description = "number param") int n; - @Parameters(index = "1", description = "the host parameter") InetAddress host; - } - String result = usageString(new ParamLabels(), Help.Ansi.OFF); - assertEquals(format("" + - "Usage:
    [-f=FILE] [-n=] [-P=KEY=VALUE]... NUM %n" + - " NUM number param%n" + - " the host parameter%n" + - " -f= FILE a file%n" + - " -n= a number option%n" + - " -P, --properties=KEY=VALUE Project properties (key-value pairs)%n", - ""), result); - } - - // --------------- - @Test - public void testUsageVariableArityRequiredShortOptionArray() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "-a", required = true, paramLabel = "ARG") // default - String[] a; - @Option(names = "-b", required = true, paramLabel = "ARG", arity = "0..*") - List b; - @Option(names = "-c", required = true, paramLabel = "ARG", arity = "1..*") - String[] c; - @Option(names = "-d", required = true, paramLabel = "ARG", arity = "2..*") - List d; - } - String expected = String.format("" + - "Usage:
    -a=ARG [-a=ARG]... -b=[ARG]... [-b=[ARG]...]... -c=ARG...%n" + - " [-c=ARG...]... -d=ARG ARG... [-d=ARG ARG...]...%n" + - " -a= ARG%n" + - " -b= [ARG]...%n" + - " -c= ARG...%n" + - " -d= ARG ARG...%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageVariableArityShortOptionArray() throws UnsupportedEncodingException { - class Args { - @Option(names = "-a", paramLabel = "ARG") // default - List a; - @Option(names = "-b", paramLabel = "ARG", arity = "0..*") - String[] b; - @Option(names = "-c", paramLabel = "ARG", arity = "1..*") - List c; - @Option(names = "-d", paramLabel = "ARG", arity = "2..*") - String[] d; - } - String expected = String.format("" + - "Usage:
    [-a=ARG]... [-b=[ARG]...]... [-c=ARG...]... [-d=ARG%n" + - " ARG...]...%n" + - " -a= ARG%n" + - " -b= [ARG]...%n" + - " -c= ARG...%n" + - " -d= ARG ARG...%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityRequiredShortOptionArray() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "-a", required = true, paramLabel = "ARG", arity = "0..1") - List a; - @Option(names = "-b", required = true, paramLabel = "ARG", arity = "1..2") - String[] b; - @Option(names = "-c", required = true, paramLabel = "ARG", arity = "1..3") - String[] c; - @Option(names = "-d", required = true, paramLabel = "ARG", arity = "2..4") - String[] d; - } - String expected = String.format("" + - "Usage:
    -a[=ARG] [-a[=ARG]]... -b=ARG [ARG] [-b=ARG [ARG]]...%n" + - " -c=ARG [ARG [ARG]] [-c=ARG [ARG [ARG]]]... -d=ARG ARG [ARG%n" + - " [ARG]] [-d=ARG ARG [ARG [ARG]]]...%n" + - " -a= [ARG]%n" + - " -b= ARG [ARG]%n" + - " -c= ARG [ARG [ARG]]%n" + - " -d= ARG ARG [ARG [ARG]]%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityShortOptionArray() throws UnsupportedEncodingException { - class Args { - @Option(names = "-a", paramLabel = "ARG", arity = "0..1") - List a; - @Option(names = "-b", paramLabel = "ARG", arity = "1..2") - String[] b; - @Option(names = "-c", paramLabel = "ARG", arity = "1..3") - String[] c; - @Option(names = "-d", paramLabel = "ARG", arity = "2..4") - String[] d; - } - String expected = String.format("" + - "Usage:
    [-a[=ARG]]... [-b=ARG [ARG]]... [-c=ARG [ARG [ARG]]]...%n" + - " [-d=ARG ARG [ARG [ARG]]]...%n" + - " -a= [ARG]%n" + - " -b= ARG [ARG]%n" + - " -c= ARG [ARG [ARG]]%n" + - " -d= ARG ARG [ARG [ARG]]%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityRequiredShortOptionArray() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "-a", required = true, paramLabel = "ARG") // default - String[] a; - @Option(names = "-b", required = true, paramLabel = "ARG", arity = "0") - String[] b; - @Option(names = "-c", required = true, paramLabel = "ARG", arity = "1") - String[] c; - @Option(names = "-d", required = true, paramLabel = "ARG", arity = "2") - String[] d; - } - String expected = String.format("" + - "Usage:
    -b [-b]... -a=ARG [-a=ARG]... -c=ARG [-c=ARG]... -d=ARG ARG%n" + - " [-d=ARG ARG]...%n" + - " -a= ARG%n" + - " -b%n" + - " -c= ARG%n" + - " -d= ARG ARG%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityShortOptionArray() throws UnsupportedEncodingException { - class Args { - @Option(names = "-a", paramLabel = "ARG") // default - String[] a; - @Option(names = "-b", paramLabel = "ARG", arity = "0") - String[] b; - @Option(names = "-c", paramLabel = "ARG", arity = "1") - String[] c; - @Option(names = "-d", paramLabel = "ARG", arity = "2") - String[] d; - } - String expected = String.format("" + - "Usage:
    [-b]... [-a=ARG]... [-c=ARG]... [-d=ARG ARG]...%n" + - " -a= ARG%n" + - " -b%n" + - " -c= ARG%n" + - " -d= ARG ARG%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - //-------------- - @Test - public void testUsageVariableArityRequiredLongOptionArray() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "--aa", required = true, paramLabel = "ARG") // default - String[] a; - @Option(names = "--bb", required = true, paramLabel = "ARG", arity = "0..*") - List b; - @Option(names = "--cc", required = true, paramLabel = "ARG", arity = "1..*") - String[] c; - @Option(names = "--dd", required = true, paramLabel = "ARG", arity = "2..*") - List d; - } - String expected = String.format("" + - "Usage:
    --aa=ARG [--aa=ARG]... --bb=[ARG]... [--bb=[ARG]...]...%n" + - " --cc=ARG... [--cc=ARG...]... --dd=ARG ARG... [--dd=ARG%n" + - " ARG...]...%n" + - " --aa=ARG%n" + - " --bb=[ARG]...%n" + - " --cc=ARG...%n" + - " --dd=ARG ARG...%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageVariableArityLongOptionArray() throws UnsupportedEncodingException { - class Args { - @Option(names = "--aa", paramLabel = "ARG") // default - List a; - @Option(names = "--bb", paramLabel = "ARG", arity = "0..*") - String[] b; - @Option(names = "--cc", paramLabel = "ARG", arity = "1..*") - List c; - @Option(names = "--dd", paramLabel = "ARG", arity = "2..*") - String[] d; - } - String expected = String.format("" + - "Usage:
    [--aa=ARG]... [--bb=[ARG]...]... [--cc=ARG...]... [--dd=ARG%n" + - " ARG...]...%n" + - " --aa=ARG%n" + - " --bb=[ARG]...%n" + - " --cc=ARG...%n" + - " --dd=ARG ARG...%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityRequiredLongOptionArray() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "--aa", required = true, paramLabel = "ARG", arity = "0..1") - List a; - @Option(names = "--bb", required = true, paramLabel = "ARG", arity = "1..2") - String[] b; - @Option(names = "--cc", required = true, paramLabel = "ARG", arity = "1..3") - String[] c; - @Option(names = "--dd", required = true, paramLabel = "ARG", arity = "2..4", description = "foobar") - String[] d; - } - String expected = String.format("" + - "Usage:
    --aa[=ARG] [--aa[=ARG]]... --bb=ARG [ARG] [--bb=ARG%n" + - " [ARG]]... --cc=ARG [ARG [ARG]] [--cc=ARG [ARG [ARG]]]...%n" + - " --dd=ARG ARG [ARG [ARG]] [--dd=ARG ARG [ARG [ARG]]]...%n" + - " --aa[=ARG]%n" + - " --bb=ARG [ARG]%n" + - " --cc=ARG [ARG [ARG]]%n" + - " --dd=ARG ARG [ARG [ARG]]%n" + - " foobar%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityLongOptionArray() throws UnsupportedEncodingException { - class Args { - @Option(names = "--aa", paramLabel = "ARG", arity = "0..1") - List a; - @Option(names = "--bb", paramLabel = "ARG", arity = "1..2") - String[] b; - @Option(names = "--cc", paramLabel = "ARG", arity = "1..3") - String[] c; - @Option(names = "--dd", paramLabel = "ARG", arity = "2..4", description = "foobar") - String[] d; - } - String expected = String.format("" + - "Usage:
    [--aa[=ARG]]... [--bb=ARG [ARG]]... [--cc=ARG [ARG%n" + - " [ARG]]]... [--dd=ARG ARG [ARG [ARG]]]...%n" + - " --aa[=ARG]%n" + - " --bb=ARG [ARG]%n" + - " --cc=ARG [ARG [ARG]]%n" + - " --dd=ARG ARG [ARG [ARG]]%n" + - " foobar%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityRequiredLongOptionArray() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "--aa", required = true, paramLabel = "ARG") // default - String[] a; - @Option(names = "--bb", required = true, paramLabel = "ARG", arity = "0") - String[] b; - @Option(names = "--cc", required = true, paramLabel = "ARG", arity = "1") - String[] c; - @Option(names = "--dd", required = true, paramLabel = "ARG", arity = "2") - String[] d; - } - String expected = String.format("" + - "Usage:
    --bb [--bb]... --aa=ARG [--aa=ARG]... --cc=ARG%n" + - " [--cc=ARG]... --dd=ARG ARG [--dd=ARG ARG]...%n" + - " --aa=ARG%n" + - " --bb%n" + - " --cc=ARG%n" + - " --dd=ARG ARG%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityLongOptionArray() throws UnsupportedEncodingException { - class Args { - @Option(names = "--aa", paramLabel = "ARG") // default - String[] a; - @Option(names = "--bb", paramLabel = "ARG", arity = "0") - String[] b; - @Option(names = "--cc", paramLabel = "ARG", arity = "1") - String[] c; - @Option(names = "--dd", paramLabel = "ARG", arity = "2") - String[] d; - } - String expected = String.format("" + - "Usage:
    [--bb]... [--aa=ARG]... [--cc=ARG]... [--dd=ARG ARG]...%n" + - " --aa=ARG%n" + - " --bb%n" + - " --cc=ARG%n" + - " --dd=ARG ARG%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - //------------------ - @Test - public void testUsageVariableArityRequiredShortOptionMap() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "-a", required = true, paramLabel = "KEY=VAL") // default - Map a; - @Option(names = "-b", required = true, arity = "0..*") - @SuppressWarnings("unchecked") - Map b; - @Option(names = "-c", required = true, arity = "1..*", type = {String.class, TimeUnit.class}) - Map c; - @Option(names = "-d", required = true, arity = "2..*", type = {Integer.class, URL.class}, description = "description") - Map d; - } - String expected = String.format("" + - "Usage:
    -a=KEY=VAL [-a=KEY=VAL]... -b=[]... [-b=%n" + - " []...]... -c=...%n" + - " [-c=...]... -d=%n" + - " ... [-d= ...]...%n" + - " -a= KEY=VAL%n" + - " -b= []...%n" + - " -c= ...%n" + - " -d= ...%n" + - " description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageVariableArityOptionMap() throws UnsupportedEncodingException { - class Args { - @Option(names = "-a") // default - Map a; - @Option(names = "-b", arity = "0..*", type = {Integer.class, Integer.class}) - Map b; - @Option(names = "-c", paramLabel = "KEY=VALUE", arity = "1..*", type = {String.class, TimeUnit.class}) - Map c; - @Option(names = "-d", arity = "2..*", type = {String.class, URL.class}, description = "description") - Map d; - } - String expected = String.format("" + - "Usage:
    [-a=]... [-b=[]...]...%n" + - " [-c=KEY=VALUE...]... [-d= ...]...%n" + - " -a= %n" + - " -b= []...%n" + - " -c= KEY=VALUE...%n" + - " -d= ...%n" + - " description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityRequiredOptionMap() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "-a", required = true, arity = "0..1", description = "a description") - Map a; - @Option(names = "-b", required = true, arity = "1..2", type = {Integer.class, Integer.class}, description = "b description") - Map b; - @Option(names = "-c", required = true, arity = "1..3", type = {String.class, URL.class}, description = "c description") - Map c; - @Option(names = "-d", required = true, paramLabel = "K=URL", arity = "2..4", description = "d description") - Map d; - } - String expected = String.format("" + - "Usage:
    -a[=] [-a[=]]...%n" + - " -b= []%n" + - " [-b= []]...%n" + - " -c= [ []]%n" + - " [-c= [ []]]... -d=K=URL%n" + - " K=URL [K=URL [K=URL]] [-d=K=URL K=URL [K=URL [K=URL]]]...%n" + - " -a= [] a description%n" + - " -b= []%n" + - " b description%n" + - " -c= [ []]%n" + - " c description%n" + - " -d= K=URL K=URL [K=URL [K=URL]]%n" + - " d description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityOptionMap() throws UnsupportedEncodingException { - class Args { - @Option(names = "-a", arity = "0..1"/*, type = {UUID.class, URL.class}*/, description = "a description") - Map a; - @Option(names = "-b", arity = "1..2", type = {Long.class, UUID.class}, description = "b description") - Map b; - @Option(names = "-c", arity = "1..3", type = {Long.class}, description = "c description") - Map c; - @Option(names = "-d", paramLabel = "K=V", arity = "2..4", description = "d description") - Map d; - } - String expected = String.format("" + - "Usage:
    [-a[=]]... [-b= []]...%n" + - " [-c= [ []]]...%n" + - " [-d=K=V K=V [K=V [K=V]]]...%n" + - " -a= [] a description%n" + - " -b= []%n" + - " b description%n" + - " -c= [ []]%n" + - " c description%n" + - " -d= K=V K=V [K=V [K=V]] d description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityRequiredOptionMap() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Option(names = "-a", required = true, description = "a description") - Map a; - @Option(names = "-b", required = true, paramLabel = "KEY=VAL", arity = "0", description = "b description") - @SuppressWarnings("unchecked") - Map b; - @Option(names = "-c", required = true, arity = "1", type = {Long.class, File.class}, description = "c description") - Map c; - @Option(names = "-d", required = true, arity = "2", type = {URI.class, URL.class}, description = "d description") - Map d; - } - String expected = String.format("" + - "Usage:
    -b [-b]... -a= [-a=]...%n" + - " -c= [-c=]... -d= %n" + - " [-d= ]...%n" + - " -a= a description%n" + - " -b b description%n" + - " -c= c description%n" + - " -d= d description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityOptionMap() throws UnsupportedEncodingException { - class Args { - @Option(names = "-a", type = {Short.class, Field.class}, description = "a description") - Map a; - @Option(names = "-b", arity = "0", type = {UUID.class, Long.class}, description = "b description") - @SuppressWarnings("unchecked") - Map b; - @Option(names = "-c", arity = "1", description = "c description") - Map c; - @Option(names = "-d", arity = "2", type = {URI.class, URL.class}, description = "d description") - Map d; - } - String expected = String.format("" + - "Usage:
    [-b]... [-a=]... [-c=]...%n" + - " [-d= ]...%n" + - " -a= a description%n" + - " -b b description%n" + - " -c= c description%n" + - " -d= d description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - //-------------- - @Test - public void testUsageVariableArityParametersArray() throws UnsupportedEncodingException { - // if option is required at least once and can be specified multiple times: - // -f=ARG [-f=ARG]... - class Args { - @Parameters(paramLabel = "APARAM", description = "APARAM description") - String[] a; - @Parameters(arity = "0..*", description = "b description") - List b; - @Parameters(arity = "1..*", description = "c description") - String[] c; - @Parameters(arity = "2..*", description = "d description") - List d; - } - String expected = String.format("" + - "Usage:
    [APARAM]... []... ... ...%n" + - " [APARAM]... APARAM description%n" + - " []... b description%n" + - " ... c description%n" + - " ... d description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityParameterArray() throws UnsupportedEncodingException { - class Args { - @Parameters(index = "0", paramLabel = "PARAMA", arity = "0..1", description = "PARAMA description") - List a; - @Parameters(index = "0", paramLabel = "PARAMB", arity = "1..2", description = "PARAMB description") - String[] b; - @Parameters(index = "0", paramLabel = "PARAMC", arity = "1..3", description = "PARAMC description") - String[] c; - @Parameters(index = "0", paramLabel = "PARAMD", arity = "2..4", description = "PARAMD description") - String[] d; - } - String expected = String.format("" + - "Usage:
    [PARAMA] PARAMB [PARAMB] PARAMC [PARAMC [PARAMC]] PARAMD%n" + - " PARAMD [PARAMD [PARAMD]]%n" + - " [PARAMA] PARAMA description%n" + - " PARAMB [PARAMB] PARAMB description%n" + - " PARAMC [PARAMC [PARAMC]]%n" + - " PARAMC description%n" + - " PARAMD PARAMD [PARAMD [PARAMD]]%n" + - " PARAMD description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityParametersArray() throws UnsupportedEncodingException { - class Args { - @Parameters(description = "a description (default arity)") - String[] a; - @Parameters(index = "0", arity = "0", description = "b description (arity=0)") - String[] b; - @Parameters(index = "1", arity = "1", description = "b description (arity=1)") - String[] c; - @Parameters(index = "2", arity = "2", description = "b description (arity=2)") - String[] d; - } - String expected = String.format("" + - "Usage:
    []... %n" + - " b description (arity=0)%n" + - " []... a description (default arity)%n" + - " b description (arity=1)%n" + - " b description (arity=2)%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageVariableArityParametersMap() throws UnsupportedEncodingException { - class Args { - @Parameters() - Map a; - @Parameters(arity = "0..*", description = "a description (arity=0..*)") - Map b; - @Parameters(paramLabel = "KEY=VALUE", arity = "1..*", type = {String.class, TimeUnit.class}) - Map c; - @Parameters(arity = "2..*", type = {String.class, URL.class}, description = "description") - Map d; - } - String expected = String.format("" + - "Usage:
    []... []... KEY=VALUE...%n" + - " ...%n" + - " []...%n" + - " []... a description (arity=0..*)%n" + - " KEY=VALUE...%n" + - " ...%n" + - " description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageRangeArityParametersMap() throws UnsupportedEncodingException { - class Args { - @Parameters(index = "0", arity = "0..1"/*, type = {UUID.class, URL.class}*/, description = "a description") - Map a; - @Parameters(index = "1", arity = "1..2", type = {Long.class, UUID.class}, description = "b description") - Map b; - @Parameters(index = "2", arity = "1..3", type = {Long.class}, description = "c description") - Map c; - @Parameters(index = "3", paramLabel = "K=V", arity = "2..4", description = "d description") - Map d; - } - String expected = String.format("" + - "Usage:
    [] [] %n" + - " [ []] K=V K=V [K=V [K=V]]%n" + - " [] a description%n" + - " []%n" + - " b description%n" + - " [ []]%n" + - " c description%n" + - " K=V K=V [K=V [K=V]] d description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - - @Test - public void testUsageFixedArityParametersMap() throws UnsupportedEncodingException { - class Args { - @Parameters(type = {Short.class, Field.class}, description = "a description") - Map a; - @Parameters(index = "0", arity = "0", type = {UUID.class, Long.class}, description = "b description (arity=0)") - @SuppressWarnings("unchecked") - Map b; - @Parameters(index = "1", arity = "1", description = "c description") - Map c; - @Parameters(index = "2", arity = "2", type = {URI.class, URL.class}, description = "d description") - Map d; - } - String expected = String.format("" + - "Usage:
    []... %n" + - " b description (arity=0)%n" + - " []... a description%n" + - " c description%n" + - " d description%n"); - //CommandLine.usage(new Args(), System.out); - assertEquals(expected, usageString(new Args(), Help.Ansi.OFF)); - } - //---------- - @Test - public void testShortestFirstComparator_sortsShortestFirst() { - String[] values = {"12345", "12", "123", "123456", "1", "", "1234"}; - Arrays.sort(values, new Help.ShortestFirst()); - String[] expected = {"", "1", "12", "123", "1234", "12345", "123456"}; - assertArrayEquals(expected, values); - } - - @Test - public void testShortestFirstComparator_sortsDeclarationOrderIfEqualLength() { - String[] values = {"-d", "-", "-a", "--alpha", "--b", "--a", "--beta"}; - Arrays.sort(values, new Help.ShortestFirst()); - String[] expected = {"-", "-d", "-a", "--b", "--a", "--beta", "--alpha"}; - assertArrayEquals(expected, values); - } - - @Test - public void testSortByShortestOptionNameComparator() throws Exception { - class App { - @Option(names = {"-t", "--aaaa"}) boolean aaaa; - @Option(names = {"--bbbb", "-k"}) boolean bbbb; - @Option(names = {"-c", "--cccc"}) boolean cccc; - } - Field[] fields = fields(App.class, "aaaa", "bbbb", "cccc"); // -tkc - Arrays.sort(fields, new Help.SortByShortestOptionNameAlphabetically()); - Field[] expected = fields(App.class, "cccc", "bbbb", "aaaa"); // -ckt - assertArrayEquals(expected, fields); - } - - @Test - public void testSortByOptionArityAndNameComparator_sortsByMaxThenMinThenName() throws Exception { - class App { - @Option(names = {"-t", "--aaaa"} ) boolean tImplicitArity0; - @Option(names = {"-e", "--EEE"}, arity = "1" ) boolean explicitArity1; - @Option(names = {"--bbbb", "-k"} ) boolean kImplicitArity0; - @Option(names = {"--AAAA", "-a"} ) int aImplicitArity1; - @Option(names = {"--BBBB", "-z"} ) String[] zImplicitArity1; - @Option(names = {"--ZZZZ", "-b"}, arity = "1..3") String[] bExplicitArity1_3; - @Option(names = {"-f", "--ffff"} ) boolean fImplicitArity0; - } - Field[] fields = fields(App.class, "tImplicitArity0", "explicitArity1", "kImplicitArity0", - "aImplicitArity1", "zImplicitArity1", "bExplicitArity1_3", "fImplicitArity0"); - Arrays.sort(fields, new Help.SortByOptionArityAndNameAlphabetically()); - Field[] expected = fields(App.class, - "fImplicitArity0", - "kImplicitArity0", - "tImplicitArity0", - "aImplicitArity1", - "explicitArity1", - "zImplicitArity1", - "bExplicitArity1_3"); - assertArrayEquals(expected, fields); - } - - @Test - public void testCreateMinimalOptionRenderer_ReturnsMinimalOptionRenderer() { - assertEquals(Help.MinimalOptionRenderer.class, Help.createMinimalOptionRenderer().getClass()); - } - - @Test - public void testMinimalOptionRenderer_rendersFirstDeclaredOptionNameAndDescription() { - class Example { - @Option(names = {"---long", "-L"}, description = "long description") String longField; - @Option(names = {"-b", "-a", "--alpha"}, description = "other") String otherField; - } - Help.IOptionRenderer renderer = Help.createMinimalOptionRenderer(); - Help help = new Help(new Example(), Help.defaultColorScheme(Help.Ansi.ON)); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - Field field = help.optionFields.get(0); - Text[][] row1 = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, Help.defaultColorScheme( - help.ansi())); - assertEquals(1, row1.length); - //assertArrayEquals(new String[]{"---long=", "long description"}, row1[0]); - assertArrayEquals(new Text[]{ - help.ansi().new Text(format("%s---long%s=%s%s", "@|fg(yellow) ", "|@", "@|italic ", "|@")), - help.ansi().new Text("long description")}, row1[0]); - - field = help.optionFields.get(1); - Text[][] row2 = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, Help.defaultColorScheme( - help.ansi())); - assertEquals(1, row2.length); - //assertArrayEquals(new String[]{"-b=", "other"}, row2[0]); - assertArrayEquals(new Text[]{ - help.ansi().new Text(format("%s-b%s=%s%s", "@|fg(yellow) ", "|@", "@|italic ", "|@")), - help.ansi().new Text("other")}, row2[0]); - } - - @Test - public void testCreateDefaultOptionRenderer_ReturnsDefaultOptionRenderer() { - assertEquals(Help.DefaultOptionRenderer.class, new Help(new UsageDemo()).createDefaultOptionRenderer().getClass()); - } - - private static Text[] textArray(Help help, String... str) { - return textArray(help.ansi(), str); - } - private static Text[] textArray(Help.Ansi ansi, String... str) { - Text[] result = new Text[str.length]; - for (int i = 0; i < str.length; i++) { - result[i] = str[i] == null ? Help.Ansi.EMPTY_TEXT : ansi.new Text(str[i]); - } - return result; - } - - @Test - public void testDefaultOptionRenderer_rendersShortestOptionNameThenOtherOptionNamesAndDescription() { - @Command(showDefaultValues = true) - class Example { - @Option(names = {"---long", "-L"}, description = "long description") String longField; - @Option(names = {"-b", "-a", "--alpha"}, description = "other") String otherField = "abc"; - } - Help help = new Help(new Example()); - Help.IOptionRenderer renderer = help.createDefaultOptionRenderer(); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - Field field = help.optionFields.get(0); - Text[][] row1 = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row1.length); - assertArrayEquals(Arrays.toString(row1[0]), textArray(help, "", "-L", ",", "---long=", "long description"), row1[0]); - //assertArrayEquals(Arrays.toString(row1[1]), textArray(help, "", "", "", "", " Default: null"), row1[1]); // #201 don't show null defaults - - field = help.optionFields.get(1); - Text[][] row2 = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(2, row2.length); - assertArrayEquals(Arrays.toString(row2[0]), textArray(help, "", "-b", ",", "-a, --alpha=", "other"), row2[0]); - assertArrayEquals(Arrays.toString(row2[1]), textArray(help, "", "", "", "", " Default: abc"), row2[1]); - } - - @Test - public void testDefaultOptionRenderer_rendersSpecifiedMarkerForRequiredOptionsWithDefault() { - @Command(requiredOptionMarker = '*', showDefaultValues = true) - class Example { - @Option(names = {"-b", "-a", "--alpha"}, required = true, description = "other") String otherField ="abc"; - } - Help help = new Help(new Example()); - Help.IOptionRenderer renderer = help.createDefaultOptionRenderer(); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - Field field = help.optionFields.get(0); - Text[][] row = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(2, row.length); - assertArrayEquals(Arrays.toString(row[0]), textArray(help, "*", "-b", ",", "-a, --alpha=", "other"), row[0]); - assertArrayEquals(Arrays.toString(row[1]), textArray(help, "", "", "", "", " Default: abc"), row[1]); - } - - @Test - public void testDefaultOptionRenderer_rendersSpecifiedMarkerForRequiredOptionsWithoutDefault() { - @Command(requiredOptionMarker = '*') - class Example { - @Option(names = {"-b", "-a", "--alpha"}, required = true, description = "other") String otherField ="abc"; - } - Help help = new Help(new Example()); - Help.IOptionRenderer renderer = help.createDefaultOptionRenderer(); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - Field field = help.optionFields.get(0); - Text[][] row = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row.length); - assertArrayEquals(Arrays.toString(row[0]), textArray(help, "*", "-b", ",", "-a, --alpha=", "other"), row[0]); - } - - @Test - public void testDefaultOptionRenderer_rendersSpacePrefixByDefaultForRequiredOptionsWithoutDefaultValue() { - class Example { - @Option(names = {"-b", "-a", "--alpha"}, required = true, description = "other") String otherField; - } - Help help = new Help(new Example()); - Help.IOptionRenderer renderer = help.createDefaultOptionRenderer(); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - Field field = help.optionFields.get(0); - Text[][] row = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row.length); - assertArrayEquals(Arrays.toString(row[0]), textArray(help, " ", "-b", ",", "-a, --alpha=", "other"), row[0]); - } - - @Test - public void testDefaultOptionRenderer_rendersSpacePrefixByDefaultForRequiredOptionsWithDefaultValue() { - //@Command(showDefaultValues = true) // set programmatically - class Example { - @Option(names = {"-b", "-a", "--alpha"}, required = true, description = "other") String otherField; - } - Help help = new Help(new Example()); - help.showDefaultValues = true; - Help.IOptionRenderer renderer = help.createDefaultOptionRenderer(); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - Field field = help.optionFields.get(0); - Text[][] row = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row.length); - assertArrayEquals(Arrays.toString(row[0]), textArray(help, " ", "-b", ",", "-a, --alpha=", "other"), row[0]); - // assertArrayEquals(Arrays.toString(row[1]), textArray(help, "", "", "", "", " Default: null"), row[1]); // #201 don't show null defaults - } - - @Test - public void testDefaultParameterRenderer_rendersSpacePrefixByDefaultForParametersWithPositiveArity() { - class Required { - @Parameters(description = "required") String required; - } - Help help = new Help(new Required()); - Help.IParameterRenderer renderer = help.createDefaultParameterRenderer(); - Help.IParamLabelRenderer parameterRenderer = Help.createMinimalParamLabelRenderer(); - Field field = help.positionalParametersFields.get(0); - Text[][] row1 = renderer.render(field.getAnnotation(Parameters.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row1.length); - assertArrayEquals(Arrays.toString(row1[0]), textArray(help, " ", "", "", "", "required"), row1[0]); - } - - @Test - public void testDefaultParameterRenderer_rendersSpecifiedMarkerForParametersWithPositiveArity() { - @Command(requiredOptionMarker = '*') - class Required { - @Parameters(description = "required") String required; - } - Help help = new Help(new Required()); - Help.IParameterRenderer renderer = help.createDefaultParameterRenderer(); - Help.IParamLabelRenderer parameterRenderer = Help.createMinimalParamLabelRenderer(); - Field field = help.positionalParametersFields.get(0); - Text[][] row1 = renderer.render(field.getAnnotation(Parameters.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row1.length); - assertArrayEquals(Arrays.toString(row1[0]), textArray(help, "*", "", "", "", "required"), row1[0]); - } - - @Test - public void testDefaultParameterRenderer_rendersSpacePrefixForParametersWithZeroArity() { - @Command(requiredOptionMarker = '*') - class Optional { - @Parameters(arity = "0..1", description = "optional") String optional; - } - Help help = new Help(new Optional()); - Help.IParameterRenderer renderer = help.createDefaultParameterRenderer(); - Help.IParamLabelRenderer parameterRenderer = Help.createMinimalParamLabelRenderer(); - Field field = help.positionalParametersFields.get(0); - Text[][] row1 = renderer.render(field.getAnnotation(Parameters.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row1.length); - assertArrayEquals(Arrays.toString(row1[0]), textArray(help, "", "", "", "", "optional"), row1[0]); - } - - @Test - public void testDefaultOptionRenderer_rendersCommaOnlyIfBothShortAndLongOptionNamesExist() { - class Example { - @Option(names = {"-v"}, description = "shortBool") boolean shortBoolean; - @Option(names = {"--verbose"}, description = "longBool") boolean longBoolean; - @Option(names = {"-x", "--xeno"}, description = "combiBool") boolean combiBoolean; - @Option(names = {"-s"}, description = "shortOnly") String shortOnlyField; - @Option(names = {"--long"}, description = "longOnly") String longOnlyField; - @Option(names = {"-b", "--beta"}, description = "combi") String combiField; - } - Help help = new Help(new Example()); - help.showDefaultValues = false; // omit default values from description column - Help.IOptionRenderer renderer = help.createDefaultOptionRenderer(); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - - String[][] expected = new String[][] { - {"", "-v", "", "", "shortBool"}, - {"", "", "", "--verbose", "longBool"}, - {"", "-x", ",", "--xeno", "combiBool"}, - {"", "-s", "=", "", "shortOnly"}, - {"", "", "", "--long=", "longOnly"}, - {"", "-b", ",", "--beta=", "combi"}, - }; - int i = -1; - for (Field field : help.optionFields) { - Text[][] row = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(1, row.length); - assertArrayEquals(Arrays.toString(row[0]), textArray(help, expected[++i]), row[0]); - } - } - - @Test - public void testDefaultOptionRenderer_omitsDefaultValuesForBooleanFields() { - @Command(showDefaultValues = true) - class Example { - @Option(names = {"-v"}, description = "shortBool") boolean shortBoolean; - @Option(names = {"--verbose"}, description = "longBool") Boolean longBoolean; - @Option(names = {"-s"}, description = "shortOnly") String shortOnlyField = "short"; - @Option(names = {"--long"}, description = "longOnly") String longOnlyField = "long"; - @Option(names = {"-b", "--beta"}, description = "combi") int combiField = 123; - } - Help help = new Help(new Example()); - Help.IOptionRenderer renderer = help.createDefaultOptionRenderer(); - Help.IParamLabelRenderer parameterRenderer = help.createDefaultParamLabelRenderer(); - - String[][] expected = new String[][] { - {"", "-v", "", "", "shortBool"}, - {"", "", "", "--verbose", "longBool"}, - {"", "-s", "=", "", "shortOnly"}, - {"", "", "", "", "Default: short"}, - {"", "", "", "--long=", "longOnly"}, - {"", "", "", "", "Default: long"}, - {"", "-b", ",", "--beta=", "combi"}, - {"", "", "", "", "Default: 123"}, - }; - int[] rowCount = {1, 1, 2, 2, 2}; - int i = -1; - int rowIndex = 0; - for (Field field : help.optionFields) { - Text[][] row = renderer.render(field.getAnnotation(Option.class), field, parameterRenderer, help.colorScheme); - assertEquals(rowCount[++i], row.length); - assertArrayEquals(Arrays.toString(row[0]), textArray(help, expected[rowIndex]), row[0]); - rowIndex += rowCount[i]; - } - } - - @Test - public void testCreateDefaultParameterRenderer_ReturnsDefaultParameterRenderer() { - assertEquals(Help.DefaultParamLabelRenderer.class, new Help(new UsageDemo()).createDefaultParamLabelRenderer().getClass()); - } - - @Test - public void testDefaultParameterRenderer_showsParamLabelIfPresentOrFieldNameOtherwise() { - class Example { - @Option(names = "--without" ) String longField; - @Option(names = "--with", paramLabel = "LABEL") String otherField; - } - Help help = new Help(new Example()); - Help.IParamLabelRenderer equalSeparatedParameterRenderer = help.createDefaultParamLabelRenderer(); - help.separator = " "; - Help.IParamLabelRenderer spaceSeparatedParameterRenderer = help.createDefaultParamLabelRenderer(); - - String[] expected = new String[] { - "", - "LABEL", - }; - int i = -1; - for (Field field : help.optionFields) { - i++; - Text withSpace = spaceSeparatedParameterRenderer.renderParameterLabel(field, help.ansi(), Collections.emptyList()); - assertEquals(withSpace.toString(), " " + expected[i], withSpace.toString()); - Text withEquals = equalSeparatedParameterRenderer.renderParameterLabel(field, help.ansi(), Collections.emptyList()); - assertEquals(withEquals.toString(), "=" + expected[i], withEquals.toString()); - } - } - - @Test - public void testDefaultParameterRenderer_appliesToPositionalArgumentsIgnoresSeparator() { - class WithLabel { @Parameters(paramLabel = "POSITIONAL_ARGS") String positional; } - class WithoutLabel { @Parameters() String positional; } - - Help withLabel = new Help(new WithLabel()); - Help.IParamLabelRenderer equals = withLabel.createDefaultParamLabelRenderer(); - withLabel.separator = "="; - Help.IParamLabelRenderer spaced = withLabel.createDefaultParamLabelRenderer(); - - Text withSpace = spaced.renderParameterLabel(withLabel.positionalParametersFields.get(0), withLabel.ansi(), Collections.emptyList()); - assertEquals(withSpace.toString(), "POSITIONAL_ARGS", withSpace.toString()); - Text withEquals = equals.renderParameterLabel(withLabel.positionalParametersFields.get(0), withLabel.ansi(), Collections.emptyList()); - assertEquals(withEquals.toString(), "POSITIONAL_ARGS", withEquals.toString()); - - Help withoutLabel = new Help(new WithoutLabel()); - withSpace = spaced.renderParameterLabel(withoutLabel.positionalParametersFields.get(0), withoutLabel.ansi(), Collections.emptyList()); - assertEquals(withSpace.toString(), "", withSpace.toString()); - withEquals = equals.renderParameterLabel(withoutLabel.positionalParametersFields.get(0), withoutLabel.ansi(), Collections.emptyList()); - assertEquals(withEquals.toString(), "", withEquals.toString()); - } - - @Test - public void testDefaultLayout_addsEachRowToTable() { - final Text[][] values = { - textArray(Help.Ansi.OFF, "a", "b", "c", "d"), - textArray(Help.Ansi.OFF, "1", "2", "3", "4") - }; - final int[] count = {0}; - TextTable tt = new TextTable(Help.Ansi.OFF) { - @Override public void addRowValues(Text[] columnValues) { - assertArrayEquals(values[count[0]], columnValues); - count[0]++; - } - }; - Help.Layout layout = new Help.Layout(Help.defaultColorScheme(Help.Ansi.OFF), tt); - layout.layout(null, values); - assertEquals(2, count[0]); - } - - @Test - public void testAbreviatedSynopsis_withoutParameters() { - @CommandLine.Command(abbreviateSynopsis = true) - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [OPTIONS]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testAbreviatedSynopsis_withoutParameters_ANSI() { - @CommandLine.Command(abbreviateSynopsis = true) - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [OPTIONS]" + LINESEP).toString(), help.synopsis(0)); - } - - @Test - public void testAbreviatedSynopsis_withParameters() { - @CommandLine.Command(abbreviateSynopsis = true) - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [OPTIONS] []..." + LINESEP, help.synopsis(0)); - } - - @Test - public void testAbreviatedSynopsis_withParameters_ANSI() { - @CommandLine.Command(abbreviateSynopsis = true) - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [OPTIONS] [@|yellow |@]..." + LINESEP).toString(), help.synopsis(0)); - } - - @Test - public void testAbreviatedSynopsis_commandNameCustomizableDeclaratively() throws UnsupportedEncodingException { - @CommandLine.Command(abbreviateSynopsis = true, name = "aprogram") - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - String expected = "" + - "Usage: aprogram [OPTIONS] []...%n" + - " []...%n" + - " -c, --count=%n" + - " -v, --verbose%n"; - String actual = usageString(new CommandLine(new App()), Help.Ansi.OFF); - assertEquals(String.format(expected), actual); - } - - @Test - public void testAbreviatedSynopsis_commandNameCustomizableProgrammatically() throws UnsupportedEncodingException { - @CommandLine.Command(abbreviateSynopsis = true) - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - String expected = "" + - "Usage: anotherProgram [OPTIONS] []...%n" + - " []...%n" + - " -c, --count=%n" + - " -v, --verbose%n"; - String actual = usageString(new CommandLine(new App()).setCommandName("anotherProgram"), Help.Ansi.OFF); - assertEquals(String.format(expected), actual); - } - - @Test - public void testSynopsis_commandNameCustomizableDeclaratively() throws UnsupportedEncodingException { - @CommandLine.Command(name = "aprogram") - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - String expected = "" + - "Usage: aprogram [-v] [-c=] []...%n" + - " []...%n" + - " -c, --count=%n" + - " -v, --verbose%n"; - String actual = usageString(new CommandLine(new App()), Help.Ansi.OFF); - assertEquals(String.format(expected), actual); - } - - @Test - public void testSynopsis_commandNameCustomizableProgrammatically() throws UnsupportedEncodingException { - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - String expected = "" + - "Usage: anotherProgram [-v] [-c=] []...%n" + - " []...%n" + - " -c, --count=%n" + - " -v, --verbose%n"; - String actual = usageString(new CommandLine(new App()).setCommandName("anotherProgram"), Help.Ansi.OFF); - assertEquals(String.format(expected), actual); - } - - @Test - public void testSynopsis_optionalOptionArity1_n_withDefaultSeparator() { - @Command() class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, arity = "1..*") int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c=...]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_optionalOptionArity1_n_withDefaultSeparator_ANSI() { - @Command() class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, arity = "1..*") int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [@|yellow -v|@] [@|yellow -c|@=@|italic |@...]" + LINESEP), - help.synopsis(0)); - } - - @Test - public void testSynopsis_optionalOptionArity0_1_withSpaceSeparator() { - @CommandLine.Command(separator = " ") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, arity = "0..1") int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c []]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_optionalOptionArity0_1_withSpaceSeparator_ANSI() { - @CommandLine.Command(separator = " ") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, arity = "0..1") int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [@|yellow -v|@] [@|yellow -c|@ [@|italic |@]]" + LINESEP), help.synopsis(0)); - } - - @Test - public void testSynopsis_requiredOptionWithSeparator() { - @Command() class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, required = true) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] -c=" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_requiredOptionWithSeparator_ANSI() { - @Command() class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, required = true) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [@|yellow -v|@] @|yellow -c|@=@|italic |@" + LINESEP), help.synopsis(0)); - } - - @Test - public void testSynopsis_optionalOption_withSpaceSeparator() { - @CommandLine.Command(separator = " ") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c ]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_optionalOptionArity0_1__withSeparator() { - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, arity = "0..1") int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c[=]]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_optionalOptionArity0_n__withSeparator() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, arity = "0..*") int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - // NOTE Expected :
    [-v] [-c[=]...] but arity=0 for int field is weird anyway... - assertEquals("
    [-v] [-c=[]...]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_optionalOptionArity1_n__withSeparator() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}, arity = "1..*") int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c=...]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_withProgrammaticallySetSeparator_withParameters() throws UnsupportedEncodingException { - class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - CommandLine commandLine = new CommandLine(new App()).setSeparator(":"); - String actual = usageString(commandLine, Help.Ansi.OFF); - String expected = "" + - "Usage:
    [-v] [-c:] []...%n" + - " []...%n" + - " -c, --count:%n" + - " -v, --verbose%n"; - assertEquals(String.format(expected), actual); - } - - @Test - public void testSynopsis_withSeparator_withParameters() { - @CommandLine.Command(separator = ":") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c:] []..." + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_withSeparator_withParameters_ANSI() { - @CommandLine.Command(separator = ":") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters File[] files; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [@|yellow -v|@] [@|yellow -c|@:@|italic |@] [@|yellow |@]..." + LINESEP), - help.synopsis(0)); - } - - @Test - public void testSynopsis_withSeparator_withLabeledParameters() { - @Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters(paramLabel = "FILE") File[] files; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c=] [FILE]..." + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_withSeparator_withLabeledParameters_ANSI() { - @Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters(paramLabel = "FILE") File[] files; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [@|yellow -v|@] [@|yellow -c|@=@|italic |@] [@|yellow FILE|@]..." + LINESEP), - help.synopsis(0)); - } - - @Test - public void testSynopsis_withSeparator_withLabeledRequiredParameters() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters(paramLabel = "FILE", arity = "1..*") File[] files; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-v] [-c=] FILE..." + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_withSeparator_withLabeledRequiredParameters_ANSI() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters(paramLabel = "FILE", arity = "1..*") File[] files; - } - Help help = new Help(new App(), Help.Ansi.ON); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ [@|yellow -v|@] [@|yellow -c|@=@|italic |@] @|yellow FILE|@..." + LINESEP), - help.synopsis(0)); - } - - @Test - public void testSynopsis_clustersBooleanOptions() { - @Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--aaaa", "-a"}) boolean aBoolean; - @Option(names = {"--xxxx", "-x"}) Boolean xBoolean; - @Option(names = {"--count", "-c"}, paramLabel = "COUNT") int count; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    [-avx] [-c=COUNT]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_clustersRequiredBooleanOptions() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}, required = true) boolean verbose; - @Option(names = {"--aaaa", "-a"}, required = true) boolean aBoolean; - @Option(names = {"--xxxx", "-x"}, required = true) Boolean xBoolean; - @Option(names = {"--count", "-c"}, paramLabel = "COUNT") int count; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    -avx [-c=COUNT]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_clustersRequiredBooleanOptionsSeparately() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--aaaa", "-a"}) boolean aBoolean; - @Option(names = {"--xxxx", "-x"}) Boolean xBoolean; - @Option(names = {"--Verbose", "-V"}, required = true) boolean requiredVerbose; - @Option(names = {"--Aaaa", "-A"}, required = true) boolean requiredABoolean; - @Option(names = {"--Xxxx", "-X"}, required = true) Boolean requiredXBoolean; - @Option(names = {"--count", "-c"}, paramLabel = "COUNT") int count; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals("
    -AVX [-avx] [-c=COUNT]" + LINESEP, help.synopsis(0)); - } - - @Test - public void testSynopsis_clustersRequiredBooleanOptionsSeparately_ANSI() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--aaaa", "-a"}) boolean aBoolean; - @Option(names = {"--xxxx", "-x"}) Boolean xBoolean; - @Option(names = {"--Verbose", "-V"}, required = true) boolean requiredVerbose; - @Option(names = {"--Aaaa", "-A"}, required = true) boolean requiredABoolean; - @Option(names = {"--Xxxx", "-X"}, required = true) Boolean requiredXBoolean; - @Option(names = {"--count", "-c"}, paramLabel = "COUNT") int count; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text("@|bold
    |@ @|yellow -AVX|@ [@|yellow -avx|@] [@|yellow -c|@=@|italic COUNT|@]" + LINESEP), - help.synopsis(0)); - } - - @Test - public void testSynopsis_firstLineLengthAdjustedForSynopsisHeading() throws Exception { - //Usage: small-test-program [-acorv!?] [--version] [-h ] [-p |] [-d -// []] [-i -// [...]] - @CommandLine.Command(name="small-test-program", sortOptions = false, separator = " ") - class App { - @Option(names = "-a") boolean a; - @Option(names = "-c") boolean c; - @Option(names = "-o") boolean o; - @Option(names = "-r") boolean r; - @Option(names = "-v") boolean v; - @Option(names = "-!") boolean exclamation; - @Option(names = "-?") boolean question; - @Option(names = {"--version"}) boolean version; - @Option(names = {"--handle", "-h"}) int number; - @Option(names = {"--ppp", "-p"}, paramLabel = "|") File f; - @Option(names = {"--ddd", "-d"}, paramLabel = "", arity="1..2") File[] d; - @Option(names = {"--include", "-i"}, paramLabel = "") String pattern; - } - Help help = new Help(new App(), Help.Ansi.OFF); - String expected = "" + - "Usage: small-test-program [-!?acorv] [--version] [-h ] [-i" + LINESEP + - " ] [-p |] [-d " + LINESEP + - " []]..." + LINESEP; - assertEquals(expected, help.synopsisHeading() + help.synopsis(help.synopsisHeadingLength())); - - help.synopsisHeading = "Usage:%n"; - expected = "" + - "Usage:" + LINESEP + - "small-test-program [-!?acorv] [--version] [-h ] [-i ]" + LINESEP + - " [-p |] [-d []]..." + LINESEP; - assertEquals(expected, help.synopsisHeading() + help.synopsis(help.synopsisHeadingLength())); - } - - @Test - public void testLongMultiLineSynopsisIndented() { - @Command(name = "") - class App { - @Option(names = "--long-option-name", paramLabel = "") int a; - @Option(names = "--another-long-option-name", paramLabel = "") int b; - @Option(names = "--third-long-option-name", paramLabel = "") int c; - @Option(names = "--fourth-long-option-name", paramLabel = "") int d; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals(String.format( - " [--another-long-option-name=]%n" + - " [--fourth-long-option-name=]%n" + - " [--long-option-name=]%n" + - " [--third-long-option-name=]%n"), - help.synopsis(0)); - } - - @Test - public void testLongMultiLineSynopsisWithAtMarkIndented() { - @Command(name = "") - class App { - @Option(names = "--long-option@-name", paramLabel = "") int a; - @Option(names = "--another-long-option-name", paramLabel = "^[]") int b; - @Option(names = "--third-long-option-name", paramLabel = "") int c; - @Option(names = "--fourth-long-option-name", paramLabel = "") int d; - } - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals(String.format( - " [--another-long-option-name=^[]]%n" + - " [--fourth-long-option-name=]%n" + - " [--long-option@-name=]%n" + - " [--third-long-option-name=]%n"), - help.synopsis(0)); - } - - @Test - public void testLongMultiLineSynopsisWithAtMarkIndented_ANSI() { - @Command(name = "") - class App { - @Option(names = "--long-option@-name", paramLabel = "") int a; - @Option(names = "--another-long-option-name", paramLabel = "^[]") int b; - @Option(names = "--third-long-option-name", paramLabel = "") int c; - @Option(names = "--fourth-long-option-name", paramLabel = "") int d; - } - Help help = new Help(new App(), Help.defaultColorScheme(Help.Ansi.ON)); - assertEquals(Help.Ansi.ON.new Text(String.format( - "@|bold |@ [@|yellow --another-long-option-name|@=@|italic ^[]|@]%n" + - " [@|yellow --fourth-long-option-name|@=@|italic |@]%n" + - " [@|yellow --long-option@-name|@=@|italic |@]%n" + - " [@|yellow --third-long-option-name|@=@|italic |@]%n")), - help.synopsis(0)); - } - - @Test - public void testCustomSynopsis() { - @Command(customSynopsis = { - " --number=NUMBER --other-option=", - " --more=OTHER --and-other-option=", - " --number=NUMBER --and-other-option=", - }) - class App {@Option(names = "--ignored") boolean ignored;} - Help help = new Help(new App(), Help.Ansi.OFF); - assertEquals(String.format( - " --number=NUMBER --other-option=%n" + - " --more=OTHER --and-other-option=%n" + - " --number=NUMBER --and-other-option=%n"), - help.synopsis(0)); - } - @Test - public void testTextTable() { - TextTable table = new TextTable(Help.Ansi.OFF); - table.addRowValues(textArray(Help.Ansi.OFF, "", "-v", ",", "--verbose", "show what you're doing while you are doing it")); - table.addRowValues(textArray(Help.Ansi.OFF, "", "-p", null, null, "the quick brown fox jumped over the lazy dog. The quick brown fox jumped over the lazy dog.")); - assertEquals(String.format( - " -v, --verbose show what you're doing while you are doing it%n" + - " -p the quick brown fox jumped over the lazy dog. The%n" + - " quick brown fox jumped over the lazy dog.%n" - ,""), table.toString(new StringBuilder()).toString()); - } - - @Test(expected = IllegalArgumentException.class) - public void testTextTableAddsNewRowWhenTooManyValuesSpecified() { - TextTable table = new TextTable(Help.Ansi.OFF); - table.addRowValues(textArray(Help.Ansi.OFF, "", "-c", ",", "--create", "description", "INVALID", "Row 3")); -// assertEquals(String.format("" + -// " -c, --create description %n" + -// " INVALID %n" + -// " Row 3 %n" -// ,""), table.toString(new StringBuilder()).toString()); - } - - @Test - public void testTextTableAddsNewRowWhenAnyColumnTooLong() { - TextTable table = new TextTable(Help.Ansi.OFF); - table.addRowValues("*", "-c", ",", - "--create, --create2, --create3, --create4, --create5, --create6, --create7, --create8", - "description"); - assertEquals(String.format("" + - "* -c, --create, --create2, --create3, --create4, --create5, --create6,%n" + - " --create7, --create8%n" + - " description%n" - ,""), table.toString(new StringBuilder()).toString()); - - table = new TextTable(Help.Ansi.OFF); - table.addRowValues("", "-c", ",", - "--create, --create2, --create3, --create4, --create5, --create6, --createAA7, --create8", - "description"); - assertEquals(String.format("" + - " -c, --create, --create2, --create3, --create4, --create5, --create6,%n" + - " --createAA7, --create8%n" + - " description%n" - ,""), table.toString(new StringBuilder()).toString()); - } - - @Test - public void testCatUsageFormat() { - @Command(name = "cat", - customSynopsis = "cat [OPTIONS] [FILE...]", - description = "Concatenate FILE(s), or standard input, to standard output.", - footer = "Copyright(c) 2017") - class Cat { - @Parameters(paramLabel = "FILE", hidden = true, description = "Files whose contents to display") List files; - @Option(names = "--help", help = true, description = "display this help and exit") boolean help; - @Option(names = "--version", help = true, description = "output version information and exit") boolean version; - @Option(names = "-u", description = "(ignored)") boolean u; - @Option(names = "-t", description = "equivalent to -vT") boolean t; - @Option(names = "-e", description = "equivalent to -vET") boolean e; - @Option(names = {"-A", "--show-all"}, description = "equivalent to -vET") boolean showAll; - @Option(names = {"-s", "--squeeze-blank"}, description = "suppress repeated empty output lines") boolean squeeze; - @Option(names = {"-v", "--show-nonprinting"}, description = "use ^ and M- notation, except for LDF and TAB") boolean v; - @Option(names = {"-b", "--number-nonblank"}, description = "number nonempty output lines, overrides -n") boolean b; - @Option(names = {"-T", "--show-tabs"}, description = "display TAB characters as ^I") boolean T; - @Option(names = {"-E", "--show-ends"}, description = "display $ at end of each line") boolean E; - @Option(names = {"-n", "--number"}, description = "number all output lines") boolean n; - } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CommandLine.usage(new Cat(), new PrintStream(baos), Help.Ansi.OFF); - String expected = String.format( - "Usage: cat [OPTIONS] [FILE...]%n" + - "Concatenate FILE(s), or standard input, to standard output.%n" + - " -A, --show-all equivalent to -vET%n" + - " -b, --number-nonblank number nonempty output lines, overrides -n%n" + - " -e equivalent to -vET%n" + - " -E, --show-ends display $ at end of each line%n" + - " -n, --number number all output lines%n" + - " -s, --squeeze-blank suppress repeated empty output lines%n" + - " -t equivalent to -vT%n" + - " -T, --show-tabs display TAB characters as ^I%n" + - " -u (ignored)%n" + - " -v, --show-nonprinting use ^ and M- notation, except for LDF and TAB%n" + - " --help display this help and exit%n" + - " --version output version information and exit%n" + - "Copyright(c) 2017%n", ""); - assertEquals(expected, baos.toString()); - } - - @Test - public void testZipUsageFormat() { - String expected = String.format("" + - "Copyright (c) 1990-2008 Info-ZIP - Type 'zip \"-L\"' for software license.%n" + - "Zip 3.0 (July 5th 2008). Command:%n" + - "zip [-options] [-b path] [-t mmddyyyy] [-n suffixes] [zipfile list] [-xi list]%n" + - " The default action is to add or replace zipfile entries from list, which%n" + - " can include the special name - to compress standard input.%n" + - " If zipfile and list are omitted, zip compresses stdin to stdout.%n" + - " -f freshen: only changed files -u update: only changed or new files%n" + - " -d delete entries in zipfile -m move into zipfile (delete OS files)%n" + - " -r recurse into directories -j junk (don't record) directory names%n" + - " -0 store only -l convert LF to CR LF (-ll CR LF to LF)%n" + - " -1 compress faster -9 compress better%n" + - " -q quiet operation -v verbose operation/print version info%n" + - " -c add one-line comments -z add zipfile comment%n" + - " -@ read names from stdin -o make zipfile as old as latest entry%n" + - " -x exclude the following names -i include only the following names%n" + - " -F fix zipfile (-FF try harder) -D do not add directory entries%n" + - " -A adjust self-extracting exe -J junk zipfile prefix (unzipsfx)%n" + - " -T test zipfile integrity -X eXclude eXtra file attributes%n" + - " -y store symbolic links as the link instead of the referenced file%n" + - " -e encrypt -n don't compress these suffixes%n" + - " -h2 show more help%n"); - assertEquals(expected, CustomLayoutDemo.createZipUsageFormat(Help.Ansi.OFF)); - } - @Test - public void testNetstatUsageFormat() { - String expected = String.format("" + - "Displays protocol statistics and current TCP/IP network connections.%n" + - "%n" + - "NETSTAT [-a] [-b] [-e] [-f] [-n] [-o] [-p proto] [-q] [-r] [-s] [-t] [-x] [-y]%n" + - " [interval]%n" + - "%n" + - " -a Displays all connections and listening ports.%n" + - " -b Displays the executable involved in creating each connection or%n" + - " listening port. In some cases well-known executables host%n" + - " multiple independent components, and in these cases the%n" + - " sequence of components involved in creating the connection or%n" + - " listening port is displayed. In this case the executable name%n" + - " is in [] at the bottom, on top is the component it called, and%n" + - " so forth until TCP/IP was reached. Note that this option can be%n" + - " time-consuming and will fail unless you have sufficient%n" + - " permissions.%n" + - " -e Displays Ethernet statistics. This may be combined with the -s%n" + - " option.%n" + - " -f Displays Fully Qualified Domain Names (FQDN) for foreign%n" + - " addresses.%n" + - " -n Displays addresses and port numbers in numerical form.%n" + - " -o Displays the owning process ID associated with each connection.%n" + - " -p proto Shows connections for the protocol specified by proto; proto%n" + - " may be any of: TCP, UDP, TCPv6, or UDPv6. If used with the -s%n" + - " option to display per-protocol statistics, proto may be any of:%n" + - " IP, IPv6, ICMP, ICMPv6, TCP, TCPv6, UDP, or UDPv6.%n" + - " -q Displays all connections, listening ports, and bound%n" + - " nonlistening TCP ports. Bound nonlistening ports may or may not%n" + - " be associated with an active connection.%n" + - " -r Displays the routing table.%n" + - " -s Displays per-protocol statistics. By default, statistics are%n" + - " shown for IP, IPv6, ICMP, ICMPv6, TCP, TCPv6, UDP, and UDPv6;%n" + - " the -p option may be used to specify a subset of the default.%n" + - " -t Displays the current connection offload state.%n" + - " -x Displays NetworkDirect connections, listeners, and shared%n" + - " endpoints.%n" + - " -y Displays the TCP connection template for all connections.%n" + - " Cannot be combined with the other options.%n" + - " interval Redisplays selected statistics, pausing interval seconds%n" + - " between each display. Press CTRL+C to stop redisplaying%n" + - " statistics. If omitted, netstat will print the current%n" + - " configuration information once.%n" - , ""); - assertEquals(expected, CustomLayoutDemo.createNetstatUsageFormat(Help.Ansi.OFF)); - } - - @Test - public void testUsageIndexedPositionalParameters() throws UnsupportedEncodingException { - @Command() - class App { - @Parameters(index = "0", description = "source host") InetAddress host1; - @Parameters(index = "1", description = "source port") int port1; - @Parameters(index = "2", description = "destination host") InetAddress host2; - @Parameters(index = "3", arity = "1..2", description = "destination port range") int[] port2range; - @Parameters(index = "4..*", description = "files to transfer") String[] files; - @Parameters(hidden = true) String[] all; - } - String actual = usageString(new App(), Help.Ansi.OFF); - String expected = String.format( - "Usage:
    []%n" + - " []...%n" + - " source host%n" + - " source port%n" + - " destination host%n" + - " []%n" + - " destination port range%n" + - " []... files to transfer%n" - ); - assertEquals(expected, actual); - } - @Command(name = "base", abbreviateSynopsis = true, commandListHeading = "c o m m a n d s", - customSynopsis = "cust", description = "base description", descriptionHeading = "base descr heading", - footer = "base footer", footerHeading = "base footer heading", - header = "base header", headerHeading = "base header heading", - optionListHeading = "base option heading", parameterListHeading = "base param heading", - requiredOptionMarker = '&', separator = ";", showDefaultValues = true, - sortOptions = false, synopsisHeading = "abcd") - class Base { } - - @Test - public void testAttributesInheritedWhenSubclassingForReuse() throws UnsupportedEncodingException { - @Command - class EmptySub extends Base {} - Help help = new Help(new EmptySub()); - assertEquals("base", help.commandName); - assertEquals(String.format("cust%n"), help.synopsis(0)); - assertEquals(String.format("cust%n"), help.customSynopsis()); - assertEquals(String.format("base%n"), help.abbreviatedSynopsis()); - assertEquals(String.format("base%n"), help.detailedSynopsis(0,null, true)); - assertEquals("abcd", help.synopsisHeading); - assertEquals("", help.commandList()); - assertEquals("c o m m a n d s", help.commandListHeading); - assertEquals(String.format("base description%n"), help.description()); - assertEquals("base descr heading", help.descriptionHeading); - assertEquals(String.format("base footer%n"), help.footer()); - assertEquals("base footer heading", help.footerHeading); - assertEquals(String.format("base header%n"), help.header()); - assertEquals("base header heading", help.headerHeading); - assertEquals("", help.optionList()); - assertEquals("base option heading", help.optionListHeading); - assertEquals("", help.parameterList()); - assertEquals("base param heading", help.parameterListHeading); - - // these values NOT inherited!! - assertEquals("=", help.separator); - assertEquals(' ', help.requiredOptionMarker.charValue()); - assertFalse(help.abbreviateSynopsis); - assertFalse(help.showDefaultValues); - assertTrue(help.sortOptions); - } - - @Test - public void testSubclassAttributesOverrideEmptySuper() { - @Command - class EmptyBase {} - @Command(name = "base", abbreviateSynopsis = true, commandListHeading = "c o m m a n d s", - customSynopsis = "cust", description = "base description", descriptionHeading = "base descr heading", - footer = "base footer", footerHeading = "base footer heading", - header = "base header", headerHeading = "base header heading", - optionListHeading = "base option heading", parameterListHeading = "base param heading", - requiredOptionMarker = '&', separator = ";", showDefaultValues = true, - sortOptions = false, synopsisHeading = "abcd") - class FullBase extends EmptyBase{ } - Help help = new Help(new FullBase()); - assertEquals("base", help.commandName); - assertEquals(String.format("cust%n"), help.synopsis(0)); - assertEquals(String.format("cust%n"), help.customSynopsis()); - assertEquals(String.format("base%n"), help.abbreviatedSynopsis()); - assertEquals(String.format("base%n"), help.detailedSynopsis(0, null, true)); - assertEquals("abcd", help.synopsisHeading); - assertEquals("", help.commandList()); - assertEquals("c o m m a n d s", help.commandListHeading); - assertEquals(String.format("base description%n"), help.description()); - assertEquals("base descr heading", help.descriptionHeading); - assertEquals(String.format("base footer%n"), help.footer()); - assertEquals("base footer heading", help.footerHeading); - assertEquals(String.format("base header%n"), help.header()); - assertEquals("base header heading", help.headerHeading); - assertEquals("", help.optionList()); - assertEquals("base option heading", help.optionListHeading); - assertEquals("", help.parameterList()); - assertEquals("base param heading", help.parameterListHeading); - assertTrue(help.abbreviateSynopsis); - assertTrue(help.showDefaultValues); - assertFalse(help.sortOptions); - assertEquals(";", help.separator); - assertEquals('&', help.requiredOptionMarker.charValue()); - } - @Test - public void testSubclassAttributesOverrideSuperValues() { - @Command(name = "sub", abbreviateSynopsis = false, commandListHeading = "subc o m m a n d s", - customSynopsis = "subcust", description = "sub description", descriptionHeading = "sub descr heading", - footer = "sub footer", footerHeading = "sub footer heading", - header = "sub header", headerHeading = "sub header heading", - optionListHeading = "sub option heading", parameterListHeading = "sub param heading", - requiredOptionMarker = '%', separator = ":", showDefaultValues = false, - sortOptions = true, synopsisHeading = "xyz") - class FullSub extends Base{ } - Help help = new Help(new FullSub()); - assertEquals("sub", help.commandName); - assertEquals(String.format("subcust%n"), help.synopsis(0)); - assertEquals(String.format("subcust%n"), help.customSynopsis()); - assertEquals(String.format("sub%n"), help.abbreviatedSynopsis()); - assertEquals(String.format("sub%n"), help.detailedSynopsis(0,null, true)); - assertEquals("xyz", help.synopsisHeading); - assertEquals("", help.commandList()); - assertEquals("subc o m m a n d s", help.commandListHeading); - assertEquals(String.format("sub description%n"), help.description()); - assertEquals("sub descr heading", help.descriptionHeading); - assertEquals(String.format("sub footer%n"), help.footer()); - assertEquals("sub footer heading", help.footerHeading); - assertEquals(String.format("sub header%n"), help.header()); - assertEquals("sub header heading", help.headerHeading); - assertEquals("", help.optionList()); - assertEquals("sub option heading", help.optionListHeading); - assertEquals("", help.parameterList()); - assertEquals("sub param heading", help.parameterListHeading); - assertFalse(help.abbreviateSynopsis); - assertFalse(help.showDefaultValues); - assertTrue(help.sortOptions); - assertEquals(":", help.separator); - assertEquals('%', help.requiredOptionMarker.charValue()); - } - static class UsageDemo { - @Option(names = "-a", description = "boolean option with short name only") - boolean a; - - @Option(names = "-b", paramLabel = "INT", description = "short option with a parameter") - int b; - - @Option(names = {"-c", "--c-option"}, description = "boolean option with short and long name") - boolean c; - - @Option(names = {"-d", "--d-option"}, paramLabel = "FILE", description = "option with parameter and short and long name") - File d; - - @Option(names = "--e-option", description = "boolean option with only a long name") - boolean e; - - @Option(names = "--f-option", paramLabel = "STRING", description = "option with parameter and only a long name") - String f; - - @Option(names = {"-g", "--g-option-with-a-name-so-long-that-it-runs-into-the-descriptions-column"}, description = "boolean option with short and long name") - boolean g; - - @Parameters(index = "0", paramLabel = "0BLAH", description = "first parameter") - String param0; - - @Parameters(index = "1", paramLabel = "1PARAMETER-with-a-name-so-long-that-it-runs-into-the-descriptions-column", description = "2nd parameter") - String param1; - - @Parameters(index = "2..*", paramLabel = "remaining", description = "remaining parameters") - String param2_n; - - @Parameters(index = "*", paramLabel = "all", description = "all parameters") - String param_n; - } - - @Test - public void testSubclassedCommandHelp() throws Exception { - @Command(name = "parent", description = "parent description") - class ParentOption { - } - @Command(name = "child", description = "child description") - class ChildOption extends ParentOption { - } - String actual = usageString(new ChildOption(), Help.Ansi.OFF); - assertEquals(String.format( - "Usage: child%n" + - "child description%n"), actual); - } - - @Test - public void testSynopsisOrderCorrectWhenParametersDeclaredOutOfOrder() { - class WithParams { - @Parameters(index = "1") String param1; - @Parameters(index = "0") String param0; - } - Help help = new Help(new WithParams()); - assertEquals(format("
    %n"), help.synopsis(0)); - } - - @Test - public void testSynopsisOrderCorrectWhenSubClassAddsParameters() { - class BaseWithParams { - @Parameters(index = "1") String param1; - @Parameters(index = "0") String param0; - } - class SubWithParams extends BaseWithParams { - @Parameters(index = "3") String param3; - @Parameters(index = "2") String param2; - } - Help help = new Help(new SubWithParams()); - assertEquals(format("
    %n"), help.synopsis(0)); - } - - @Test - public void testUsageMainCommand_NoAnsi() throws Exception { - String actual = usageString(Demo.mainCommand(), Help.Ansi.OFF); - assertEquals(String.format(Demo.EXPECTED_USAGE_MAIN), actual); - } - - @Test - public void testUsageMainCommand_ANSI() throws Exception { - String actual = usageString(Demo.mainCommand(), Help.Ansi.ON); - assertEquals(Help.Ansi.ON.new Text(String.format(Demo.EXPECTED_USAGE_MAIN_ANSI)), actual); - } - - @Test - public void testUsageSubcommandGitStatus_NoAnsi() throws Exception { - String actual = usageString(new Demo.GitStatus(), Help.Ansi.OFF); - assertEquals(String.format(Demo.EXPECTED_USAGE_GITSTATUS), actual); - } - - @Test - public void testUsageSubcommandGitStatus_ANSI() throws Exception { - String actual = usageString(new Demo.GitStatus(), Help.Ansi.ON); - assertEquals(Help.Ansi.ON.new Text(String.format(Demo.EXPECTED_USAGE_GITSTATUS_ANSI)), actual); - } - - @Test - public void testUsageSubcommandGitCommit_NoAnsi() throws Exception { - String actual = usageString(new Demo.GitCommit(), Help.Ansi.OFF); - assertEquals(String.format(Demo.EXPECTED_USAGE_GITCOMMIT), actual); - } - - @Test - public void testUsageSubcommandGitCommit_ANSI() throws Exception { - String actual = usageString(new Demo.GitCommit(), Help.Ansi.ON); - assertEquals(Help.Ansi.ON.new Text(String.format(Demo.EXPECTED_USAGE_GITCOMMIT_ANSI)), actual); - } - - @Test - public void testUsageNestedSubcommand() throws IOException { - @Command(name = "main") class MainCommand { @Option(names = "-a") boolean a; @Option(names = "-h", help = true) boolean h;} - @Command(name = "cmd1") class ChildCommand1 { @Option(names = "-b") boolean b; } - @Command(name = "cmd2") class ChildCommand2 { @Option(names = "-c") boolean c; @Option(names = "-h", help = true) boolean h;} - @Command(name = "sub11") class GrandChild1Command1 { @Option(names = "-d") boolean d; } - @Command(name = "sub12") class GrandChild1Command2 { @Option(names = "-e") int e; } - @Command(name = "sub21") class GrandChild2Command1 { @Option(names = "-h", help = true) boolean h; } - @Command(name = "sub22") class GrandChild2Command2 { @Option(names = "-g") boolean g; } - @Command(name = "sub22sub1") class GreatGrandChild2Command2_1 { - @Option(names = "-h", help = true) boolean h; - @Option(names = {"-t", "--type"}) String customType; - } - CommandLine commandLine = new CommandLine(new MainCommand()); - commandLine - .addSubcommand("cmd1", new CommandLine(new ChildCommand1()) - .addSubcommand("sub11", new GrandChild1Command1()) - .addSubcommand("sub12", new GrandChild1Command2()) - ) - .addSubcommand("cmd2", new CommandLine(new ChildCommand2()) - .addSubcommand("sub21", new GrandChild2Command1()) - .addSubcommand("sub22", new CommandLine(new GrandChild2Command2()) - .addSubcommand("sub22sub1", new GreatGrandChild2Command2_1()) - ) - ); - String main = usageString(commandLine, Help.Ansi.OFF); - assertEquals(String.format("" + - "Usage: main [-ah]%n" + - " -a%n" + - " -h%n" + - "Commands:%n" + - " cmd1%n" + - " cmd2%n"), main); - - String cmd2 = usageString(commandLine.getSubcommands().get("cmd2"), Help.Ansi.OFF); - assertEquals(String.format("" + - "Usage: cmd2 [-ch]%n" + - " -c%n" + - " -h%n" + - "Commands:%n" + - " sub21%n" + - " sub22%n"), cmd2); - - String sub22 = usageString(commandLine.getSubcommands().get("cmd2").getSubcommands().get("sub22"), Help.Ansi.OFF); - assertEquals(String.format("" + - "Usage: sub22 [-g]%n" + - " -g%n" + - "Commands:%n" + - " sub22sub1%n"), sub22); - } - - @Test - public void testTextConstructorPlain() { - assertEquals("--NoAnsiFormat", Help.Ansi.ON.new Text("--NoAnsiFormat").toString()); - } - - @Test - public void testTextConstructorWithStyle() { - assertEquals("\u001B[1m--NoAnsiFormat\u001B[21m\u001B[0m", Help.Ansi.ON.new Text("@|bold --NoAnsiFormat|@").toString()); - } - - @Ignore("Until nested styles are supported") - @Test - public void testTextConstructorWithNestedStyle() { - assertEquals("\u001B[1mfirst \u001B[2msecond\u001B[22m\u001B[21m", Help.Ansi.ON.new Text("@|bold first @|underline second|@|@").toString()); - assertEquals("\u001B[1mfirst \u001B[4msecond\u001B[24m third\u001B[21m", Help.Ansi.ON.new Text("@|bold first @|underline second|@ third|@").toString()); - } - - @Test - public void testTextApply() { - Text txt = Help.Ansi.ON.apply("--p", Arrays.asList(Style.fg_red, Style.bold)); - assertEquals(Help.Ansi.ON.new Text("@|fg(red),bold --p|@"), txt); - } - - @Test - public void testTextDefaultColorScheme() { - Help.Ansi ansi = Help.Ansi.ON; - ColorScheme scheme = Help.defaultColorScheme(ansi); - assertEquals(scheme.ansi().new Text("@|yellow -p|@"), scheme.optionText("-p")); - assertEquals(scheme.ansi().new Text("@|bold command|@"), scheme.commandText("command")); - assertEquals(scheme.ansi().new Text("@|yellow FILE|@"), scheme.parameterText("FILE")); - assertEquals(scheme.ansi().new Text("@|italic NUMBER|@"), scheme.optionParamText("NUMBER")); - } - - @Test - public void testTextSubString() { - Help.Ansi ansi = Help.Ansi.ON; - Text txt = ansi.new Text("@|bold 01234|@").append("56").append("@|underline 7890|@"); - assertEquals(ansi.new Text("@|bold 01234|@56@|underline 7890|@"), txt.substring(0)); - assertEquals(ansi.new Text("@|bold 1234|@56@|underline 7890|@"), txt.substring(1)); - assertEquals(ansi.new Text("@|bold 234|@56@|underline 7890|@"), txt.substring(2)); - assertEquals(ansi.new Text("@|bold 34|@56@|underline 7890|@"), txt.substring(3)); - assertEquals(ansi.new Text("@|bold 4|@56@|underline 7890|@"), txt.substring(4)); - assertEquals(ansi.new Text("56@|underline 7890|@"), txt.substring(5)); - assertEquals(ansi.new Text("6@|underline 7890|@"), txt.substring(6)); - assertEquals(ansi.new Text("@|underline 7890|@"), txt.substring(7)); - assertEquals(ansi.new Text("@|underline 890|@"), txt.substring(8)); - assertEquals(ansi.new Text("@|underline 90|@"), txt.substring(9)); - assertEquals(ansi.new Text("@|underline 0|@"), txt.substring(10)); - assertEquals(ansi.new Text(""), txt.substring(11)); - assertEquals(ansi.new Text("@|bold 01234|@56@|underline 7890|@"), txt.substring(0, 11)); - assertEquals(ansi.new Text("@|bold 01234|@56@|underline 789|@"), txt.substring(0, 10)); - assertEquals(ansi.new Text("@|bold 01234|@56@|underline 78|@"), txt.substring(0, 9)); - assertEquals(ansi.new Text("@|bold 01234|@56@|underline 7|@"), txt.substring(0, 8)); - assertEquals(ansi.new Text("@|bold 01234|@56"), txt.substring(0, 7)); - assertEquals(ansi.new Text("@|bold 01234|@5"), txt.substring(0, 6)); - assertEquals(ansi.new Text("@|bold 01234|@"), txt.substring(0, 5)); - assertEquals(ansi.new Text("@|bold 0123|@"), txt.substring(0, 4)); - assertEquals(ansi.new Text("@|bold 012|@"), txt.substring(0, 3)); - assertEquals(ansi.new Text("@|bold 01|@"), txt.substring(0, 2)); - assertEquals(ansi.new Text("@|bold 0|@"), txt.substring(0, 1)); - assertEquals(ansi.new Text(""), txt.substring(0, 0)); - assertEquals(ansi.new Text("@|bold 1234|@56@|underline 789|@"), txt.substring(1, 10)); - assertEquals(ansi.new Text("@|bold 234|@56@|underline 78|@"), txt.substring(2, 9)); - assertEquals(ansi.new Text("@|bold 34|@56@|underline 7|@"), txt.substring(3, 8)); - assertEquals(ansi.new Text("@|bold 4|@56"), txt.substring(4, 7)); - assertEquals(ansi.new Text("5"), txt.substring(5, 6)); - assertEquals(ansi.new Text("@|bold 2|@"), txt.substring(2, 3)); - assertEquals(ansi.new Text("@|underline 8|@"), txt.substring(8, 9)); - - Text txt2 = ansi.new Text("@|bold abc|@@|underline DEF|@"); - assertEquals(ansi.new Text("@|bold abc|@@|underline DEF|@"), txt2.substring(0)); - assertEquals(ansi.new Text("@|bold bc|@@|underline DEF|@"), txt2.substring(1)); - assertEquals(ansi.new Text("@|bold abc|@@|underline DE|@"), txt2.substring(0,5)); - assertEquals(ansi.new Text("@|bold bc|@@|underline DE|@"), txt2.substring(1,5)); - } - @Test - public void testTextSplitLines() { - Help.Ansi ansi = Help.Ansi.ON; - Text[] all = { - ansi.new Text("@|bold 012\n34|@").append("5\nAA\n6").append("@|underline 78\n90|@"), - ansi.new Text("@|bold 012\r34|@").append("5\rAA\r6").append("@|underline 78\r90|@"), - ansi.new Text("@|bold 012\r\n34|@").append("5\r\nAA\r\n6").append("@|underline 78\r\n90|@"), - }; - for (Text text : all) { - Text[] lines = text.splitLines(); - int i = 0; - assertEquals(ansi.new Text("@|bold 012|@"), lines[i++]); - assertEquals(ansi.new Text("@|bold 34|@5"), lines[i++]); - assertEquals(ansi.new Text("AA"), lines[i++]); - assertEquals(ansi.new Text("6@|underline 78|@"), lines[i++]); - assertEquals(ansi.new Text("@|underline 90|@"), lines[i++]); - } - } - @Test - public void testTextSplitLinesStartEnd() { - Help.Ansi ansi = Help.Ansi.ON; - Text[] all = { - ansi.new Text("\n@|bold 012\n34|@").append("5\nAA\n6").append("@|underline 78\n90|@\n"), - ansi.new Text("\r@|bold 012\r34|@").append("5\rAA\r6").append("@|underline 78\r90|@\r"), - ansi.new Text("\r\n@|bold 012\r\n34|@").append("5\r\nAA\r\n6").append("@|underline 78\r\n90|@\r\n"), - }; - for (Text text : all) { - Text[] lines = text.splitLines(); - int i = 0; - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text("@|bold 012|@"), lines[i++]); - assertEquals(ansi.new Text("@|bold 34|@5"), lines[i++]); - assertEquals(ansi.new Text("AA"), lines[i++]); - assertEquals(ansi.new Text("6@|underline 78|@"), lines[i++]); - assertEquals(ansi.new Text("@|underline 90|@"), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - } - } - @Test - public void testTextSplitLinesStartEndIntermediate() { - Help.Ansi ansi = Help.Ansi.ON; - Text[] all = { - ansi.new Text("\n@|bold 012\n\n\n34|@").append("5\n\n\nAA\n\n\n6").append("@|underline 78\n90|@\n"), - ansi.new Text("\r@|bold 012\r\r\r34|@").append("5\r\r\rAA\r\r\r6").append("@|underline 78\r90|@\r"), - ansi.new Text("\r\n@|bold 012\r\n\r\n\r\n34|@").append("5\r\n\r\n\r\nAA\r\n\r\n\r\n6").append("@|underline 78\r\n90|@\r\n"), - }; - for (Text text : all) { - Text[] lines = text.splitLines(); - int i = 0; - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text("@|bold 012|@"), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text("@|bold 34|@5"), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text("AA"), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - assertEquals(ansi.new Text("6@|underline 78|@"), lines[i++]); - assertEquals(ansi.new Text("@|underline 90|@"), lines[i++]); - assertEquals(ansi.new Text(""), lines[i++]); - } - } - @Test - public void testEmbeddedNewLinesInUsageSections() throws UnsupportedEncodingException { - @Command(description = "first line\nsecond line\nthird line", headerHeading = "headerHeading1\nheaderHeading2", - header = "header1\nheader2", descriptionHeading = "descriptionHeading1\ndescriptionHeading2", - footerHeading = "footerHeading1\nfooterHeading2", footer = "footer1\nfooter2") - class App { - @Option(names = {"-v", "--verbose"}, description = "optionDescription1\noptionDescription2") boolean v; - @Parameters(description = "paramDescription1\nparamDescription2") String file; - } - String actual = usageString(new App(), Help.Ansi.OFF); - String expected = String.format("" + - "headerHeading1%n" + - "headerHeading2header1%n" + - "header2%n" + - "Usage:
    [-v] %n" + - "descriptionHeading1%n" + - "descriptionHeading2first line%n" + - "second line%n" + - "third line%n" + - " paramDescription1%n" + - " paramDescription2%n" + - " -v, --verbose optionDescription1%n" + - " optionDescription2%n" + - "footerHeading1%n" + - "footerHeading2footer1%n" + - "footer2%n"); - assertEquals(expected, actual); - } - @Test - public void testTextWithMultipleStyledSections() { - assertEquals("\u001B[1m
    \u001B[21m\u001B[0m [\u001B[33m-v\u001B[39m\u001B[0m] [\u001B[33m-c\u001B[39m\u001B[0m [\u001B[3m\u001B[23m\u001B[0m]]", - Help.Ansi.ON.new Text("@|bold
    |@ [@|yellow -v|@] [@|yellow -c|@ [@|italic |@]]").toString()); - } - - @Test - public void testTextAdjacentStyles() { - assertEquals("\u001B[3m\u001B[23m\u001B[0m%n", - Help.Ansi.ON.new Text("@|italic |@%n").toString()); - } - - @Test - public void testTextNoConversionWithoutClosingTag() { - assertEquals("\u001B[3mabc\u001B[23m\u001B[0m", Help.Ansi.ON.new Text("@|italic abc|@").toString()); - assertEquals("@|italic abc", Help.Ansi.ON.new Text("@|italic abc").toString()); - } - - @Test - public void testTextNoConversionWithoutSpaceSeparator() { - assertEquals("\u001B[3ma\u001B[23m\u001B[0m", Help.Ansi.ON.new Text("@|italic a|@").toString()); - assertEquals("@|italic|@", Help.Ansi.ON.new Text("@|italic|@").toString()); - assertEquals("", Help.Ansi.ON.new Text("@|italic |@").toString()); - } - - @Test - public void testPalette236ColorForegroundIndex() { - assertEquals("\u001B[38;5;45mabc\u001B[39m\u001B[0m", Help.Ansi.ON.new Text("@|fg(45) abc|@").toString()); - } - - @Test - public void testPalette236ColorForegroundRgb() { - int num = 16 + 36 * 5 + 6 * 5 + 5; - assertEquals("\u001B[38;5;" + num + "mabc\u001B[39m\u001B[0m", Help.Ansi.ON.new Text("@|fg(5;5;5) abc|@").toString()); - } - - @Test - public void testPalette236ColorBackgroundIndex() { - assertEquals("\u001B[48;5;77mabc\u001B[49m\u001B[0m", Help.Ansi.ON.new Text("@|bg(77) abc|@").toString()); - } - - @Test - public void testPalette236ColorBackgroundRgb() { - int num = 16 + 36 * 3 + 6 * 3 + 3; - assertEquals("\u001B[48;5;" + num + "mabc\u001B[49m\u001B[0m", Help.Ansi.ON.new Text("@|bg(3;3;3) abc|@").toString()); - } - - @Test - public void testAnsiEnabled() { - assertTrue(Help.Ansi.ON.enabled()); - assertFalse(Help.Ansi.OFF.enabled()); - - System.setProperty("picocli.ansi", "true"); - assertEquals(true, Help.Ansi.AUTO.enabled()); - - System.setProperty("picocli.ansi", "false"); - assertEquals(false, Help.Ansi.AUTO.enabled()); - - System.clearProperty("picocli.ansi"); - boolean isWindows = System.getProperty("os.name").startsWith("Windows"); - boolean isXterm = System.getenv("TERM") != null && System.getenv("TERM").startsWith("xterm"); - boolean isAtty = (isWindows && isXterm) // cygwin pseudo-tty - || hasConsole(); - assertEquals(isAtty && (!isWindows || isXterm), Help.Ansi.AUTO.enabled()); - } - - private boolean hasConsole() { - try { return System.class.getDeclaredMethod("console").invoke(null) != null; } - catch (Throwable reflectionFailed) { return true; } - } - - @Test - public void testSystemPropertiesOverrideDefaultColorScheme() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters(paramLabel = "FILE", arity = "1..*") File[] files; - } - Help.Ansi ansi = Help.Ansi.ON; - // default color scheme - assertEquals(ansi.new Text("@|bold
    |@ [@|yellow -v|@] [@|yellow -c|@=@|italic |@] @|yellow FILE|@..." + LINESEP), - new Help(new App(), ansi).synopsis(0)); - - System.setProperty("picocli.color.commands", "blue"); - System.setProperty("picocli.color.options", "green"); - System.setProperty("picocli.color.parameters", "cyan"); - System.setProperty("picocli.color.optionParams", "magenta"); - assertEquals(ansi.new Text("@|blue
    |@ [@|green -v|@] [@|green -c|@=@|magenta |@] @|cyan FILE|@..." + LINESEP), - new Help(new App(), ansi).synopsis(0)); - } - - @Test - public void testSystemPropertiesOverrideExplicitColorScheme() { - @CommandLine.Command(separator = "=") class App { - @Option(names = {"--verbose", "-v"}) boolean verbose; - @Option(names = {"--count", "-c"}) int count; - @Option(names = {"--help", "-h"}, hidden = true) boolean helpRequested; - @Parameters(paramLabel = "FILE", arity = "1..*") File[] files; - } - Help.Ansi ansi = Help.Ansi.ON; - ColorScheme explicit = new ColorScheme(ansi) - .commands(Style.faint, Style.bg_magenta) - .options(Style.bg_red) - .parameters(Style.reverse) - .optionParams(Style.bg_green); - // default color scheme - assertEquals(ansi.new Text("@|faint,bg(magenta)
    |@ [@|bg(red) -v|@] [@|bg(red) -c|@=@|bg(green) |@] @|reverse FILE|@..." + LINESEP), - new Help(new App(), explicit).synopsis(0)); - - System.setProperty("picocli.color.commands", "blue"); - System.setProperty("picocli.color.options", "blink"); - System.setProperty("picocli.color.parameters", "red"); - System.setProperty("picocli.color.optionParams", "magenta"); - assertEquals(ansi.new Text("@|blue
    |@ [@|blink -v|@] [@|blink -c|@=@|magenta |@] @|red FILE|@..." + LINESEP), - new Help(new App(), explicit).synopsis(0)); - } - - @Test - public void testCommandLine_printVersionInfo_printsSinglePlainTextString() throws Exception { - @Command(version = "1.0") class Versioned {} - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CommandLine(new Versioned()).printVersionHelp(new PrintStream(baos, true, "UTF8"), Help.Ansi.OFF); - String result = baos.toString("UTF8"); - assertEquals(String.format("1.0%n"), result); - } - - @Test - public void testCommandLine_printVersionInfo_printsArrayOfPlainTextStrings() throws Exception { - @Command(version = {"Versioned Command 1.0", "512-bit superdeluxe", "(c) 2017"}) class Versioned {} - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CommandLine(new Versioned()).printVersionHelp(new PrintStream(baos, true, "UTF8"), Help.Ansi.OFF); - String result = baos.toString("UTF8"); - assertEquals(String.format("Versioned Command 1.0%n512-bit superdeluxe%n(c) 2017%n"), result); - } - - @Test - public void testCommandLine_printVersionInfo_printsSingleStringWithMarkup() throws Exception { - @Command(version = "@|red 1.0|@") class Versioned {} - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CommandLine(new Versioned()).printVersionHelp(new PrintStream(baos, true, "UTF8"), Help.Ansi.ON); - String result = baos.toString("UTF8"); - assertEquals(String.format("\u001B[31m1.0\u001B[39m\u001B[0m%n"), result); - } - - @Test - public void testCommandLine_printVersionInfo_printsArrayOfStringsWithMarkup() throws Exception { - @Command(version = { - "@|yellow Versioned Command 1.0|@", - "@|blue Build 12345|@", - "@|red,bg(white) (c) 2017|@" }) - class Versioned {} - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new CommandLine(new Versioned()).printVersionHelp(new PrintStream(baos, true, "UTF8"), Help.Ansi.ON); - String result = baos.toString("UTF8"); - assertEquals(String.format("" + - "\u001B[33mVersioned Command 1.0\u001B[39m\u001B[0m%n" + - "\u001B[34mBuild 12345\u001B[39m\u001B[0m%n" + - "\u001B[31m\u001B[47m(c) 2017\u001B[49m\u001B[39m\u001B[0m%n"), result); - } - @Test - public void testCommandLine_printVersionInfo_formatsArguments() throws Exception { - @Command(version = {"First line %1$s", "Second line %2$s", "Third line %s %s"}) class Versioned {} - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(baos, true, "UTF8"); - new CommandLine(new Versioned()).printVersionHelp(ps, Help.Ansi.OFF, "VALUE1", "VALUE2", "VALUE3"); - String result = baos.toString("UTF8"); - assertEquals(String.format("First line VALUE1%nSecond line VALUE2%nThird line VALUE1 VALUE2%n"), result); - } - - @Test - public void testCommandLine_printVersionInfo_withMarkupAndParameterContainingMarkup() throws Exception { - @Command(version = { - "@|yellow Versioned Command 1.0|@", - "@|blue Build 12345|@%1$s", - "@|red,bg(white) (c) 2017|@%2$s" }) - class Versioned {} - String[] args = {"@|bold VALUE1|@", "@|underline VALUE2|@", "VALUE3"}; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(baos, true, "UTF8"); - new CommandLine(new Versioned()).printVersionHelp(ps, Help.Ansi.ON, (Object[]) args); - String result = baos.toString("UTF8"); - assertEquals(String.format("" + - "\u001B[33mVersioned Command 1.0\u001B[39m\u001B[0m%n" + - "\u001B[34mBuild 12345\u001B[39m\u001B[0m\u001B[1mVALUE1\u001B[21m\u001B[0m%n" + - "\u001B[31m\u001B[47m(c) 2017\u001B[49m\u001B[39m\u001B[0m\u001B[4mVALUE2\u001B[24m\u001B[0m%n"), result); - } - - @Test - public void testMapFieldHelp() throws Exception { - class App { - @Parameters(arity = "2", split = "\\|", - paramLabel = "FIXTAG=VALUE", - description = "Exactly two lists of vertical bar '|'-separated FIXTAG=VALUE pairs.") - Map message; - - @Option(names = {"-P", "-map"}, split = ",", - paramLabel = "TIMEUNIT=VALUE", - description = "Any number of TIMEUNIT=VALUE pairs. These may be specified separately (-PTIMEUNIT=VALUE) or as a comma-separated list.") - Map map; - } - String actual = usageString(new App(), Help.Ansi.OFF); - String expected = String.format("" + - "Usage:
    [-P=TIMEUNIT=VALUE[,TIMEUNIT=VALUE]...]... FIXTAG=VALUE%n" + - " [\\|FIXTAG=VALUE]... FIXTAG=VALUE[\\|FIXTAG=VALUE]...%n" + - " FIXTAG=VALUE[\\|FIXTAG=VALUE]... FIXTAG=VALUE[\\|FIXTAG=VALUE]...%n" + - " Exactly two lists of vertical bar '|'-separated%n" + - " FIXTAG=VALUE pairs.%n" + - " -P, -map=TIMEUNIT=VALUE[,TIMEUNIT=VALUE]...%n" + - " Any number of TIMEUNIT=VALUE pairs. These may be%n" + - " specified separately (-PTIMEUNIT=VALUE) or as a%n" + - " comma-separated list.%n"); - assertEquals(expected, actual); - } - @Test - public void testMapFieldTypeInference() throws UnsupportedEncodingException { - class App { - @Option(names = "-a") Map a; - @Option(names = "-b") Map b; - @SuppressWarnings("unchecked") - @Option(names = "-c") Map c; - @Option(names = "-d") List d; - @Option(names = "-e") Map e; - @Option(names = "-f", type = {Long.class, Float.class}) Map f; - @SuppressWarnings("unchecked") - @Option(names = "-g", type = {TimeUnit.class, Float.class}) Map g; - } - String actual = usageString(new App(), Help.Ansi.OFF); - String expected = String.format("" + - "Usage:
    [-a=]... [-b=]...%n" + - " [-c=]... [-d=]... [-e=]...%n" + - " [-f=]... [-g=]...%n" + - " -a= %n" + - " -b= %n" + - "%n" + - " -c= %n" + - " -d= %n" + - " -e= %n" + - " -f= %n" + - " -g= %n"); - assertEquals(expected, actual); - } - @Test - public void test200NPEWithEmptyCommandName() throws UnsupportedEncodingException { - @Command(name = "") class Args {} - String actual = usageString(new Args(), Help.Ansi.OFF); - String expected = String.format("" + - "Usage: %n" + - ""); - assertEquals(expected, actual); - } -} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineTest.java deleted file mode 100644 index 118a59f442c..00000000000 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/tools/picocli/CommandLineTest.java +++ /dev/null @@ -1,3188 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache license, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the license for the specific language governing permissions and - * limitations under the license. - */ -package org.apache.logging.log4j.core.tools.picocli; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.net.InetAddress; -import java.net.MalformedURLException; -import java.net.Socket; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.UnknownHostException; -import java.nio.charset.Charset; -import java.sql.Time; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.UUID; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -import static java.util.concurrent.TimeUnit.*; -import static org.junit.Assert.*; -import static org.apache.logging.log4j.core.tools.picocli.CommandLine.*; - -/** - * Tests for the CommandLine argument parsing interpreter functionality. - */ -// DONE arrays -// DONE collection fields -// DONE all built-in types -// DONE private fields, public fields (TODO methods?) -// DONE arity 2, 3 -// DONE arity -1, -2, -3 -// TODO arity ignored for single-value types (non-array, non-collection) -// DONE positional arguments with '--' separator (where positional arguments look like options) -// DONE positional arguments without '--' separator (based on arity of last option?) -// DONE positional arguments based on arity of last option -// TODO ambiguous options: writing --input ARG (as opposed to --input=ARG) is ambiguous, -// meaning it is not possible to tell whether ARG is option's argument or a positional argument. -// In usage patterns this will be interpreted as an option with argument only if a description (covered below) -// for that option is provided. -// Otherwise it will be interpreted as an option and separate positional argument. -// TODO ambiguous short options: ambiguity with the -f FILE and -fFILE notation. -// In the latter case it is not possible to tell whether it is a number of stacked short options, -// or an option with an argument. These notations will be interpreted as an option with argument only if a -// description for the option is provided. -// DONE compact flags -// DONE compact flags where last option has an argument, separate by space -// DONE compact flags where last option has an argument attached to the option character -// DONE long options with argument separate by space -// DONE long options with argument separated by '=' (no spaces) -// TODO document that if arity>1 and args="-opt=val1 val2", arity overrules the "=": both values are assigned -// TODO test superclass bean and child class bean where child class field shadows super class and have same annotation Option name -// TODO test superclass bean and child class bean where child class field shadows super class and have different annotation Option name -// DONE -vrx, -vro outputFile, -vrooutputFile, -vro=outputFile, -vro:outputFile, -vro=, -vro:, -vro -// DONE --out outputFile, --out=outputFile, --out:outputFile, --out=, --out:, --out -public class CommandLineTest { - @Before public void setUp() { System.clearProperty("picocli.trace"); } - @After public void tearDown() { System.clearProperty("picocli.trace"); } - - private static void setTraceLevel(String level) { - System.setProperty("picocli.trace", level); - } - @Test - public void testVersion() { - assertEquals("2.0.3", CommandLine.VERSION); - } - - static class SupportedTypes { - @Option(names = "-boolean") boolean booleanField; - @Option(names = "-Boolean") Boolean aBooleanField; - @Option(names = "-byte") byte byteField; - @Option(names = "-Byte") Byte aByteField; - @Option(names = "-char") char charField; - @Option(names = "-Character") Character aCharacterField; - @Option(names = "-short") short shortField; - @Option(names = "-Short") Short aShortField; - @Option(names = "-int") int intField; - @Option(names = "-Integer") Integer anIntegerField; - @Option(names = "-long") long longField; - @Option(names = "-Long") Long aLongField; - @Option(names = "-float") float floatField; - @Option(names = "-Float") Float aFloatField; - @Option(names = "-double") double doubleField; - @Option(names = "-Double") Double aDoubleField; - @Option(names = "-String") String aStringField; - @Option(names = "-StringBuilder") StringBuilder aStringBuilderField; - @Option(names = "-CharSequence") CharSequence aCharSequenceField; - @Option(names = "-File") File aFileField; - @Option(names = "-URL") URL anURLField; - @Option(names = "-URI") URI anURIField; - @Option(names = "-Date") Date aDateField; - @Option(names = "-Time") Time aTimeField; - @Option(names = "-BigDecimal") BigDecimal aBigDecimalField; - @Option(names = "-BigInteger") BigInteger aBigIntegerField; - @Option(names = "-Charset") Charset aCharsetField; - @Option(names = "-InetAddress") InetAddress anInetAddressField; - @Option(names = "-Pattern") Pattern aPatternField; - @Option(names = "-UUID") UUID anUUIDField; - } - @Test - public void testDefaults() { - SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes()); - assertEquals("boolean", false, bean.booleanField); - assertEquals("Boolean", null, bean.aBooleanField); - assertEquals("byte", 0, bean.byteField); - assertEquals("Byte", null, bean.aByteField); - assertEquals("char", 0, bean.charField); - assertEquals("Character", null, bean.aCharacterField); - assertEquals("short", 0, bean.shortField); - assertEquals("Short", null, bean.aShortField); - assertEquals("int", 0, bean.intField); - assertEquals("Integer", null, bean.anIntegerField); - assertEquals("long", 0, bean.longField); - assertEquals("Long", null, bean.aLongField); - assertEquals("float", 0f, bean.floatField, Float.MIN_VALUE); - assertEquals("Float", null, bean.aFloatField); - assertEquals("double", 0d, bean.doubleField, Double.MIN_VALUE); - assertEquals("Double", null, bean.aDoubleField); - assertEquals("String", null, bean.aStringField); - assertEquals("StringBuilder", null, bean.aStringBuilderField); - assertEquals("CharSequence", null, bean.aCharSequenceField); - assertEquals("File", null, bean.aFileField); - assertEquals("URL", null, bean.anURLField); - assertEquals("URI", null, bean.anURIField); - assertEquals("Date", null, bean.aDateField); - assertEquals("Time", null, bean.aTimeField); - assertEquals("BigDecimal", null, bean.aBigDecimalField); - assertEquals("BigInteger", null, bean.aBigIntegerField); - assertEquals("Charset", null, bean.aCharsetField); - assertEquals("InetAddress", null, bean.anInetAddressField); - assertEquals("Pattern", null, bean.aPatternField); - assertEquals("UUID", null, bean.anUUIDField); - } - @Test - public void testTypeConversionSucceedsForValidInput() - throws MalformedURLException, URISyntaxException, UnknownHostException, ParseException { - SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), - "-boolean", "-Boolean", // - "-byte", "12", "-Byte", "23", // - "-char", "p", "-Character", "i", // - "-short", "34", "-Short", "45", // - "-int", "56", "-Integer", "67", // - "-long", "78", "-Long", "89", // - "-float", "1.23", "-Float", "2.34", // - "-double", "3.45", "-Double", "4.56", // - "-String", "abc", "-StringBuilder", "bcd", "-CharSequence", "xyz", // - "-File", "abc.txt", // - "-URL", "http://pico-cli.github.io", // - "-URI", "http://pico-cli.github.io/index.html", // - "-Date", "2017-01-30", // - "-Time", "23:59:59", // - "-BigDecimal", "12345678901234567890.123", // - "-BigInteger", "123456789012345678901", // - "-Charset", "UTF8", // - "-InetAddress", InetAddress.getLocalHost().getHostName(), // - "-Pattern", "a*b", // - "-UUID", "c7d51423-bf9d-45dd-a30d-5b16fafe42e2" - ); - assertEquals("boolean", true, bean.booleanField); - assertEquals("Boolean", Boolean.TRUE, bean.aBooleanField); - assertEquals("byte", 12, bean.byteField); - assertEquals("Byte", Byte.valueOf((byte) 23), bean.aByteField); - assertEquals("char", 'p', bean.charField); - assertEquals("Character", Character.valueOf('i'), bean.aCharacterField); - assertEquals("short", 34, bean.shortField); - assertEquals("Short", Short.valueOf((short) 45), bean.aShortField); - assertEquals("int", 56, bean.intField); - assertEquals("Integer", Integer.valueOf(67), bean.anIntegerField); - assertEquals("long", 78L, bean.longField); - assertEquals("Long", Long.valueOf(89L), bean.aLongField); - assertEquals("float", 1.23f, bean.floatField, Float.MIN_VALUE); - assertEquals("Float", Float.valueOf(2.34f), bean.aFloatField); - assertEquals("double", 3.45, bean.doubleField, Double.MIN_VALUE); - assertEquals("Double", Double.valueOf(4.56), bean.aDoubleField); - assertEquals("String", "abc", bean.aStringField); - assertEquals("StringBuilder type", StringBuilder.class, bean.aStringBuilderField.getClass()); - assertEquals("StringBuilder", "bcd", bean.aStringBuilderField.toString()); - assertEquals("CharSequence", "xyz", bean.aCharSequenceField); - assertEquals("File", new File("abc.txt"), bean.aFileField); - assertEquals("URL", new URL("http://pico-cli.github.io"), bean.anURLField); - assertEquals("URI", new URI("http://pico-cli.github.io/index.html"), bean.anURIField); - assertEquals("Date", new SimpleDateFormat("yyyy-MM-dd").parse("2017-01-30"), bean.aDateField); - assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss").parse("23:59:59").getTime()), bean.aTimeField); - assertEquals("BigDecimal", new BigDecimal("12345678901234567890.123"), bean.aBigDecimalField); - assertEquals("BigInteger", new BigInteger("123456789012345678901"), bean.aBigIntegerField); - assertEquals("Charset", Charset.forName("UTF8"), bean.aCharsetField); - assertEquals("InetAddress", InetAddress.getByName(InetAddress.getLocalHost().getHostName()), bean.anInetAddressField); - assertEquals("Pattern", Pattern.compile("a*b").pattern(), bean.aPatternField.pattern()); - assertEquals("UUID", UUID.fromString("c7d51423-bf9d-45dd-a30d-5b16fafe42e2"), bean.anUUIDField); - } - @Test - public void testByteFieldsAreDecimal() { - try { - CommandLine.populateCommand(new SupportedTypes(), "-byte", "0x1F", "-Byte", "0x0F"); - fail("Should fail on hex input"); - } catch (ParameterException expected) { - assertEquals("Could not convert '0x1F' to byte for option '-byte'" + - ": java.lang.NumberFormatException: For input string: \"0x1F\"", expected.getMessage()); - } - } - @Test - public void testCustomByteConverterAcceptsHexadecimalDecimalAndOctal() { - SupportedTypes bean = new SupportedTypes(); - CommandLine commandLine = new CommandLine(bean); - ITypeConverter converter = new ITypeConverter() { - @Override - public Byte convert(String s) { - return Byte.decode(s); - } - }; - commandLine.registerConverter(Byte.class, converter); - commandLine.registerConverter(Byte.TYPE, converter); - commandLine.parse("-byte", "0x1F", "-Byte", "0x0F"); - assertEquals(0x1F, bean.byteField); - assertEquals(Byte.valueOf((byte) 0x0F), bean.aByteField); - - commandLine.parse("-byte", "010", "-Byte", "010"); - assertEquals(8, bean.byteField); - assertEquals(Byte.valueOf((byte) 8), bean.aByteField); - - commandLine.parse("-byte", "34", "-Byte", "34"); - assertEquals(34, bean.byteField); - assertEquals(Byte.valueOf((byte) 34), bean.aByteField); - } - @Test - public void testShortFieldsAreDecimal() { - try { - CommandLine.populateCommand(new SupportedTypes(), "-short", "0xFF", "-Short", "0x6FFE"); - fail("Should fail on hex input"); - } catch (ParameterException expected) { - assertEquals("Could not convert '0xFF' to short for option '-short'" + - ": java.lang.NumberFormatException: For input string: \"0xFF\"", expected.getMessage()); - } - } - @Test - public void testCustomShortConverterAcceptsHexadecimalDecimalAndOctal() { - SupportedTypes bean = new SupportedTypes(); - CommandLine commandLine = new CommandLine(bean); - ITypeConverter shortConverter = new ITypeConverter() { - @Override - public Short convert(String s) { - return Short.decode(s); - } - }; - commandLine.registerConverter(Short.class, shortConverter); - commandLine.registerConverter(Short.TYPE, shortConverter); - commandLine.parse("-short", "0xFF", "-Short", "0x6FFE"); - assertEquals(0xFF, bean.shortField); - assertEquals(Short.valueOf((short) 0x6FFE), bean.aShortField); - - commandLine.parse("-short", "010", "-Short", "010"); - assertEquals(8, bean.shortField); - assertEquals(Short.valueOf((short) 8), bean.aShortField); - - commandLine.parse("-short", "34", "-Short", "34"); - assertEquals(34, bean.shortField); - assertEquals(Short.valueOf((short) 34), bean.aShortField); - } - @Test - public void testIntFieldsAreDecimal() { - try { - CommandLine.populateCommand(new SupportedTypes(), "-int", "0xFF", "-Integer", "0xFFFF"); - fail("Should fail on hex input"); - } catch (ParameterException expected) { - assertEquals("Could not convert '0xFF' to int for option '-int'" + - ": java.lang.NumberFormatException: For input string: \"0xFF\"", expected.getMessage()); - } - } - @Test - public void testCustomIntConverterAcceptsHexadecimalDecimalAndOctal() { - SupportedTypes bean = new SupportedTypes(); - CommandLine commandLine = new CommandLine(bean); - ITypeConverter intConverter = new ITypeConverter() { - @Override - public Integer convert(String s) { - return Integer.decode(s); - } - }; - commandLine.registerConverter(Integer.class, intConverter); - commandLine.registerConverter(Integer.TYPE, intConverter); - commandLine.parse("-int", "0xFF", "-Integer", "0xFFFF"); - assertEquals(255, bean.intField); - assertEquals(Integer.valueOf(0xFFFF), bean.anIntegerField); - - commandLine.parse("-int", "010", "-Integer", "010"); - assertEquals(8, bean.intField); - assertEquals(Integer.valueOf(8), bean.anIntegerField); - - commandLine.parse("-int", "34", "-Integer", "34"); - assertEquals(34, bean.intField); - assertEquals(Integer.valueOf(34), bean.anIntegerField); - } - @Test - public void testLongFieldsAreDecimal() { - try { - CommandLine.populateCommand(new SupportedTypes(), "-long", "0xAABBCC", "-Long", "0xAABBCCDD"); - fail("Should fail on hex input"); - } catch (ParameterException expected) { - assertEquals("Could not convert '0xAABBCC' to long for option '-long'" + - ": java.lang.NumberFormatException: For input string: \"0xAABBCC\"", expected.getMessage()); - } - } - @Test - public void testCustomLongConverterAcceptsHexadecimalDecimalAndOctal() { - SupportedTypes bean = new SupportedTypes(); - CommandLine commandLine = new CommandLine(bean); - ITypeConverter longConverter = new ITypeConverter() { - @Override - public Long convert(String s) { - return Long.decode(s); - } - }; - commandLine.registerConverter(Long.class, longConverter); - commandLine.registerConverter(Long.TYPE, longConverter); - commandLine.parse("-long", "0xAABBCC", "-Long", "0xAABBCCDD"); - assertEquals(0xAABBCC, bean.longField); - assertEquals(Long.valueOf(0xAABBCCDDL), bean.aLongField); - - commandLine.parse("-long", "010", "-Long", "010"); - assertEquals(8, bean.longField); - assertEquals(Long.valueOf(8), bean.aLongField); - - commandLine.parse("-long", "34", "-Long", "34"); - assertEquals(34, bean.longField); - assertEquals(Long.valueOf(34), bean.aLongField); - } - @Test - public void testTimeFormatHHmmSupported() throws ParseException { - SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59"); - assertEquals("Time", new Time(new SimpleDateFormat("HH:mm").parse("23:59").getTime()), bean.aTimeField); - } - @Test - public void testTimeFormatHHmmssSupported() throws ParseException { - SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58"); - assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss").parse("23:59:58").getTime()), bean.aTimeField); - } - @Test - public void testTimeFormatHHmmssDotSSSSupported() throws ParseException { - SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58.123"); - assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss.SSS").parse("23:59:58.123").getTime()), bean.aTimeField); - } - @Test - public void testTimeFormatHHmmssCommaSSSSupported() throws ParseException { - SupportedTypes bean = CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58,123"); - assertEquals("Time", new Time(new SimpleDateFormat("HH:mm:ss,SSS").parse("23:59:58,123").getTime()), bean.aTimeField); - } - @Test - public void testTimeFormatHHmmssSSSInvalidError() throws ParseException { - try { - CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58;123"); - fail("Invalid format was accepted"); - } catch (ParameterException expected) { - assertEquals("'23:59:58;123' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); - } - } - @Test - public void testTimeFormatHHmmssDotInvalidError() throws ParseException { - try { - CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:58."); - fail("Invalid format was accepted"); - } catch (ParameterException expected) { - assertEquals("'23:59:58.' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); - } - } - @Test - public void testTimeFormatHHmmsssInvalidError() throws ParseException { - try { - CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:587"); - fail("Invalid format was accepted"); - } catch (ParameterException expected) { - assertEquals("'23:59:587' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); - } - } - @Test - public void testTimeFormatHHmmssColonInvalidError() throws ParseException { - try { - CommandLine.populateCommand(new SupportedTypes(), "-Time", "23:59:"); - fail("Invalid format was accepted"); - } catch (ParameterException expected) { - assertEquals("'23:59:' is not a HH:mm[:ss[.SSS]] time for option '-Time'", expected.getMessage()); - } - } - @Test - public void testDateFormatYYYYmmddInvalidError() throws ParseException { - try { - CommandLine.populateCommand(new SupportedTypes(), "-Date", "20170131"); - fail("Invalid format was accepted"); - } catch (ParameterException expected) { - assertEquals("'20170131' is not a yyyy-MM-dd date for option '-Date'", expected.getMessage()); - } - } - @Test - public void testCharConverterInvalidError() throws ParseException { - try { - CommandLine.populateCommand(new SupportedTypes(), "-Character", "aa"); - fail("Invalid format was accepted"); - } catch (ParameterException expected) { - assertEquals("'aa' is not a single character for option '-Character'", expected.getMessage()); - } - try { - CommandLine.populateCommand(new SupportedTypes(), "-char", "aa"); - fail("Invalid format was accepted"); - } catch (ParameterException expected) { - assertEquals("'aa' is not a single character for option '-char'", expected.getMessage()); - } - } - @Test - public void testNumberConvertersInvalidError() { - parseInvalidValue("-Byte", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-byte", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-Short", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-short", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-Integer", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-int", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-Long", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-long", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-Float", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-float", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-Double", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-double", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - parseInvalidValue("-BigDecimal", "aa", ": java.lang.NumberFormatException"); - parseInvalidValue("-BigInteger", "aa", ": java.lang.NumberFormatException: For input string: \"aa\""); - } - @Test - public void testURLConvertersInvalidError() { - parseInvalidValue("-URL", ":::", ": java.net.MalformedURLException: no protocol: :::"); - } - @Test - public void testURIConvertersInvalidError() { - parseInvalidValue("-URI", ":::", ": java.net.URISyntaxException: Expected scheme name at index 0: :::"); - } - @Test - public void testCharsetConvertersInvalidError() { - parseInvalidValue("-Charset", "aa", ": java.nio.charset.UnsupportedCharsetException: aa"); - } - @Test - public void testInetAddressConvertersInvalidError() { - parseInvalidValue("-InetAddress", "%$::a?*!a", ": java.net.UnknownHostException: %$::a?*!a"); - } - @Test - public void testUUIDConvertersInvalidError() { - parseInvalidValue("-UUID", "aa", ": java.lang.IllegalArgumentException: Invalid UUID string: aa"); - } - @Test - public void testRegexPatternConverterInvalidError() { - parseInvalidValue("-Pattern", "[[(aa", String.format(": java.util.regex.PatternSyntaxException: Unclosed character class near index 4%n" + - "[[(aa%n" + - " ^")); - } - - private void parseInvalidValue(String option, String value, String errorMessage) { - try { - CommandLine.populateCommand(new SupportedTypes(), option, value); - fail("Invalid format " + value + " was accepted for " + option); - } catch (ParameterException actual) { - String type = option.substring(1); - String expected = "Could not convert '" + value + "' to " + type + " for option '" + option + "'" + errorMessage; - assertTrue("expected:<" + expected + "> but was:<" + actual.getMessage() + ">", - actual.getMessage().startsWith(actual.getMessage())); - } - } - - @Test - public void testCustomConverter() { - class Glob { - public final String glob; - public Glob(String glob) { this.glob = glob; } - } - class App { - @Parameters Glob globField; - } - class GlobConverter implements ITypeConverter { - @Override - public Glob convert(String value) throws Exception { return new Glob(value); } - } - CommandLine commandLine = new CommandLine(new App()); - commandLine.registerConverter(Glob.class, new GlobConverter()); - - String[] args = {"a*glob*pattern"}; - List parsed = commandLine.parse(args); - assertEquals("not empty", 1, parsed.size()); - assertTrue(parsed.get(0).getCommand() instanceof App); - App app = (App) parsed.get(0).getCommand(); - assertEquals(args[0], app.globField.glob); - } - - private static class EnumParams { - @Option(names = "-timeUnit") TimeUnit timeUnit; - @Option(names = "-timeUnitArray", arity = "2") TimeUnit[] timeUnitArray; - @Option(names = "-timeUnitList", type = TimeUnit.class, arity = "3") List timeUnitList; - } - @Test - public void testEnumTypeConversionSuceedsForValidInput() { - EnumParams params = CommandLine.populateCommand(new EnumParams(), - "-timeUnit SECONDS -timeUnitArray MILLISECONDS SECONDS -timeUnitList SECONDS MICROSECONDS NANOSECONDS".split(" ")); - assertEquals(SECONDS, params.timeUnit); - assertArrayEquals(new TimeUnit[]{MILLISECONDS, TimeUnit.SECONDS}, params.timeUnitArray); - List expected = new ArrayList(Arrays.asList(TimeUnit.SECONDS, TimeUnit.MICROSECONDS, TimeUnit.NANOSECONDS)); - assertEquals(expected, params.timeUnitList); - } - @Test - public void testEnumTypeConversionFailsForInvalidInput() { - try { - CommandLine.populateCommand(new EnumParams(), "-timeUnit", "xyz"); - fail("Accepted invalid timeunit"); - } catch (Exception ex) { - String prefix = "Could not convert 'xyz' to TimeUnit for option '-timeUnit'" + - ": java.lang.IllegalArgumentException: No enum cons"; - String suffix = " java.util.concurrent.TimeUnit.xyz"; - assertEquals(prefix, ex.getMessage().substring(0, prefix.length())); - assertEquals(suffix, ex.getMessage().substring(ex.getMessage().length() - suffix.length(), ex.getMessage().length())); - } - } - @Ignore("Requires #14 case-insensitive enum parsing") - @Test - public void testEnumTypeConversionIsCaseInsensitive() { - EnumParams params = CommandLine.populateCommand(new EnumParams(), - "-timeUnit sEcONds -timeUnitArray milliSeconds miCroSeConds -timeUnitList SEConds MiCROsEconds nanoSEConds".split(" ")); - assertEquals(SECONDS, params.timeUnit); - assertArrayEquals(new TimeUnit[]{MILLISECONDS, TimeUnit.MICROSECONDS}, params.timeUnitArray); - List expected = new ArrayList(Arrays.asList(TimeUnit.SECONDS, TimeUnit.MICROSECONDS, TimeUnit.NANOSECONDS)); - assertEquals(expected, params.timeUnitList); - } - @Test - public void testEnumArrayTypeConversionFailsForInvalidInput() { - try { - CommandLine.populateCommand(new EnumParams(), "-timeUnitArray", "a", "b"); - fail("Accepted invalid timeunit"); - } catch (Exception ex) { - String prefix = "Could not convert 'a' to TimeUnit for option '-timeUnitArray' at index 0 ()" + - ": java.lang.IllegalArgumentException: No enum const"; - String suffix = " java.util.concurrent.TimeUnit.a"; - assertEquals(prefix, ex.getMessage().substring(0, prefix.length())); - assertEquals(suffix, ex.getMessage().substring(ex.getMessage().length() - suffix.length(), ex.getMessage().length())); - } - } - @Test - public void testEnumListTypeConversionFailsForInvalidInput() { - try { - CommandLine.populateCommand(new EnumParams(), "-timeUnitList", "SECONDS", "b", "c"); - fail("Accepted invalid timeunit"); - } catch (Exception ex) { - String prefix = "Could not convert 'b' to TimeUnit for option '-timeUnitList' at index 1 ()" + - ": java.lang.IllegalArgumentException: No enum const"; - String suffix = " java.util.concurrent.TimeUnit.b"; - assertEquals(prefix, ex.getMessage().substring(0, prefix.length())); - assertEquals(suffix, ex.getMessage().substring(ex.getMessage().length() - suffix.length(), ex.getMessage().length())); - } - } - - @Test - public void testArrayOptionParametersAreAlwaysInstantiated() { - EnumParams params = new EnumParams(); - TimeUnit[] array = params.timeUnitArray; - new CommandLine(params).parse("-timeUnitArray", "SECONDS", "MILLISECONDS"); - assertNotSame(array, params.timeUnitArray); - } - @Test - public void testListOptionParametersAreInstantiatedIfNull() { - EnumParams params = new EnumParams(); - assertNull(params.timeUnitList); - new CommandLine(params).parse("-timeUnitList", "SECONDS", "MICROSECONDS", "MILLISECONDS"); - assertEquals(Arrays.asList(SECONDS, MICROSECONDS, MILLISECONDS), params.timeUnitList); - } - @Test - public void testListOptionParametersAreReusedInstantiatedIfNonNull() { - EnumParams params = new EnumParams(); - List list = new ArrayList(); - params.timeUnitList = list; - new CommandLine(params).parse("-timeUnitList", "SECONDS", "MICROSECONDS", "SECONDS"); - assertEquals(Arrays.asList(SECONDS, MICROSECONDS, SECONDS), params.timeUnitList); - assertSame(list, params.timeUnitList); - } - @Test - public void testArrayPositionalParametersAreAppendedNotReplaced() { - class ArrayPositionalParams { - @Parameters() int[] array; - } - ArrayPositionalParams params = new ArrayPositionalParams(); - params.array = new int[3]; - int[] array = params.array; - new CommandLine(params).parse("3", "2", "1"); - assertNotSame(array, params.array); - assertArrayEquals(new int[]{0, 0, 0, 3, 2, 1}, params.array); - } - private class ListPositionalParams { - @Parameters(type = Integer.class) List list; - } - @Test - public void testListPositionalParametersAreInstantiatedIfNull() { - ListPositionalParams params = new ListPositionalParams(); - assertNull(params.list); - new CommandLine(params).parse("3", "2", "1"); - assertNotNull(params.list); - assertEquals(Arrays.asList(3, 2, 1), params.list); - } - @Test - public void testListPositionalParametersAreReusedIfNonNull() { - ListPositionalParams params = new ListPositionalParams(); - params.list = new ArrayList(); - List list = params.list; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.list); - assertEquals(Arrays.asList(3, 2, 1), params.list); - } - @Test - public void testListPositionalParametersAreAppendedToIfNonNull() { - ListPositionalParams params = new ListPositionalParams(); - params.list = new ArrayList(); - params.list.add(234); - List list = params.list; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.list); - assertEquals(Arrays.asList(234, 3, 2, 1), params.list); - } - class SortedSetPositionalParams { - @Parameters(type = Integer.class) SortedSet sortedSet; - } - @Test - public void testSortedSetPositionalParametersAreInstantiatedIfNull() { - SortedSetPositionalParams params = new SortedSetPositionalParams(); - assertNull(params.sortedSet); - new CommandLine(params).parse("3", "2", "1"); - assertNotNull(params.sortedSet); - assertEquals(Arrays.asList(1, 2, 3), new ArrayList(params.sortedSet)); - } - @Test - public void testSortedSetPositionalParametersAreReusedIfNonNull() { - SortedSetPositionalParams params = new SortedSetPositionalParams(); - params.sortedSet = new TreeSet(); - SortedSet list = params.sortedSet; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.sortedSet); - assertEquals(Arrays.asList(1, 2, 3), new ArrayList(params.sortedSet)); - } - @Test - public void testSortedSetPositionalParametersAreAppendedToIfNonNull() { - SortedSetPositionalParams params = new SortedSetPositionalParams(); - params.sortedSet = new TreeSet(); - params.sortedSet.add(234); - SortedSet list = params.sortedSet; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.sortedSet); - assertEquals(Arrays.asList(1, 2, 3, 234), new ArrayList(params.sortedSet)); - } - class SetPositionalParams { - @Parameters(type = Integer.class) Set set; - } - @Test - public void testSetPositionalParametersAreInstantiatedIfNull() { - SetPositionalParams params = new SetPositionalParams(); - assertNull(params.set); - new CommandLine(params).parse("3", "2", "1"); - assertNotNull(params.set); - assertEquals(new HashSet(Arrays.asList(1, 2, 3)), params.set); - } - @Test - public void testSetPositionalParametersAreReusedIfNonNull() { - SetPositionalParams params = new SetPositionalParams(); - params.set = new TreeSet(); - Set list = params.set; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.set); - assertEquals(new HashSet(Arrays.asList(1, 2, 3)), params.set); - } - @Test - public void testSetPositionalParametersAreAppendedToIfNonNull() { - SetPositionalParams params = new SetPositionalParams(); - params.set = new TreeSet(); - params.set.add(234); - Set list = params.set; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.set); - assertEquals(new HashSet(Arrays.asList(1, 2, 3, 234)), params.set); - } - class QueuePositionalParams { - @Parameters(type = Integer.class) Queue queue; - } - @Test - public void testQueuePositionalParametersAreInstantiatedIfNull() { - QueuePositionalParams params = new QueuePositionalParams(); - assertNull(params.queue); - new CommandLine(params).parse("3", "2", "1"); - assertNotNull(params.queue); - assertEquals(new LinkedList(Arrays.asList(3, 2, 1)), params.queue); - } - @Test - public void testQueuePositionalParametersAreReusedIfNonNull() { - QueuePositionalParams params = new QueuePositionalParams(); - params.queue = new LinkedList(); - Queue list = params.queue; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.queue); - assertEquals(new LinkedList(Arrays.asList(3, 2, 1)), params.queue); - } - @Test - public void testQueuePositionalParametersAreAppendedToIfNonNull() { - QueuePositionalParams params = new QueuePositionalParams(); - params.queue = new LinkedList(); - params.queue.add(234); - Queue list = params.queue; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.queue); - assertEquals(new LinkedList(Arrays.asList(234, 3, 2, 1)), params.queue); - } - class CollectionPositionalParams { - @Parameters(type = Integer.class) Collection collection; - } - @Test - public void testCollectionPositionalParametersAreInstantiatedIfNull() { - CollectionPositionalParams params = new CollectionPositionalParams(); - assertNull(params.collection); - new CommandLine(params).parse("3", "2", "1"); - assertNotNull(params.collection); - assertEquals(Arrays.asList(3, 2, 1), params.collection); - } - @Test - public void testCollectionPositionalParametersAreReusedIfNonNull() { - CollectionPositionalParams params = new CollectionPositionalParams(); - params.collection = new ArrayList(); - Collection list = params.collection; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.collection); - assertEquals(Arrays.asList(3, 2, 1), params.collection); - } - @Test - public void testCollectionPositionalParametersAreAppendedToIfNonNull() { - CollectionPositionalParams params = new CollectionPositionalParams(); - params.collection = new ArrayList(); - params.collection.add(234); - Collection list = params.collection; - new CommandLine(params).parse("3", "2", "1"); - assertSame(list, params.collection); - assertEquals(Arrays.asList(234, 3, 2, 1), params.collection); - } - - @Test(expected = DuplicateOptionAnnotationsException.class) - public void testDuplicateOptionsAreRejected() { - /** Duplicate parameter names are invalid. */ - class DuplicateOptions { - @Option(names = "-duplicate") public int value1; - @Option(names = "-duplicate") public int value2; - } - new CommandLine(new DuplicateOptions()); - } - - @Test(expected = DuplicateOptionAnnotationsException.class) - public void testClashingAnnotationsAreRejected() { - class ClashingAnnotation { - @Option(names = "-o") - @Parameters - public String[] bothOptionAndParameters; - } - new CommandLine(new ClashingAnnotation()); - } - - private static class PrivateFinalOptionFields { - @Option(names = "-f") private final String field = null; - @Option(names = "-p") private final int primitive = 43; - } - @Test - public void testCanInitializePrivateFinalFields() { - PrivateFinalOptionFields ff = CommandLine.populateCommand(new PrivateFinalOptionFields(), "-f", "reference value"); - assertEquals("reference value", ff.field); - } - @Ignore("Needs Reject final primitive fields annotated with @Option or @Parameters #68") - @Test - public void testCanInitializeFinalPrimitiveFields() { - PrivateFinalOptionFields ff = CommandLine.populateCommand(new PrivateFinalOptionFields(), "-p", "12"); - assertEquals("primitive value", 12, ff.primitive); - } - @Test - public void testLastValueSelectedIfOptionSpecifiedMultipleTimes() { - setTraceLevel("OFF"); - CommandLine cmd = new CommandLine(new PrivateFinalOptionFields()).setOverwrittenOptionsAllowed(true); - cmd.parse("-f", "111", "-f", "222"); - PrivateFinalOptionFields ff = (PrivateFinalOptionFields) cmd.getCommand(); - assertEquals("222", ff.field); - } - - private static class PrivateFinalParameterFields { - @Parameters(index = "0") private final String field = null; - @Parameters(index = "1", arity = "0..1") private final int primitive = 43; - } - @Test - public void testCanInitializePrivateFinalParameterFields() { - PrivateFinalParameterFields ff = CommandLine.populateCommand(new PrivateFinalParameterFields(), "ref value"); - assertEquals("ref value", ff.field); - } - @Ignore("Needs Reject final primitive fields annotated with @Option or @Parameters #68") - @Test - public void testCannotInitializePrivateFinalPrimitiveParameterFields() { - PrivateFinalParameterFields ff = CommandLine.populateCommand(new PrivateFinalParameterFields(), "ref value", "12"); - assertEquals("ref value", ff.field); - assertEquals("primitive value", 12, ff.primitive); - } - - private static class RequiredField { - @Option(names = {"-?", "/?"}, help = true) boolean isHelpRequested; - @Option(names = {"-V", "--version"}, versionHelp= true) boolean versionHelp; - @Option(names = {"-h", "--help"}, usageHelp = true) boolean usageHelp; - @Option(names = "--required", required = true) private String required; - @Parameters private String[] remainder; - } - @Test - public void testErrorIfRequiredOptionNotSpecified() { - try { - CommandLine.populateCommand(new RequiredField(), "arg1", "arg2"); - fail("Missing required field should have thrown exception"); - } catch (MissingParameterException ex) { - assertEquals("Missing required option '--required='", ex.getMessage()); - } - } - @Test - public void testNoErrorIfRequiredOptionSpecified() { - CommandLine.populateCommand(new RequiredField(), "--required", "arg1", "arg2"); - } - @Test - public void testNoErrorIfRequiredOptionNotSpecifiedWhenHelpRequested() { - RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "-?"); - assertTrue("help requested", requiredField.isHelpRequested); - } - @Test - public void testNoErrorIfRequiredOptionNotSpecifiedWhenUsageHelpRequested() { - RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "--help"); - assertTrue("usage help requested", requiredField.usageHelp); - } - @Test - public void testNoErrorIfRequiredOptionNotSpecifiedWhenVersionHelpRequested() { - RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "--version"); - assertTrue("version info requested", requiredField.versionHelp); - } - @Test - public void testCommandLine_isUsageHelpRequested_trueWhenSpecified() { - List parsedCommands = new CommandLine(new RequiredField()).parse("--help"); - assertTrue("usage help requested", parsedCommands.get(0).isUsageHelpRequested()); - } - @Test - public void testCommandLine_isVersionHelpRequested_trueWhenSpecified() { - List parsedCommands = new CommandLine(new RequiredField()).parse("--version"); - assertTrue("version info requested", parsedCommands.get(0).isVersionHelpRequested()); - } - @Test - public void testCommandLine_isUsageHelpRequested_falseWhenNotSpecified() { - List parsedCommands = new CommandLine(new RequiredField()).parse("--version"); - assertFalse("usage help requested", parsedCommands.get(0).isUsageHelpRequested()); - } - @Test - public void testCommandLine_isVersionHelpRequested_falseWhenNotSpecified() { - List parsedCommands = new CommandLine(new RequiredField()).parse("--help"); - assertFalse("version info requested", parsedCommands.get(0).isVersionHelpRequested()); - } - @Test - public void testHelpRequestedFlagResetWhenParsing_staticMethod() { - RequiredField requiredField = CommandLine.populateCommand(new RequiredField(), "-?"); - assertTrue("help requested", requiredField.isHelpRequested); - - requiredField.isHelpRequested = false; - - // should throw error again on second pass (no help was requested here...) - try { - CommandLine.populateCommand(requiredField, "arg1", "arg2"); - fail("Missing required field should have thrown exception"); - } catch (MissingParameterException ex) { - assertEquals("Missing required option '--required='", ex.getMessage()); - } - } - @Test - public void testHelpRequestedFlagResetWhenParsing_instanceMethod() { - RequiredField requiredField = new RequiredField(); - CommandLine commandLine = new CommandLine(requiredField); - commandLine.parse("-?"); - assertTrue("help requested", requiredField.isHelpRequested); - - requiredField.isHelpRequested = false; - - // should throw error again on second pass (no help was requested here...) - try { - commandLine.parse("arg1", "arg2"); - fail("Missing required field should have thrown exception"); - } catch (MissingParameterException ex) { - assertEquals("Missing required option '--required='", ex.getMessage()); - } - } - - private class CompactFields { - @Option(names = "-v") boolean verbose; - @Option(names = "-r") boolean recursive; - @Option(names = "-o") File outputFile; - @Parameters File[] inputFiles; - } - @Test - public void testCompactFieldsAnyOrder() { - //cmd -a -o arg path path - //cmd -o arg -a path path - //cmd -a -o arg -- path path - //cmd -a -oarg path path - //cmd -aoarg path path - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvoout"); - verifyCompact(compact, true, true, "out", null); - - // change order within compact group - compact = CommandLine.populateCommand(new CompactFields(), "-vroout"); - verifyCompact(compact, true, true, "out", null); - - // compact group with separator - compact = CommandLine.populateCommand(new CompactFields(), "-vro=out"); - verifyCompact(compact, true, true, "out", null); - - compact = CommandLine.populateCommand(new CompactFields(), "-rv p1 p2".split(" ")); - verifyCompact(compact, true, true, null, fileArray("p1", "p2")); - - compact = CommandLine.populateCommand(new CompactFields(), "-voout p1 p2".split(" ")); - verifyCompact(compact, true, false, "out", fileArray("p1", "p2")); - - compact = CommandLine.populateCommand(new CompactFields(), "-voout -r p1 p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - - compact = CommandLine.populateCommand(new CompactFields(), "-r -v -oout p1 p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - - compact = CommandLine.populateCommand(new CompactFields(), "-rv -o out p1 p2".split(" ")); //#233 - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - - compact = CommandLine.populateCommand(new CompactFields(), "-oout -r -v p1 p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - - compact = CommandLine.populateCommand(new CompactFields(), "-rvo out p1 p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - - try { - CommandLine.populateCommand(new CompactFields(), "-oout -r -vp1 p2".split(" ")); - fail("should fail: -v does not take an argument"); - } catch (UnmatchedArgumentException ex) { - assertEquals("Unmatched argument [-p1]", ex.getMessage()); - } - } - - @Test - public void testCompactFieldsWithUnmatchedArguments() { - setTraceLevel("OFF"); - CommandLine cmd = new CommandLine(new CompactFields()).setUnmatchedArgumentsAllowed(true); - cmd.parse("-oout -r -vp1 p2".split(" ")); - assertEquals(Arrays.asList("-p1"), cmd.getUnmatchedArguments()); - } - - @Test - public void testCompactWithOptionParamSeparatePlusParameters() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-r -v -o out p1 p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - } - - @Test - public void testCompactWithOptionParamAttachedEqualsSeparatorChar() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvo=out p1 p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - } - - @Test - public void testCompactWithOptionParamAttachedColonSeparatorChar() { - CompactFields compact = new CompactFields(); - CommandLine cmd = new CommandLine(compact); - cmd.setSeparator(":"); - cmd.parse("-rvo:out p1 p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - } - - /** See {@link #testGnuLongOptionsWithVariousSeparators()} */ - @Test - public void testDefaultSeparatorIsEquals() { - assertEquals("=", new CommandLine(new CompactFields()).getSeparator()); - } - - @Test - public void testOptionsMixedWithParameters() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-r -v p1 -o out p2".split(" ")); - verifyCompact(compact, true, true, "out", fileArray("p1", "p2")); - } - @Test - public void testShortOptionsWithSeparatorButNoValueAssignsEmptyStringEvenIfNotLast() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-ro= -v".split(" ")); - verifyCompact(compact, false, true, "-v", null); - } - @Test - public void testShortOptionsWithColonSeparatorButNoValueAssignsEmptyStringEvenIfNotLast() { - CompactFields compact = new CompactFields(); - CommandLine cmd = new CommandLine(compact); - cmd.setSeparator(":"); - cmd.parse("-ro: -v".split(" ")); - verifyCompact(compact, false, true, "-v", null); - } - @Test - public void testShortOptionsWithSeparatorButNoValueFailsIfValueRequired() { - try { - CommandLine.populateCommand(new CompactFields(), "-rvo=".split(" ")); - fail("Expected exception"); - } catch (ParameterException ex) { - assertEquals("Missing required parameter for option '-o' ()", ex.getMessage()); - } - } - @Test - public void testShortOptionsWithSeparatorButNoValueAssignsQuotedEmptyStringEvenIfNotLast() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-ro=\"\" -v".split(" ")); - verifyCompact(compact, true, true, "", null); - } - @Test - public void testShortOptionsWithColonSeparatorButNoValueAssignsQuotedEmptyStringEvenIfNotLast() { - CompactFields compact = new CompactFields(); - CommandLine cmd = new CommandLine(compact); - cmd.setSeparator(":"); - cmd.parse("-ro:\"\" -v".split(" ")); - verifyCompact(compact, true, true, "", null); - } - @Test - public void testShortOptionsWithSeparatorButNoValueAssignsEmptyQuotedStringIfLast() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-rvo=\"\"".split(" ")); - verifyCompact(compact, true, true, "", null); - } - - @Test - public void testDoubleDashSeparatesPositionalParameters() { - CompactFields compact = CommandLine.populateCommand(new CompactFields(), "-oout -- -r -v p1 p2".split(" ")); - verifyCompact(compact, false, false, "out", fileArray("-r", "-v", "p1", "p2")); - } - - @Test - public void testDebugOutputForDoubleDashSeparatesPositionalParameters() throws UnsupportedEncodingException { - PrintStream originalErr = System.err; - ByteArrayOutputStream baos = new ByteArrayOutputStream(2500); - System.setErr(new PrintStream(baos)); - final String PROPERTY = "picocli.trace"; - String old = System.getProperty(PROPERTY); - System.setProperty(PROPERTY, "DEBUG"); - CommandLine.populateCommand(new CompactFields(), "-oout -- -r -v p1 p2".split(" ")); - System.setErr(originalErr); - if (old == null) { - System.clearProperty(PROPERTY); - } else { - System.setProperty(PROPERTY, old); - } - String expected = String.format("" + - "[picocli INFO] Parsing 6 command line args [-oout, --, -r, -v, p1, p2]%n" + - "[picocli DEBUG] Initializing %1$s$CompactFields: 3 options, 1 positional parameters, 0 required, 0 subcommands.%n" + - "[picocli DEBUG] Processing argument '-oout'. Remainder=[--, -r, -v, p1, p2]%n" + - "[picocli DEBUG] '-oout' cannot be separated into
    base64 + Base64 encoded data. The format is ${dollar}{base64:Base64_encoded_data}. + For example: + ${dollar}{base64:SGVsbG8gV29ybGQhCg==} yields Hello World!. +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Log4j DependencyDependencies to Exclude
    [log4j-1.2-api](log4j-1.2-api)log4j:log4jorg.slf4j:log4j-over-slf4j -
    [log4j-core](log4j-core)log4j:log4jch.qos.logback:logback-coreorg.apache.logging.log4j:log4j-to-slf4j
    [log4j-jcl](log4j-jcl)org.slf4j:jcl-over-slf4j - -
    [log4j-jul](log4j-jul)org.slf4j:jul-to-slf4j - -
    [log4j-slf4j-impl](log4j-slf4j-impl)org.apache.logging.log4j:log4j-to-slf4jch.qos.logback:logback-core -
    - -Using Apache Maven, dependencies can be globally excluded in your project like so: - -``` - - - log4j - log4j - 1.2.17 - provided - - -``` - -Dependencies can be explicitly excluded for specific dependencies as well. For example, to use a project -with Log4j 2 instead of Log4j 1.x: - -``` - - - com.example - example-project - 1.0 - - - log4j - log4j - - - org.slf4j - slf4j-log4j12 - - - - - org.apache.logging.log4j - log4j-core - ${Log4jReleaseVersion} - - - org.apache.logging.log4j - log4j-slf4j-impl - ${Log4jReleaseVersion} - - - org.apache.logging.log4j - log4j-1.2-api - ${Log4jReleaseVersion} - - -``` - -Dependencies can be globally excluded in Gradle like so: - -``` -configurations { - all*.exclude group: 'log4j', module: 'log4j' -} -``` - -The equivalent Gradle config for the above Maven exclusion would look like: - -``` -dependencies { - compile('com.example:example-project:1.0') { - exclude group: 'log4j', module: 'log4j' - exclude group: 'org.slf4j', module: 'slf4j-log4j12' - } - compile('org.apache.logging.log4j:log4j-core:${Log4jReleaseVersion}') - compile('org.apache.logging.log4j:log4j-slf4j-impl:${Log4jReleaseVersion}') - compile('org.apache.logging.log4j:log4j-1.2-api:${Log4jReleaseVersion}') -} -``` - - -$h4 How do I specify the configuration file location? - -By default, Log4j looks for a configuration file named **log4j2.xml** (not log4j.xml) in the classpath. - -You can also specify the full path of the configuration file with this system property: -`-Dlog4j.configurationFile=path/to/log4j2.xml` - -That property can also be included in a classpath resource file named `log4j2.component.properties`. - -Web applications can specify the Log4j configuration file location with a servlet context parameter. -See [this section](http://logging.apache.org/log4j/2.x/manual/webapp.html#ContextParams) -of the Using Log4j 2 in Web Applications manual page. - - -$h4 How do I configure log4j2 in code without a configuration file? - -Starting with version 2.4, Log4j 2 provides an [API for programmatic configuration](manual/customconfig.html) -The new -[`ConfigurationBuilder` API](log4j-core/apidocs/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.html) -allows you to create Configurations in code by constructing component definitions -without requiring you to know about the internals of actual configuration objects like Loggers and Appenders. - - -$h4 How do I reconfigure log4j2 in code with a specific configuration file? - -See the below example. -Be aware that this LoggerContext class is not part of the public API so your code may break with any minor release. - -``` -// import org.apache.logging.log4j.core.LoggerContext; - -LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false); -File file = new File("path/to/a/different/log4j2.xml"); - -// this will force a reconfiguration -context.setConfigLocation(file.toURI()); -``` - - -$h4 How do I shut down log4j2 in code? - -Normally there is no need to do this manually. -Each `LoggerContext` registers a shutdown hook that takes care of releasing resources -when the JVM exits (unless system property `log4j.shutdownHookEnabled` is set to `false`). -Web applications should include the log4j-web -module in their classpath which disables the shutdown hook but instead -cleans up log4j resources when the web application is stopped. - -However, if you need to manually shut down Log4j, you can do so as in the below example. -Note that there is an optional parameter for specifying which `LoggerContext` to -shut down. - -``` -import org.apache.logging.log4j.LogManager; - -// ... - -LogManager.shutdown(); -``` - - -$h4 How do I send log messages with different levels to different appenders? -You don't need to declare separate loggers to achieve this. -You can set the logging level on the `AppenderRef` element. - -``` - - - - - - %d %p %c{1.} [%t] %m %ex%n - - - - - - - - - - - - - -``` - - -$h4 How do I debug my configuration? - -First, make sure you have [the right jar files](#which_jars) on your classpath. -You need at least log4j-api and log4j-core. - -Next, check the name of your configuration file. By default, log4j2 will look -for a configuration file named `log4j2.xml` on the classpath. Note the "2" in the file name! -(See the [configuration manual page](manual/configuration.html#AutomaticConfiguration) for more details.) - -**From log4j-2.9 onward** - -From log4j-2.9 onward, log4j2 will print all internal logging to the console if system property `log4j2.debug` -is defined (with any or no value). - -**Prior to log4j-2.9** - -Prior to log4j-2.9, there are two places where internal logging can be controlled: - -If the configuration file is found correctly, log4j2 internal status logging can be controlled by -setting `` in the configuration file. This will display detailed log4j2-internal -log statements on the console about what happens during the configuration process. -This may be useful to trouble-shoot configuration issues. -By default the status logger level is WARN, so you only see notifications when there is a problem. - -If the configuration file is not found correctly, you can still enable -log4j2 internal status logging by setting system property -`-Dorg.apache.logging.log4j.simplelog.StatusLogger.level=TRACE`. - - -$h4 How do I dynamically write to separate log files? - -Look at the [RoutingAppender](http://logging.apache.org/log4j/2.x/manual/appenders.html#RoutingAppender). -You can define multiple routes in the configuration, and put values in the `ThreadContext` -map that determine which log file subsequent events in this thread get logged to. - -You can use the `ThreadContext` map value to determine the log file name. - -``` - - - - - - - - %d{ISO8601} [%t] %p %c{3} - %m%n - - - - - - - - - - - - - %d{ISO8601} [%t] %p %c{3} - %m%n - - - - - - - - - - - - - %d{ISO8601} [%t] %p %c{3} - %m%n - - - - - - - - - -``` - - -$h4 How do I set a logger's level programmatically? - -You can set a logger's level with the class -[`Configurator`](log4j-core/apidocs/org/apache/logging/log4j/core/config/Configurator.html) -from Log4j Core. Be aware that the `Configurator` class is not part of the public API. - -``` -// org.apache.logging.log4j.core.config.Configurator; - -Configurator.setLevel("com.example.Foo", Level.DEBUG); - -// You can also set the root logger: -Configurator.setRootLevel(Level.DEBUG); -``` - - -$h4 How do I set my log archive retention policy? How do I delete old log archives? - -The `DefaultRolloverStrategy` of the Rolling File appender (and Rolling Random Access File -appender) supports a [Delete](manual/appenders.html#CustomDeleteOnRollover) element. - -Starting at a specified base directory, you can delete all files for which some condition -holds true, for example all files that match a given file name pattern and are older -than some number of days. More complex conditions are possible, and if the built-in -conditions are not sufficient, users can provide custom conditions by creating -[plugin conditions](manual/appenders.html#DeletePathCondition) or by writing a -[script condition](manual/appenders.html#ScriptCondition). - - -$h4 What are the trade-offs of using the Log4j 2 API versus the SLF4J API? - -The Log4j 2 API and SLF4J have a lot in common. -They both share the objective of cleanly separating the logging API from the implementation. -We believe that the Log4j 2 API can help make your application more performant -while offering more functionality and more flexibility. - -There may be a concern that using the Log4j 2 API will tightly couple your application to Log4j 2. -This is not the case: applications coded to the Log4j 2 API always have the option to use any SLF4J-compliant -library as their logging implementation with the log4j-to-slf4j adapter. -See the [which jars](#which_jars_log4j-to-slf4j) FAQ entry for details. - -There are several advantages to using the Log4j 2 API: - -* SLF4J forces your application to log Strings. -The Log4j 2 API supports logging any CharSequence if you want to log text, but also -supports logging any Object as is. -It is the responsibility of the logging implementation to handle this object, -and we consider it a design mistake to limit applications to logging Strings. -* The Log4j 2 API offers support for logging [Message objects](manual/messages.html). -Messages allow support for interesting and complex constructs to be passed -through the logging system and be efficiently manipulated. -Users are free to create their own Message types and write custom Layouts, -Filters and Lookups to manipulate them. -* The Log4j 2 API has support for Java 8 [lambda expressions](manual/api.html#LambdaSupport). -* The Log4j 2 API has better support for [garbage-free logging](manual/garbagefree.html): -it avoids creating vararg arrays and avoids creating Strings when logging CharSequence objects. - - -$h4 Is Log4j 2 still garbage-free when I use the SLF4J API? - -Yes, the log4j-slf4j-impl binding (together with log4j-core) implements the -`org.slf4j.Logger` methods to be GC-free. -However, bear in mind that there are some limitations: - -The SLF4J API only offers up to two parameters for a parameterized message. -More than that uses varargs which creates a temporary object for the parameter array. -The Log4j 2.6 API has methods for up to ten unrolled parameters. - -Another consideration is that the SLF4J API forces your application to log Strings. -Log4j 2 API lets you log any java.lang.CharSequence, and even any Objects. -Log4j can log any Object that implements `java.lang.CharSequence` -or `org.apache.logging.log4j.util.StringBuilderFormattable` without creating garbage. - -The [`org.slf4j.spi.LocationAwareLogger::log`](http://www.slf4j.org/api/org/slf4j/spi/LocationAwareLogger.html#log(org.slf4j.Marker, java.lang.String, int, java.lang.String, java.lang.Object[], java.lang.Throwable)) -method is not yet implemented -in a garbage-free manner in the log4j-slf4j-impl binding. It creates a new message object for each call. - - -$h4 How do I log my domain object without creating garbage? - -One option is to let the domain object implement java.lang.CharSequence. -However, for many domain objects it may not be trivial to implement this without allocating temporary -objects. - -An alternative is to implement the `org.apache.logging.log4j.util.StringBuilderFormattable` interface. -If an object is logged that implements this interface, its `formatTo` method is called instead of -`toString()`. - -``` -package org.apache.logging.log4j.util; -public interface StringBuilderFormattable { - /** - * Writes a text representation of this object into the specified {@code StringBuilder}, - * ideally without allocating temporary objects. - * - * @param buffer the StringBuilder to write into - */ - void formatTo(StringBuilder buffer); -} -``` - - -$h4 How do I create a custom logger wrapper that shows the correct class, method and line number? - -Log4j remembers the fully qualified class name (FQCN) of the logger and uses this to walk the stack trace -for every log event when configured to print location. -(Be aware that logging with location is slow and may impact the performance of your application.) - -The problem with custom logger wrappers is that they have a different FQCN than the actual logger, -so Log4j can't find the place where your custom logger was called. - -The solution is to provide the correct FQCN. The easiest way to do this is to let Log4j generate -the logger wrapper for you. Log4j comes with a Logger wrapper generator tool. -This tool was originally meant to support custom log levels and is documented -[here](https://logging.apache.org/log4j/2.x/manual/customloglevels.html#CustomLoggers). - -The generated logger code will take care of the FQCN. diff --git a/src/site/markdown/index.md.vm b/src/site/markdown/index.md.vm deleted file mode 100644 index ad28be41a49..00000000000 --- a/src/site/markdown/index.md.vm +++ /dev/null @@ -1,132 +0,0 @@ - - -#set($h1='#') -#set($h2='##') -#set($h3='###') - -$h1 Apache Log4j 2 - -Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and -provides many of the improvements available in Logback while fixing some inherent problems in Logback's architecture. - -$h2 Features - -$h3 API Separation - -The API for Log4j is separate from the implementation making it clear for application developers which classes and -methods they can use while ensuring forward compatibility. This allows the Log4j team to improve the implementation -safely and in a compatible manner. - -$h3 Improved Performance - -Log4j 2 contains next-generation Asynchronous Loggers based on the LMAX Disruptor library. In multi-threaded scenarios -Asynchronous Loggers have 18 times higher throughput and orders of magnitude lower latency than Log4j 1.x and Logback. -See [Asynchronous Logging Performance](manual/async.html#Performance) for details. Otherwise, Log4j 2 significantly -outperforms Log4j 1.x, Logback and java.util.logging, especially in multi-threaded applications. -See [Performance](performance.html) for more information. - -$h3 Support for multiple APIs - -While the Log4j 2 API will provide the best performance, Log4j 2 provides support for the Log4j 1.2, SLF4J, Commons -Logging and java.util.logging (JUL) APIs. - -$h3 Avoid lock-in - -Applications coded to the Log4j 2 API always have the option to use any SLF4J-compliant library as their logger -implementation with the log4j-to-slf4j adapter. - -$h3 Automatic Reloading of Configurations - -Like Logback, Log4j 2 can automatically reload its configuration upon modification. Unlike Logback, it will do so -without losing log events while reconfiguration is taking place. - -$h3 Advanced Filtering - -Like Logback, Log4j 2 supports filtering based on context data, markers, regular expressions, and other components in -the Log event. Filtering can be specified to apply to all events before being passed to Loggers or as they pass through -Appenders. In addition, filters can also be associated with Loggers. Unlike Logback, you can use a common Filter class -in any of these circumstances. - -$h3 Plugin Architecture - -Log4j uses the plugin pattern to configure components. As such, you do not need to write code to create and configure an -Appender, Layout, Pattern Converter, and so on. Log4j automatically recognizes plugins and uses them when a -configuration references them. - -$h3 Property Support - -You can reference properties in a configuration, Log4j will directly replace them, or Log4j will pass them to an -underlying component that will dynamically resolve them. Properties come from values defined in the configuration file, -system properties, environment variables, the ThreadContext Map, and data present in the event. Users can further -customize the property providers by adding their own [Lookup](manual/lookups.html) Plugin. - -$h3 Java 8 Lambda Support - -Previously, if a log message was expensive to construct, you would often explicitly check if the requested log level is -enabled before constructing the message. Client code running on Java 8 can benefit from Log4j's -[lambda support](manual/api.html#LambdaSupport). Since Log4j will not evaluate a lambda expression if the requested log -level is not enabled, the same effect can be achieved with less code. - -$h3 Custom Log Levels - -In Log4j 2, [custom log levels](manual/customloglevels.html) can easily be defined in code or in configuration. No -subclassing is required. - -$h3 Garbage-free - -During steady state logging, Log4j 2 is [garbage-free](manual/garbagefree.html) in stand-alone applications, and low -garbage in web applications. This reduces pressure on the garbage collector and can give better response time performance. - -#h3 Integrating with Application Servers - -Version 2.10.0 introduces a the module log4j-appserver to improve integration with Apache Tomcat and Eclipse Jetty. - -$h2 Documentation - -The Log4j 2 User's Guide is available on this [site](manual/index.html) or as a downloadable -[PDF](log4j-users-guide.pdf). - -$h2 Requirements - -Log4j 2.4 and greater requires Java 7, versions 2.0-alpha1 to 2.3 required Java 6. Some features require optional -dependencies; the documentation for these features specifies the dependencies. - -$h2 News - -Log4j $Log4jReleaseVersion is now available for production. The API for Log4j 2 is not compatible with Log4j 1.x, however an adapter is -available to allow applications to continue to use the Log4j 1.x API. Adapters are also available for Apache Commons -Logging, SLF4J, and java.util.logging. - -Log4j $Log4jReleaseVersion is the latest release of Log4j and contains several bug fixes that were found after the release of Log4j 2.6. -The list of fixes can be found in the latest [changes report](changes-report.html#a$Log4jReleaseVersion). - -Note that subsequent to the release of Log4j 2.6 a minor source incompatibility with prior release was found due to the -addition of new methods to the Logger interface. If you have code that does: - - logger.error(null, "This is the log message", throwable); - -or similar with any log level you will get a compiler error saying the reference is ambiguous. To correct this either -do: - - logger.error("This is the log message", throwable); - -or - - logger.error((Marker) null, "This is the log message", throwable); - -Log4j $Log4jReleaseVersion maintains binary compatibility with previous releases. diff --git a/src/site/markdown/javadoc.md b/src/site/markdown/javadoc.md deleted file mode 100644 index 00cd556e214..00000000000 --- a/src/site/markdown/javadoc.md +++ /dev/null @@ -1,49 +0,0 @@ - - - -# Log4j 2 Javadoc API Documentation and TLD Documentation - -## Javadoc API Documentation - -The table below contains links to the Javadoc API Documentation for the components you are most likely to use -directly in code. You can also use the menu links on the left. - -Component | Description ---------- | ----------- -[Log4j 2 API](log4j-api/apidocs/index.html) | The interface that applications should use and code against. -[Implementation](log4j-core/apidocs/index.html) | The standard implementation, also called the Log4j 2 Core, that contains Appenders, Filters, and more. -[Log4j IO Streams](log4j-iostreams/apidocs/index.html) | Extra classes for dealing with older APIs that expect classes from `java.io` for logging. -[JSP Tag Library](log4j-taglib/apidocs/index.html) | The tag library that enables Java-free logging in JavaServer Pages™ using Log4j 2. -[JSP Tag Library (TLD Doc)](log4j-taglib/tlddoc/index.html) | The special Javadoc-like Tag Library Documentation for the Log4j 2 JSP Tag Library. - -The table below contains links to the Javadoc API Documentation for all the other Log4j 2 components, which you -likely will not use directly in code but instead will only configure or include in your dependencies. - -Component | Description ---------- | ----------- -[Commons Logging Bridge](log4j-jcl/apidocs/index.html) | A bridge that permits applications written against the Apache Commons Logging API to log using Log4j 2. -[SLF4J Binding](log4j-slf4j-impl/apidocs/index.html) | A bridge that permits applications written against the SLF4J API to log using Log4j 2. -[Java Util Logging Adapter](log4j-jul/apidocs/index.html) | A bridge that permits applications written against the `java.util.logging` API to log using Log4j 2. -[Log4j 1.2 API Bridge](log4j-1.2-api/apidocs/index.html) | A bridge that permits applications written against the Log4j 1.2.x API to log using Log4j 2. -[Log4j 2 to SLF4J Adapter](log4j-to-slf4j/apidocs/index.html) | An adapter that permits applications written against the Log4j 2 API to log using SLF4J. -[Apache Flume Appender](log4j-flume-ng/apidocs/index.html) | An Appender that allows applications to send logging events to Apache Flume Agents. -[Log4j JMX GUI](log4j-jmx-gui/apidocs/index.html) | A Java Swing-based client for remotely viewing the status logger and editing the Log4j configuration. -[Log4j Web Application Support](log4j-web/apidocs/index.html) | Additional classes that enable multiple configurations within a Servlet Container. -[Log4j CouchDB Support](log4j-couchdb/apidocs/index.html) | Additional Appender for CouchDB. -[Log4j MongoDB Support](log4j-mongodb/apidocs/index.html) | Additional Appender for MongoDB. -[Log4j Cassandra Support](log4j-cassandra/apidocs/index.html) | Additional Appender for Cassandra. diff --git a/src/site/markdown/manual/cloud.md b/src/site/markdown/manual/cloud.md new file mode 100644 index 00000000000..b5c178d7989 --- /dev/null +++ b/src/site/markdown/manual/cloud.md @@ -0,0 +1,568 @@ + + + +# Using Log4j in Cloud Enabled Applications + +## The Twelve-Factor Application + +The Logging Guidelines for [The Twelve-Factor App](https://12factor.net/logs) state that all logs should be routed +unbuffered to stdout. Since this is the least common denominator it is guaranteed to work for all applications. However, +as with any set of general guidelines, choosing the least common denominator approach comes at a cost. Some of the costs +in Java applications include: + +1. Java stack traces are multi-line log messages. The standard docker log driver cannot handle these properly. See +[Docker Issue #22920](https://github.com/moby/moby/issues/22920) which was closed with the message "Don't Care". +Solutions for this are to: + a. Use a docker log driver that does support multi-line log message, + b. Use a logging format that does not produce multi-line messages, + c. Log from Log4j directly to a logging forwarder or aggregator and bypass the docker logging driver. +1. When logging to stdout in Docker, log events pass through Java's standard output handling which is then directed +to the operating system so that the output can be piped into a file. The overhead of all this is measurably slower +than just writing directly to a file as can be seen in these benchmark results where logging +to stdout is 16-20 times slower over repeated runs than logging directly to the file. The results below were obtained by +running the [Output Benchmark](https://github.com/apache/logging-log4j2/blob/release-2.x/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/OutputBenchmark.java) +on a 2018 MacBook Pro with a 2.9GHz Intel Core i9 processor and a 1TB SSD. However, these results alone would not be +enough to argue against writing to the standard output stream as they only amount to about 14-25 microseconds +per logging call vs 1.5 microseconds when writing to the file. + ``` + Benchmark Mode Cnt Score Error Units + OutputBenchmark.console thrpt 20 39291.885 ± 3370.066 ops/s + OutputBenchmark.file thrpt 20 654584.309 ± 59399.092 ops/s + OutputBenchmark.redirect thrpt 20 70284.576 ± 7452.167 ops/s + ``` +1. When performing audit logging using a framework such as log4j-audit guaranteed delivery of the audit events +is required. Many of the options for writing the output, including writing to the standard output stream, do +not guarantee delivery. In these cases the event must be delivered to a "forwarder" that acknowledges receipt +only when it has placed the event in durable storage, such as what [Apache Flume](https://flume.apache.org/) +or [Apache Kafka](https://kafka.apache.org/) will do. + +## Logging Approaches + +All the solutions discussed on this page are predicated with the idea that log files cannot permanently +reside on the file system and that all log events should be routed to one or more log analysis tools that will +be used for reporting and alerting. There are many ways to forward and collect events to be sent to the +log analysis tools. + +Note that any approach that bypasses Docker's logging drivers requires Log4j's +[Docker Lookup](lookups.html#DockerLookup) to allow Docker attributes to be injected into the log events. + +### Logging to the Standard Output Stream + +As discussed above, this is the recommended 12-Factor approach for applications running in a docker container. +The Log4j team does not recommend this approach for performance reasons. + +![Stdout](../images/DockerStdout.png "Application Logging to the Standard Output Stream") + +### Logging to the Standard Output Stream with the Docker Fluentd Logging Driver + +Docker provides alternate [logging drivers](https://docs.docker.com/config/containers/logging/configure/), +such as [gelf](https://docs.docker.com/config/containers/logging/gelf/) or +[fluentd](https://docs.docker.com/config/containers/logging/fluentd/), that +can be used to redirect the standard output stream to a log forwarder or log aggregator. + +When routing to a log forwarder it is expected that the forwarder will have the same lifetime as the +application. If the forwarder should fail the management tools would be expected to also terminate +other containers dependent on the forwarder. + +![Docker Fluentbit](../images/DockerFluentd.png "Logging via StdOut using the Docker Fluentd Logging Driver to Fluent-bit") + +As an alternative the logging drivers could be configured to route events directly to a logging aggregator. +This is generally not a good idea as the logging drivers only allow a single host and port to be configured. +The docker documentation isn't clear but infers that log events will be dropped when log events cannot be +delivered so this method should not be used if a highly available solution is required. + +![Docker Fluentd](../images/DockerFluentdAggregator.png "Logging via StdOut using the Docker Fluentd Logging Driver to Fluentd") + +### Logging to a File + +While this is not the recommended 12-Factor approach, it performs very well. However, it requires that the +application declares a volume where the log files will reside and then configures the log forwarder to tail +those files. Care must also be taken to automatically manage the disk space used for the logs, which Log4j +can perform via the "Delete" action on the [RollingFileAppender](appenders.html#RollingFileAppender). + +![File](../images/DockerLogFile.png "Logging to a File") + +### Sending Directly to a Log Forwarder via TCP + +Sending logs directly to a Log Forwarder is simple as it generally just requires that the forwarder's +host and port be configured on a SocketAppender with an appropriate layout. + +![TCP](../images/DockerTCP.png "Application Logging to a Forwarder via TCP") + +### Sending Directly to a Log Aggregator via TCP + +Similar to sending logs to a forwarder, logs can also be sent to a cluster of aggregators. However, +setting this up is not as simple since, to be highly available, a cluster of aggregators must be used. +However, the SocketAppender currently can only be configured with a single host and port. To allow +for failover if the primary aggregator fails the SocketAppender must be enclosed in a +[FailoverAppender](appenders.html#FailoverAppender), +which would also have the secondary aggregator configured. Another option is to have the SocketAppender +point to a highly available proxy that can forward to the Log Aggregator. + +If the log aggregator used is Apache Flume or Apache Kafka (or similar) the Appenders for these support +being configured with a list of hosts and ports so high availability is not an issue. + +![Aggregator](../images/LoggerAggregator.png "Application Logging to an Aggregator via TCP") + +## Logging using Elasticsearch, Logstash, and Kibana + +There are various approaches with different trade-offs for ingesting logs into +an ELK stack. Here we will briefly cover how one can forward Log4j generated +events first to Logstash and then to Elasticsearch. + +### Log4j Configuration + +### JsonTemplateLayout +Log4j provides a multitude of JSON generating layouts. In particular, [JSON +Template Layout](layouts.html#JSONTemplateLayout) allows full schema +customization and bundles ELK-specific layouts by default, which makes it a +great fit for the bill. Using the EcsLayout template as shown below will generate data in Kibana where +the message displayed exactly matches the message passed to Log4j and most of the event attributes, including +any exceptions, are present as individual attributes that can be displayed. Note, however that stack traces +will be formatted without newlines. + + + + + + + + + + + + + + + + + + + + + + +#### Gelft Template + +The JsonTemplateLayout can also be used to generate JSON that matches the GELF specification which can format +the message attribute using a pattern in accordance with the PatternLayout. For example, the following +template, named EnhancedGelf.json, can be used to generate GELF-compliant data that can be passed to Logstash. +With this template the message attribute will include the thread id, level, specific ThreadContext attributes, +the class name, method name, and line number as well as the message. If an exception is included it will also +be included with newlines. This format follows very closely what you would see in a typical log file on disk +using the PatternLayout but has the additional advantage of including the attributes as separate fields that +can be queried. + + { + "version": "1.1", + "host": "${hostName}", + "short_message": { + "$resolver": "message", + "stringified": true + }, + "full_message": { + "$resolver": "message", + "pattern": "[%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m", + "stringified": true + }, + "timestamp": { + "$resolver": "timestamp", + "epoch": { + "unit": "secs" + } + }, + "level": { + "$resolver": "level", + "field": "severity", + "severity": { + "field": "code" + } + }, + "_logger": { + "$resolver": "logger", + "field": "name" + }, + "_thread": { + "$resolver": "thread", + "field": "name" + }, + "_mdc": { + "$resolver": "mdc", + "flatten": { + "prefix": "_" + }, + "stringified": true + } + } + +The logging configuration to use this template would be + + + + + + + + + + + + + + + + + + + + + + +The significant difference with this configuration from the first example is that it references the +custom template and it specifies an event delimiter of a null character ('\0'); + +**Note**: The level being passed with the above template does not strictly conform to the GELF spec as the +Level being passed is the Log4j Level NOT the Level defined in the GELF spec. However, testing has shown +that Logstash, Elk, and Kibana are pretty tolerant of whatever data is passed to it. + +#### Custom Template + +Another option is to use a custom template, possibly based on one of the standard templates. The template +below is loosely based on ECS but a) adds the spring boot application name, b) formats the message +using PatternLayout, formats Map Messages as event.data attributes while setting the event action based on +any Marker included in the event, includes all the ThreadContext attributes. + +**Note**: The Json Template Layout escapes control sequences so messages that contain '\n' will have those +control sequences copied as "\n" into the text rather than converted to a newline character. This bypasses +many problems that occur with Log Forwarders such as Filebeat and FluentBit/Fluentd. Kibana will correctly +interpret these squences as newlines and display them correctly. Also note that the message pattern does +not contain a timestamp. Kibana will display the timestamp field in its own column so placing it in the +message would be redundant. + + { + "@timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC" + } + }, + "ecs.version": "1.11.0", + "log.level": { + "$resolver": "level", + "field": "name" + }, + "application": "\${lower:\${spring:spring.application.name}}", + "short_message": { + "$resolver": "message", + "stringified": true + }, + "message": { + "$resolver": "pattern", + "pattern": "[%t] %X{requestId, sessionId, loginId, userId, ipAddress, accountNumber} %C{1.}.%M:%L - %m%n" + }, + "process.thread.name": { + "$resolver": "thread", + "field": "name" + }, + "log.logger": { + "$resolver": "logger", + "field": "name" + }, + "event.action": { + "$resolver": "marker", + "field": "name" + }, + "event.data": { + "$resolver": "map", + "stringified": true + }, + "labels": { + "$resolver": "mdc", + "flatten": true, + "stringified": true + }, + "tags": { + "$resolver": "ndc" + }, + "error.type": { + "$resolver": "exception", + "field": "className" + }, + "error.message": { + "$resolver": "exception", + "field": "message" + }, + "error.stack_trace": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } + } + + +Finally, the GelfLayout can be used to generate GELF compliant output. Unlike the JsonTemplateLayout it +adheres closely to the GELF spec. + + + + requestId,sessionId,loginId,userId,ipAddress,callingHost + %d [%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress} %C{1.}.%M:%L - %m%n + + + + + + + + + + + + + + + + + + + +#### Logstash Configuration with Gelf + +We will configure Logstash to listen on TCP port 12345 for payloads of type JSON +and then forward these to (either console and/or) an Elasticsearch server. + + input { + tcp { + port => 12345 + codec => "json" + } + } + + output { + + # (Un)comment for debugging purposes. + # stdout { codec => rubydebug } + + # Modify the hosts value to reflect where elasticsearch is installed. + elasticsearch { + hosts => ["http://localhost:9200/"] + index => "app-%{application}-%{+YYYY.MM.dd}" + } + + } + +#### Logstash Configuration with JsonTemplateLayout + +When one of the GELF compliant formats is used Logstash should be configured as + + gelf { + host => "localhost" + use_tcp => true + use_udp => false + port => 12222 + type => "gelf" + } + } + + filter { + # These are GELF/Syslog logging levels as defined in RFC 3164. Map the integer level to its human readable format. + translate { + field => "[level]" + destination => "[levelName]" + dictionary => { + "0" => "EMERG" + "1" => "ALERT" + "2" => "CRITICAL" + "3" => "ERROR" + "4" => "WARN" + "5" => "NOTICE" + "6" => "INFO" + "7" => "DEBUG" + } + } + } + + output { + # (Un)comment for debugging purposes + # stdout { codec => rubydebug } + # Modify the hosts value to reflect where elasticsearch is installed. + elasticsearch { + hosts => ["http://localhost:9200/"] + index => "app-%{application}-%{+YYYY.MM.dd}" + } + } +#### Filebeat configuration with JsonTemplateLayout + +When using a JsonTemplateLayout that complies with ECS (or is similar to the custom template previously shown) +the configuration of filebeat is straightforward. + + filebeat.inputs: + - type: log + enabled: true + json.keys_under_root: true + paths: + - /var/log/apps/*.log + + +### Kibana +Using the EnhancedGelf template, the GelfLayout or the custom template the above configurations the message +field will contain a fully formatted log event just as it would appear in a file Appender. The ThreadContext +attributes, custome fields, thread name, etc. will all be available as attributes on each log event that can +be used for filtering. The result will resemble +![](../images/kibana.png) + +## Managing Logging Configuration + +Spring Boot provides another least common denominator approach to logging configuration. It will let you set the +log level for various Loggers within an application which can be dynamically updated via REST endpoints provided +by Spring. While this works in a lot of cases it does not support any of the more advanced filtering features of +Log4j. For example, since it cannot add or modify any Filters other than the log level of a logger, changes cannot be made to allow +all log events for a specific user or customer to temporarily be logged +(see [DynamicThresholdFilter](filters.html#DynamicThresholdFilter) or +[ThreadContextMapFilter](filters.html#ThreadContextMapFilter)) or any other kinds of changes to filters. +Also, in a micro-services, clustered environment it is quite likely that these changes will need to be propagated +to multiple servers at the same time. Trying to achieve this via REST calls could be difficult. + +Since its first release Log4j has supported reconfiguration through a file. +Beginning with Log4j 2.12.0 Log4j also supports accessing the configuration via HTTP(S) and monitoring the file +for changes by using the HTTP "If-Modified-Since" header. A patch has also been integrated into Spring Cloud Config +starting with versions 2.0.3 and 2.1.1 for it to honor the If-Modified-Since header. In addition, the +log4j-spring-cloud-config project will listen for update events published by Spring Cloud Bus and then verify +that the configuration file has been modified, so polling via HTTP is not required. + +Log4j also supports composite configurations. A distributed application spread across microservices could +share a common configuration file that could be used to control things like enabling debug logging for a +specific user. + +While the standard Spring Boot REST endpoints to update logging will still work any changes made by those +REST endpoints will be lost if Log4j reconfigures itself do to changes in the logging configuration file. + +Further information regarding integration of the log4j-spring-cloud-config-client can be found at +[Log4j Spring Cloud Config Client](../log4j-spring-cloud-config/log4j-spring-cloud-config-client/index.html). + +## Integration with Spring Boot + +Log4j integrates with Spring Boot in 2 ways: + +1. A Spring Lookup can be used to access the Spring application configuration from Log4j configuration files. +2. Log4j will access the Spring configuration when it is trying to resolve log4j system properties. + +Both of these require that the log4j-spring-cloud-client jar is included in the application. + +## Integration with Docker + +Applications within a Docker container that log using a Docker logging driver can include special +attributes in the formatted log event as described at +[Customize Log Driver Output](https://docs.docker.com/config/containers/logging/log_tags/). Log4j +provides similar functionality via the [Docker Lookup](lookups.html#DockerLookup). More information on +Log4j's Docker support may also be found at [Log4j-Docker](../log4j-docker/index.html). + +## Integration with Kubernetes + +Applications managed by Kubernetes can bypass the Docker/Kubernetes logging infrastructure and log directly to +either a sidecar forwarder or a logging aggragator cluster while still including all the kubernetes +attributes by using the Log4j 2 [Kubernetes Lookup](lookups.html#KubernetesLookup). More information on +Log4j's Kubernetes support may also be found at [Log4j-Kubernetes](../log4j-kubernetes/index.html). + +## Appender Performance +The numbers in the table below represent how much time in seconds was required for the application to +call `logger.debug(...)` 100,000 times. These numbers only include the time taken to deliver to the specifically +noted endpoint and many not include the actual time required before they are available for viewing. All +measurements were performed on a MacBook Pro with a 2.9GHz Intel Core I9 processor with 6 physical and 12 +logical cores, 32GB of 2400 MHz DDR4 RAM, and 1TB of Apple SSD storage. The VM used by Docker was managed +by VMWare Fusion and had 4 CPUs and 2 GB of RAM. These number should be used for relative performance comparisons +as the results on another system may vary considerably. + +The sample application used can be found under the log4j-spring-cloud-config/log4j-spring-cloud-config-samples +directory in the Log4j [source repository](https://github.com/apache/logging-log4j2). + +| Test | 1 Thread | 2 Threads | 4 Threads | 8 Threads | +|------------------------ |---------:|----------:|----------:|----------:| +|Flume Avro ||||| +|- Batch Size 1 - JSON |49.11 |46.54 |46.70 |44.92 | +|- Batch Size 1 - RFC5424 |48.30 |45.79 |46.31 |45.50 | +|- Batch Size 100 - JSON | 6.33 |3.87 |3.57 |3.84 | +|- Batch Size 100 - RFC5424 | 6.08 |3.69 |3.22 |3.11 | +|- Batch Size 1000 - JSON | 4.83 |3.20 |3.02 |2.11 | +|- Batch Size 1000 - RFC5424 | 4.70 |2.40 |2.37 |2.37 | +|Flume Embedded ||||| +| - RFC5424 |3.58 |2.10 |2.10 |2.70 | +| - JSON |4.20 |2.49 |3.53 |2.90 | +|Kafka Local JSON ||||| +| - sendSync true |58.46 |38.55 |19.59 |19.01 | +| - sendSync false |9.8 |10.8 |12.23 |11.36 | +|Console||||| +| - JSON / Kubernetes |3.03 |3.11 |3.04 |2.51 | +| - JSON |2.80 |2.74 |2.54 |2.35 | +| - Docker fluentd driver |10.65 |9.92 |10.42 |10.27 | +|Rolling File||||| +| - RFC5424 |1.65 |0.94 |1.22 |1.55 +| - JSON |1.90 |0.95 |1.57 |1.94 | +|TCP - Fluent Bit - JSON |2.34 |2.167 |1.67 |2.50 | +|Async Logger||||| +|- TCP - Fluent Bit - JSON|0.90 |0.58 |0.36 |0.48 | +|- Console - JSON |0.83 |0.57 |0.55 |0.61 | +|- Flume Avro - 1000 - JSON|0.76 |0.37 |0.45 |0.68 | + +Notes: + +1. Flume Avro - Buffering is controlled by the batch size. Each send is complete when the remote +acknowledges the batch was written to its channel. These number seem to indicate Flume Avro could +benefit from using a pool of RPCClients, at least for a batchSize of 1. +1. Flume Embedded - This is essentially asynchronous as it writes to an in-memory buffer. It is +unclear why the performance isn't closer to the AsyncLogger results. +1. Kafka was run in standalone mode on the same laptop as the application. See sendSync set to true +requires waiting for an ack from Kafka for each log event. +1. Console - System.out is redirected to a file by Docker. Testing shows that it would be much +slower if it was writing to the terminal screen. +1. Rolling File - Test uses the default buffer size of 8K. +1. TCP to Fluent Bit - The Socket Appender uses a default buffer size of 8K. +1. Async Loggers - These all write to a circular buffer and return to the application. The actual +I/O will take place on a separate thread. If writing the events is performed more slowly than +events are being created eventually the buffer will fill up and logging will be performed at +the same pace that log events are written. + +## Logging Recommendations + +1. Use asynchronous logging unless guaranteed delivery is absolutely required. As +the performance numbers show, so long as the volume of logging is not high enough to fill up the +circular buffer the overhead of logging will almost be unnoticeable to the application. +1. If overall performance is a consideration or you require multiline events such as stack traces +be processed properly then log via TCP to a companion container that acts as a log forwarder or directly +to a log aggregator as shown above in [Logging with ELK](#ELK). Use the +Log4j Docker Lookup to add the container information to each log event. +1. Whenever guaranteed delivery is required use Flume Avro with a batch size of 1 or another Appender such +as the Kafka Appender with syncSend set to true that only return control after the downstream agent +acknowledges receipt of the event. Beware that using an Appender that writes each event individually should +be kept to a minimum since it is much slower than sending buffered events. +1. Logging to files within the container is discouraged. Doing so requires that a volume be declared in +the Docker configuration and that the file be tailed by a log forwarder. However, it performs +better than logging to the standard output stream. If logging via TCP is not an option and +proper multiline handling is required then consider this option. diff --git a/src/site/markdown/manual/compatibility.md b/src/site/markdown/manual/compatibility.md new file mode 100644 index 00000000000..69a1b301380 --- /dev/null +++ b/src/site/markdown/manual/compatibility.md @@ -0,0 +1,94 @@ + + + +# Log4j 2 Compatibility with Log4j 1 + +## API Compatibility + +Log4j 2 provides support for the Log4j 1 logging methods by providing alternate implementations +of the classes containing those methods. These classes may be found in the log4j-1.2-api jar +distributed with the project. All calls to perform logging will result in the data passed to the logging methods +to be forwarded to the Log4j2 API where they can be processed by implementations of the Log4j 2 API. + +## Configuration Compatibility + +Log4j 2 provides experimental support for Log4j 1 configuration files. Configuration of the Appenders, Layouts +and Filters that were provided in the Log4j 1 distribution will be redirected to their Log4j 2 counterparts - +with the exception of the implemented Rewrite Policies. This means that although the while the behavior of these +components will be similar they may not be exactly the same. For example, the XML generated by the XMLLayout may +not exactly match the XML generated by the Log4j 1XMLLayout. + +In addition, Log4j 2 supports custom Log4j 1 Appenders, Filters, and Layouts with some constraints. Since the +original Log4j 1 components may not be present in Log4j 2, custom components that extend them will fail. + +As support for Log4j 1 is an experimental feature one of the following steps must be taken to enable it: + +1. Set the system property "log4j1.compatibility" to a value of "true". Log4j 2 will then add log4j.properties, +log4j-test.properties, log4j.xml and log4j-test.xml to the configuration files it searches for on the class path. +1. Set the Log4j 1 system property "log4j.configuration" to the location of the log4j 1 configuration file. The +files must have a file extension of either ".properties" or ".xml". + +## Supported Components +### Appenders + +* AsyncAppender +* ConsoleAppender +* DailyRollingFileAppender +* FileAppender +* NullAppender +* RewriteAppender (limited) +* RollingFileAppender +* SyslogAppender + +## Filters + +* DenyAllFilter +* LevelMatchFilter +* LevelRangeFilter +* StringMatchFilter + +## Layouts + +* HtmlLayout +* PatternLayout +* SimpleLayout +* TTCCLayout +* XmlLayout + +## Rewrite Policies + +* MapRewritePolicy +* PropertyRewritePolicy + +## Unsupported or Unimplemented Components +### Appenders + +* JDBCAppender (cannot be mapped to Log4j 2's JdbcAppender) +* JMSAppender +* SMTPAppender +* SocketAppender (Requires the use of the SerializedLayout which is a security risk) +* SocketHubAppender (Requires the use of the SerializedLayout which is a securiy risk) +* TelnetAppender (Security risk) + +## Rewrite Policies + +* ReflectionRewritePolicy +* Custom rewrite policies since LoggingEvent is currently a no-op. + +### Renderers +Log4j 2 currently will ignore renderers. \ No newline at end of file diff --git a/src/site/markdown/maven-artifacts.md.vm b/src/site/markdown/maven-artifacts.md.vm index 6780dd8d493..aed9577ab56 100644 --- a/src/site/markdown/maven-artifacts.md.vm +++ b/src/site/markdown/maven-artifacts.md.vm @@ -59,7 +59,7 @@ ``` dependencies { #foreach($artifactId in $artifactIds) - compile group: 'org.apache.logging.log4j', name: '${artifactId}', version: '${version}' + implementation 'org.apache.logging.log4j:${artifactId}:${version}' #end } ``` @@ -166,8 +166,8 @@ dependencyManagement { } dependencies { - compile 'org.apache.logging.log4j:log4j-api' - compile 'org.apache.logging.log4j:log4j-core' + implementation 'org.apache.logging.log4j:log4j-api' + implementation 'org.apache.logging.log4j:log4j-core' // etc. } ``` diff --git a/src/site/resources/css/bootstrap.css b/src/site/resources/css/bootstrap.css deleted file mode 100644 index 3ef47e192e7..00000000000 --- a/src/site/resources/css/bootstrap.css +++ /dev/null @@ -1,5893 +0,0 @@ -/*! - * Bootstrap v2.2.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -nav, -section { - display: block; -} - -audio, -canvas, -video { - display: inline-block; - *display: inline; - *zoom: 1; -} - -audio:not([controls]) { - display: none; -} - -html { - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -a:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -a:hover, -a:active { - outline: 0; -} - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -img { - width: auto\9; - height: auto; - max-width: 100%; - vertical-align: middle; - border: 0; - -ms-interpolation-mode: bicubic; -} - -#map_canvas img, -.google-maps img { - max-width: none; -} - -button, -input, -select, -textarea { - margin: 0; - font-size: 100%; - vertical-align: middle; -} - -button, -input { - *overflow: visible; - line-height: normal; -} - -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} - -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; -} - -input[type="search"] { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; - -webkit-appearance: textfield; -} - -input[type="search"]::-webkit-search-decoration, -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; -} - -textarea { - overflow: auto; - vertical-align: top; -} - -.clearfix { - *zoom: 1; -} - -.clearfix:before, -.clearfix:after { - display: table; - line-height: 0; - content: ""; -} - -.clearfix:after { - clear: both; -} - -.hide-text { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - -.input-block-level { - display: block; - width: 100%; - min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -body { - margin: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 20px; - color: #333333; - background-color: #ffffff; -} - -a { - color: #0088cc; - text-decoration: none; -} - -a:hover { - color: #005580; - text-decoration: underline; -} - -.img-rounded { - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.img-polaroid { - padding: 4px; - background-color: #fff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.img-circle { - -webkit-border-radius: 500px; - -moz-border-radius: 500px; - border-radius: 500px; -} - -.row { - margin-left: -20px; - *zoom: 1; -} - -.row:before, -.row:after { - display: table; - line-height: 0; - content: ""; -} - -.row:after { - clear: both; -} - -[class*="span"] { - float: left; - min-height: 1px; - margin-left: 20px; -} - -.container, -.navbar-static-top .container, -.navbar-fixed-top .container, -.navbar-fixed-bottom .container { - width: 940px; -} - -.span12 { - width: 940px; -} - -.span11 { - width: 860px; -} - -.span10 { - width: 780px; -} - -.span9 { - width: 700px; -} - -.span8 { - width: 620px; -} - -.span7 { - width: 540px; -} - -.span6 { - width: 460px; -} - -.span5 { - width: 380px; -} - -.span4 { - width: 300px; -} - -.span3 { - width: 220px; -} - -.span2 { - width: 140px; -} - -.span1 { - width: 60px; -} - -.offset12 { - margin-left: 980px; -} - -.offset11 { - margin-left: 900px; -} - -.offset10 { - margin-left: 820px; -} - -.offset9 { - margin-left: 740px; -} - -.offset8 { - margin-left: 660px; -} - -.offset7 { - margin-left: 580px; -} - -.offset6 { - margin-left: 500px; -} - -.offset5 { - margin-left: 420px; -} - -.offset4 { - margin-left: 340px; -} - -.offset3 { - margin-left: 260px; -} - -.offset2 { - margin-left: 180px; -} - -.offset1 { - margin-left: 100px; -} - -.row-fluid { - width: 100%; - *zoom: 1; -} - -.row-fluid:before, -.row-fluid:after { - display: table; - line-height: 0; - content: ""; -} - -.row-fluid:after { - clear: both; -} - -.row-fluid [class*="span"] { - display: block; - float: left; - width: 100%; - min-height: 30px; - margin-left: 2.127659574468085%; - *margin-left: 2.074468085106383%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.row-fluid [class*="span"]:first-child { - margin-left: 0; -} - -.row-fluid .controls-row [class*="span"] + [class*="span"] { - margin-left: 2.127659574468085%; -} - -.row-fluid .span12 { - width: 100%; - *width: 99.94680851063829%; -} - -.row-fluid .span11 { - width: 91.48936170212765%; - *width: 91.43617021276594%; -} - -.row-fluid .span10 { - width: 82.97872340425532%; - *width: 82.92553191489361%; -} - -.row-fluid .span9 { - width: 74.46808510638297%; - *width: 74.41489361702126%; -} - -.row-fluid .span8 { - width: 65.95744680851064%; - *width: 65.90425531914893%; -} - -.row-fluid .span7 { - width: 57.44680851063829%; - *width: 57.39361702127659%; -} - -.row-fluid .span6 { - width: 48.93617021276595%; - *width: 48.88297872340425%; -} - -.row-fluid .span5 { - width: 40.42553191489362%; - *width: 40.37234042553192%; -} - -.row-fluid .span4 { - width: 31.914893617021278%; - *width: 31.861702127659576%; -} - -.row-fluid .span3 { - width: 23.404255319148934%; - *width: 23.351063829787233%; -} - -.row-fluid .span2 { - width: 14.893617021276595%; - *width: 14.840425531914894%; -} - -.row-fluid .span1 { - width: 6.382978723404255%; - *width: 6.329787234042553%; -} - -.row-fluid .offset12 { - margin-left: 104.25531914893617%; - *margin-left: 104.14893617021275%; -} - -.row-fluid .offset12:first-child { - margin-left: 102.12765957446808%; - *margin-left: 102.02127659574467%; -} - -.row-fluid .offset11 { - margin-left: 95.74468085106382%; - *margin-left: 95.6382978723404%; -} - -.row-fluid .offset11:first-child { - margin-left: 93.61702127659574%; - *margin-left: 93.51063829787232%; -} - -.row-fluid .offset10 { - margin-left: 87.23404255319149%; - *margin-left: 87.12765957446807%; -} - -.row-fluid .offset10:first-child { - margin-left: 85.1063829787234%; - *margin-left: 84.99999999999999%; -} - -.row-fluid .offset9 { - margin-left: 78.72340425531914%; - *margin-left: 78.61702127659572%; -} - -.row-fluid .offset9:first-child { - margin-left: 76.59574468085106%; - *margin-left: 76.48936170212764%; -} - -.row-fluid .offset8 { - margin-left: 70.2127659574468%; - *margin-left: 70.10638297872339%; -} - -.row-fluid .offset8:first-child { - margin-left: 68.08510638297872%; - *margin-left: 67.9787234042553%; -} - -.row-fluid .offset7 { - margin-left: 61.70212765957446%; - *margin-left: 61.59574468085106%; -} - -.row-fluid .offset7:first-child { - margin-left: 59.574468085106375%; - *margin-left: 59.46808510638297%; -} - -.row-fluid .offset6 { - margin-left: 53.191489361702125%; - *margin-left: 53.085106382978715%; -} - -.row-fluid .offset6:first-child { - margin-left: 51.063829787234035%; - *margin-left: 50.95744680851063%; -} - -.row-fluid .offset5 { - margin-left: 44.68085106382979%; - *margin-left: 44.57446808510638%; -} - -.row-fluid .offset5:first-child { - margin-left: 42.5531914893617%; - *margin-left: 42.4468085106383%; -} - -.row-fluid .offset4 { - margin-left: 36.170212765957444%; - *margin-left: 36.06382978723405%; -} - -.row-fluid .offset4:first-child { - margin-left: 34.04255319148936%; - *margin-left: 33.93617021276596%; -} - -.row-fluid .offset3 { - margin-left: 27.659574468085104%; - *margin-left: 27.5531914893617%; -} - -.row-fluid .offset3:first-child { - margin-left: 25.53191489361702%; - *margin-left: 25.425531914893618%; -} - -.row-fluid .offset2 { - margin-left: 19.148936170212764%; - *margin-left: 19.04255319148936%; -} - -.row-fluid .offset2:first-child { - margin-left: 17.02127659574468%; - *margin-left: 16.914893617021278%; -} - -.row-fluid .offset1 { - margin-left: 10.638297872340425%; - *margin-left: 10.53191489361702%; -} - -.row-fluid .offset1:first-child { - margin-left: 8.51063829787234%; - *margin-left: 8.404255319148938%; -} - -[class*="span"].hide, -.row-fluid [class*="span"].hide { - display: none; -} - -[class*="span"].pull-right, -.row-fluid [class*="span"].pull-right { - float: right; -} - -.container { - margin-right: auto; - margin-left: auto; - *zoom: 1; -} - -.container:before, -.container:after { - display: table; - line-height: 0; - content: ""; -} - -.container:after { - clear: both; -} - -.container-fluid { - padding-right: 20px; - padding-left: 20px; - *zoom: 1; -} - -.container-fluid:before, -.container-fluid:after { - display: table; - line-height: 0; - content: ""; -} - -.container-fluid:after { - clear: both; -} - -p { - margin: 0 0 10px; -} - -.lead { - margin-bottom: 20px; - font-size: 21px; - font-weight: 200; - line-height: 30px; -} - -small { - font-size: 85%; -} - -strong { - font-weight: bold; -} - -em { - font-style: italic; -} - -cite { - font-style: normal; -} - -.muted { - color: #999999; -} - -.text-warning { - color: #c09853; -} - -a.text-warning:hover { - color: #a47e3c; -} - -.text-error { - color: #b94a48; -} - -a.text-error:hover { - color: #953b39; -} - -.text-info { - color: #3a87ad; -} - -a.text-info:hover { - color: #2d6987; -} - -.text-success { - color: #468847; -} - -a.text-success:hover { - color: #356635; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - margin: 10px 0; - font-family: inherit; - font-weight: bold; - line-height: 20px; - color: inherit; - text-rendering: optimizelegibility; -} - -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small { - font-weight: normal; - line-height: 1; - color: #999999; -} - -h1, -h2, -h3 { - line-height: 40px; -} - -h1 { - font-size: 38.5px; -} - -h2 { - font-size: 31.5px; -} - -h3 { - font-size: 24.5px; -} - -h4 { - font-size: 17.5px; -} - -h5 { - font-size: 14px; -} - -h6 { - font-size: 11.9px; -} - -h1 small { - font-size: 24.5px; -} - -h2 small { - font-size: 17.5px; -} - -h3 small { - font-size: 14px; -} - -h4 small { - font-size: 14px; -} - -.page-header { - padding-bottom: 9px; - margin: 20px 0 30px; - border-bottom: 1px solid #eeeeee; -} - -ul, -ol { - padding: 0; - margin: 0 0 10px 25px; -} - -ul ul, -ul ol, -ol ol, -ol ul { - margin-bottom: 0; -} - -li { - line-height: 20px; -} - -ul.unstyled, -ol.unstyled { - margin-left: 0; - list-style: none; -} - -dl { - margin-bottom: 20px; -} - -dt, -dd { - line-height: 20px; -} - -dt { - font-weight: bold; -} - -dd { - margin-left: 10px; -} - -.dl-horizontal { - *zoom: 1; -} - -.dl-horizontal:before, -.dl-horizontal:after { - display: table; - line-height: 0; - content: ""; -} - -.dl-horizontal:after { - clear: both; -} - -.dl-horizontal dt { - float: left; - width: 160px; - overflow: hidden; - clear: left; - text-align: right; - text-overflow: ellipsis; - white-space: nowrap; -} - -.dl-horizontal dd { - margin-left: 180px; -} - -hr { - margin: 20px 0; - border: 0; - border-top: 1px solid #eeeeee; - border-bottom: 1px solid #ffffff; -} - -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #999999; -} - -abbr.initialism { - font-size: 90%; - text-transform: uppercase; -} - -blockquote { - padding: 0 0 0 15px; - margin: 0 0 20px; - border-left: 5px solid #eeeeee; -} - -blockquote p { - margin-bottom: 0; - font-size: 16px; - font-weight: 300; - line-height: 25px; -} - -blockquote small { - display: block; - line-height: 20px; - color: #999999; -} - -blockquote small:before { - content: '\2014 \00A0'; -} - -blockquote.pull-right { - float: right; - padding-right: 15px; - padding-left: 0; - border-right: 5px solid #eeeeee; - border-left: 0; -} - -blockquote.pull-right p, -blockquote.pull-right small { - text-align: right; -} - -blockquote.pull-right small:before { - content: ''; -} - -blockquote.pull-right small:after { - content: '\00A0 \2014'; -} - -q:before, -q:after, -blockquote:before, -blockquote:after { - content: ""; -} - -address { - display: block; - margin-bottom: 20px; - font-style: normal; - line-height: 20px; -} - -code, -pre { - padding: 0 3px 2px; - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; - font-size: 12px; - color: #333333; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -code { - padding: 2px 4px; - color: #d14; /* FIXME: red? really? */ - background-color: #f7f7f9; - border: 1px solid #e1e1e8; -} - -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 20px; - word-break: break-all; - word-wrap: break-word; - white-space: pre; - white-space: pre-wrap; - background-color: #f5f5f5; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.15); - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -pre.prettyprint { - margin-bottom: 20px; -} - -pre code { - padding: 0; - color: inherit; - background-color: transparent; - border: 0; -} - -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} - -form { - margin: 0 0 20px; -} - -fieldset { - padding: 0; - margin: 0; - border: 0; -} - -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: 40px; - color: #333333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} - -legend small { - font-size: 15px; - color: #999999; -} - -label, -input, -button, -select, -textarea { - font-size: 14px; - font-weight: normal; - line-height: 20px; -} - -input, -button, -select, -textarea { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -label { - display: block; - margin-bottom: 5px; -} - -select, -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - display: inline-block; - height: 20px; - padding: 4px 6px; - margin-bottom: 10px; - font-size: 14px; - line-height: 20px; - color: #555555; - vertical-align: middle; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -input, -textarea, -.uneditable-input { - width: 206px; -} - -textarea { - height: auto; -} - -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - background-color: #ffffff; - border: 1px solid #cccccc; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - -o-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; -} - -textarea:focus, -input[type="text"]:focus, -input[type="password"]:focus, -input[type="datetime"]:focus, -input[type="datetime-local"]:focus, -input[type="date"]:focus, -input[type="month"]:focus, -input[type="time"]:focus, -input[type="week"]:focus, -input[type="number"]:focus, -input[type="email"]:focus, -input[type="url"]:focus, -input[type="search"]:focus, -input[type="tel"]:focus, -input[type="color"]:focus, -.uneditable-input:focus { - border-color: rgba(82, 168, 236, 0.8); - outline: 0; - outline: thin dotted \9; - /* IE6-9 */ - - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); -} - -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - *margin-top: 0; - line-height: normal; - cursor: pointer; -} - -input[type="file"], -input[type="image"], -input[type="submit"], -input[type="reset"], -input[type="button"], -input[type="radio"], -input[type="checkbox"] { - width: auto; -} - -select, -input[type="file"] { - height: 30px; - /* In IE7, the height of the select element cannot be changed by height, only font-size */ - - *margin-top: 4px; - /* For IE7, add top margin to align select with labels */ - - line-height: 30px; -} - -select { - width: 220px; - background-color: #ffffff; - border: 1px solid #cccccc; -} - -select[multiple], -select[size] { - height: auto; -} - -select:focus, -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.uneditable-input, -.uneditable-textarea { - color: #999999; - cursor: not-allowed; - background-color: #fcfcfc; - border-color: #cccccc; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); -} - -.uneditable-input { - overflow: hidden; - white-space: nowrap; -} - -.uneditable-textarea { - width: auto; - height: auto; -} - -input:-moz-placeholder, -textarea:-moz-placeholder { - color: #999999; -} - -input:-ms-input-placeholder, -textarea:-ms-input-placeholder { - color: #999999; -} - -input::-webkit-input-placeholder, -textarea::-webkit-input-placeholder { - color: #999999; -} - -.radio, -.checkbox { - min-height: 20px; - padding-left: 20px; -} - -.radio input[type="radio"], -.checkbox input[type="checkbox"] { - float: left; - margin-left: -20px; -} - -.controls > .radio:first-child, -.controls > .checkbox:first-child { - padding-top: 5px; -} - -.radio.inline, -.checkbox.inline { - display: inline-block; - padding-top: 5px; - margin-bottom: 0; - vertical-align: middle; -} - -.radio.inline + .radio.inline, -.checkbox.inline + .checkbox.inline { - margin-left: 10px; -} - -.input-mini { - width: 60px; -} - -.input-small { - width: 90px; -} - -.input-medium { - width: 150px; -} - -.input-large { - width: 210px; -} - -.input-xlarge { - width: 270px; -} - -.input-xxlarge { - width: 530px; -} - -input[class*="span"], -select[class*="span"], -textarea[class*="span"], -.uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"] { - float: none; - margin-left: 0; -} - -.input-append input[class*="span"], -.input-append .uneditable-input[class*="span"], -.input-prepend input[class*="span"], -.input-prepend .uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"], -.row-fluid .input-prepend [class*="span"], -.row-fluid .input-append [class*="span"] { - display: inline-block; -} - -input, -textarea, -.uneditable-input { - margin-left: 0; -} - -.controls-row [class*="span"] + [class*="span"] { - margin-left: 20px; -} - -input.span12, -textarea.span12, -.uneditable-input.span12 { - width: 926px; -} - -input.span11, -textarea.span11, -.uneditable-input.span11 { - width: 846px; -} - -input.span10, -textarea.span10, -.uneditable-input.span10 { - width: 766px; -} - -input.span9, -textarea.span9, -.uneditable-input.span9 { - width: 686px; -} - -input.span8, -textarea.span8, -.uneditable-input.span8 { - width: 606px; -} - -input.span7, -textarea.span7, -.uneditable-input.span7 { - width: 526px; -} - -input.span6, -textarea.span6, -.uneditable-input.span6 { - width: 446px; -} - -input.span5, -textarea.span5, -.uneditable-input.span5 { - width: 366px; -} - -input.span4, -textarea.span4, -.uneditable-input.span4 { - width: 286px; -} - -input.span3, -textarea.span3, -.uneditable-input.span3 { - width: 206px; -} - -input.span2, -textarea.span2, -.uneditable-input.span2 { - width: 126px; -} - -input.span1, -textarea.span1, -.uneditable-input.span1 { - width: 46px; -} - -.controls-row { - *zoom: 1; -} - -.controls-row:before, -.controls-row:after { - display: table; - line-height: 0; - content: ""; -} - -.controls-row:after { - clear: both; -} - -.controls-row [class*="span"], -.row-fluid .controls-row [class*="span"] { - float: left; -} - -.controls-row .checkbox[class*="span"], -.controls-row .radio[class*="span"] { - padding-top: 5px; -} - -input[disabled], -select[disabled], -textarea[disabled], -input[readonly], -select[readonly], -textarea[readonly] { - cursor: not-allowed; - background-color: #eeeeee; -} - -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"][readonly], -input[type="checkbox"][readonly] { - background-color: transparent; -} - -.control-group.warning > label, -.control-group.warning .help-block, -.control-group.warning .help-inline { - color: #c09853; -} - -.control-group.warning .checkbox, -.control-group.warning .radio, -.control-group.warning input, -.control-group.warning select, -.control-group.warning textarea { - color: #c09853; -} - -.control-group.warning input, -.control-group.warning select, -.control-group.warning textarea { - border-color: #c09853; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.warning input:focus, -.control-group.warning select:focus, -.control-group.warning textarea:focus { - border-color: #a47e3c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; -} - -.control-group.warning .input-prepend .add-on, -.control-group.warning .input-append .add-on { - color: #c09853; - background-color: #fcf8e3; - border-color: #c09853; -} - -.control-group.error > label, -.control-group.error .help-block, -.control-group.error .help-inline { - color: #b94a48; -} - -.control-group.error .checkbox, -.control-group.error .radio, -.control-group.error input, -.control-group.error select, -.control-group.error textarea { - color: #b94a48; -} - -.control-group.error input, -.control-group.error select, -.control-group.error textarea { - border-color: #b94a48; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.error input:focus, -.control-group.error select:focus, -.control-group.error textarea:focus { - border-color: #953b39; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; -} - -.control-group.error .input-prepend .add-on, -.control-group.error .input-append .add-on { - color: #b94a48; - background-color: #f2dede; - border-color: #b94a48; -} - -.control-group.success > label, -.control-group.success .help-block, -.control-group.success .help-inline { - color: #468847; -} - -.control-group.success .checkbox, -.control-group.success .radio, -.control-group.success input, -.control-group.success select, -.control-group.success textarea { - color: #468847; -} - -.control-group.success input, -.control-group.success select, -.control-group.success textarea { - border-color: #468847; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.success input:focus, -.control-group.success select:focus, -.control-group.success textarea:focus { - border-color: #356635; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; -} - -.control-group.success .input-prepend .add-on, -.control-group.success .input-append .add-on { - color: #468847; - background-color: #dff0d8; - border-color: #468847; -} - -.control-group.info > label, -.control-group.info .help-block, -.control-group.info .help-inline { - color: #3a87ad; -} - -.control-group.info .checkbox, -.control-group.info .radio, -.control-group.info input, -.control-group.info select, -.control-group.info textarea { - color: #3a87ad; -} - -.control-group.info input, -.control-group.info select, -.control-group.info textarea { - border-color: #3a87ad; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.info input:focus, -.control-group.info select:focus, -.control-group.info textarea:focus { - border-color: #2d6987; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; -} - -.control-group.info .input-prepend .add-on, -.control-group.info .input-append .add-on { - color: #3a87ad; - background-color: #d9edf7; - border-color: #3a87ad; -} - -input:focus:required:invalid, -textarea:focus:required:invalid, -select:focus:required:invalid { - color: #b94a48; - border-color: #ee5f5b; -} - -input:focus:required:invalid:focus, -textarea:focus:required:invalid:focus, -select:focus:required:invalid:focus { - border-color: #e9322d; - -webkit-box-shadow: 0 0 6px #f8b9b7; - -moz-box-shadow: 0 0 6px #f8b9b7; - box-shadow: 0 0 6px #f8b9b7; -} - -.form-actions { - padding: 19px 20px 20px; - margin-top: 20px; - margin-bottom: 20px; - background-color: #f5f5f5; - border-top: 1px solid #e5e5e5; - *zoom: 1; -} - -.form-actions:before, -.form-actions:after { - display: table; - line-height: 0; - content: ""; -} - -.form-actions:after { - clear: both; -} - -.help-block, -.help-inline { - color: #595959; -} - -.help-block { - display: block; - margin-bottom: 10px; -} - -.help-inline { - display: inline-block; - *display: inline; - padding-left: 5px; - vertical-align: middle; - *zoom: 1; -} - -.input-append, -.input-prepend { - margin-bottom: 5px; - font-size: 0; - white-space: nowrap; -} - -.input-append input, -.input-prepend input, -.input-append select, -.input-prepend select, -.input-append .uneditable-input, -.input-prepend .uneditable-input, -.input-append .dropdown-menu, -.input-prepend .dropdown-menu { - font-size: 14px; -} - -.input-append input, -.input-prepend input, -.input-append select, -.input-prepend select, -.input-append .uneditable-input, -.input-prepend .uneditable-input { - position: relative; - margin-bottom: 0; - *margin-left: 0; - vertical-align: top; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-append input:focus, -.input-prepend input:focus, -.input-append select:focus, -.input-prepend select:focus, -.input-append .uneditable-input:focus, -.input-prepend .uneditable-input:focus { - z-index: 2; -} - -.input-append .add-on, -.input-prepend .add-on { - display: inline-block; - width: auto; - height: 20px; - min-width: 16px; - padding: 4px 5px; - font-size: 14px; - font-weight: normal; - line-height: 20px; - text-align: center; - text-shadow: 0 1px 0 #ffffff; - background-color: #eeeeee; - border: 1px solid #ccc; -} - -.input-append .add-on, -.input-prepend .add-on, -.input-append .btn, -.input-prepend .btn { - vertical-align: top; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.input-append .active, -.input-prepend .active { - background-color: #a9dba9; - border-color: #46a546; -} - -.input-prepend .add-on, -.input-prepend .btn { - margin-right: -1px; -} - -.input-prepend .add-on:first-child, -.input-prepend .btn:first-child { - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-append input, -.input-append select, -.input-append .uneditable-input { - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-append input + .btn-group .btn, -.input-append select + .btn-group .btn, -.input-append .uneditable-input + .btn-group .btn { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-append .add-on, -.input-append .btn, -.input-append .btn-group { - margin-left: -1px; -} - -.input-append .add-on:last-child, -.input-append .btn:last-child { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append input, -.input-prepend.input-append select, -.input-prepend.input-append .uneditable-input { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.input-prepend.input-append input + .btn-group .btn, -.input-prepend.input-append select + .btn-group .btn, -.input-prepend.input-append .uneditable-input + .btn-group .btn { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append .add-on:first-child, -.input-prepend.input-append .btn:first-child { - margin-right: -1px; - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-prepend.input-append .add-on:last-child, -.input-prepend.input-append .btn:last-child { - margin-left: -1px; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append .btn-group:first-child { - margin-left: 0; -} - -input.search-query { - padding-right: 14px; - padding-right: 4px \9; - padding-left: 14px; - padding-left: 4px \9; - /* IE7-8 doesn't have border-radius, so don't indent the padding */ - - margin-bottom: 0; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -/* Allow for input prepend/append in search forms */ - -.form-search .input-append .search-query, -.form-search .input-prepend .search-query { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.form-search .input-append .search-query { - -webkit-border-radius: 14px 0 0 14px; - -moz-border-radius: 14px 0 0 14px; - border-radius: 14px 0 0 14px; -} - -.form-search .input-append .btn { - -webkit-border-radius: 0 14px 14px 0; - -moz-border-radius: 0 14px 14px 0; - border-radius: 0 14px 14px 0; -} - -.form-search .input-prepend .search-query { - -webkit-border-radius: 0 14px 14px 0; - -moz-border-radius: 0 14px 14px 0; - border-radius: 0 14px 14px 0; -} - -.form-search .input-prepend .btn { - -webkit-border-radius: 14px 0 0 14px; - -moz-border-radius: 14px 0 0 14px; - border-radius: 14px 0 0 14px; -} - -.form-search input, -.form-inline input, -.form-horizontal input, -.form-search textarea, -.form-inline textarea, -.form-horizontal textarea, -.form-search select, -.form-inline select, -.form-horizontal select, -.form-search .help-inline, -.form-inline .help-inline, -.form-horizontal .help-inline, -.form-search .uneditable-input, -.form-inline .uneditable-input, -.form-horizontal .uneditable-input, -.form-search .input-prepend, -.form-inline .input-prepend, -.form-horizontal .input-prepend, -.form-search .input-append, -.form-inline .input-append, -.form-horizontal .input-append { - display: inline-block; - *display: inline; - margin-bottom: 0; - vertical-align: middle; - *zoom: 1; -} - -.form-search .hide, -.form-inline .hide, -.form-horizontal .hide { - display: none; -} - -.form-search label, -.form-inline label, -.form-search .btn-group, -.form-inline .btn-group { - display: inline-block; -} - -.form-search .input-append, -.form-inline .input-append, -.form-search .input-prepend, -.form-inline .input-prepend { - margin-bottom: 0; -} - -.form-search .radio, -.form-search .checkbox, -.form-inline .radio, -.form-inline .checkbox { - padding-left: 0; - margin-bottom: 0; - vertical-align: middle; -} - -.form-search .radio input[type="radio"], -.form-search .checkbox input[type="checkbox"], -.form-inline .radio input[type="radio"], -.form-inline .checkbox input[type="checkbox"] { - float: left; - margin-right: 3px; - margin-left: 0; -} - -.control-group { - margin-bottom: 10px; -} - -legend + .control-group { - margin-top: 20px; - -webkit-margin-top-collapse: separate; -} - -.form-horizontal .control-group { - margin-bottom: 20px; - *zoom: 1; -} - -.form-horizontal .control-group:before, -.form-horizontal .control-group:after { - display: table; - line-height: 0; - content: ""; -} - -.form-horizontal .control-group:after { - clear: both; -} - -.form-horizontal .control-label { - float: left; - width: 160px; - padding-top: 5px; - text-align: right; -} - -.form-horizontal .controls { - *display: inline-block; - *padding-left: 20px; - margin-left: 180px; - *margin-left: 0; -} - -.form-horizontal .controls:first-child { - *padding-left: 180px; -} - -.form-horizontal .help-block { - margin-bottom: 0; -} - -.form-horizontal input + .help-block, -.form-horizontal select + .help-block, -.form-horizontal textarea + .help-block { - margin-top: 10px; -} - -.form-horizontal .form-actions { - padding-left: 180px; -} - -table { - max-width: 100%; - background-color: transparent; - border-collapse: collapse; - border-spacing: 0; -} - -.table { - width: 100%; - margin-bottom: 20px; -} - -.table th, -.table td { - padding: 8px; - line-height: 20px; - text-align: left; - vertical-align: top; - border-top: 1px solid #dddddd; -} - -.table th { - font-weight: bold; -} - -.table thead th { - vertical-align: bottom; -} - -.table caption + thead tr:first-child th, -.table caption + thead tr:first-child td, -.table colgroup + thead tr:first-child th, -.table colgroup + thead tr:first-child td, -.table thead:first-child tr:first-child th, -.table thead:first-child tr:first-child td { - border-top: 0; -} - -.table tbody + tbody { - border-top: 2px solid #dddddd; -} - -.table-condensed th, -.table-condensed td { - padding: 4px 5px; -} - -.table-bordered { - border: 1px solid #dddddd; - border-collapse: separate; - *border-collapse: collapse; - border-left: 0; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.table-bordered th, -.table-bordered td { - border-left: 1px solid #dddddd; -} - -.table-bordered caption + thead tr:first-child th, -.table-bordered caption + tbody tr:first-child th, -.table-bordered caption + tbody tr:first-child td, -.table-bordered colgroup + thead tr:first-child th, -.table-bordered colgroup + tbody tr:first-child th, -.table-bordered colgroup + tbody tr:first-child td, -.table-bordered thead:first-child tr:first-child th, -.table-bordered tbody:first-child tr:first-child th, -.table-bordered tbody:first-child tr:first-child td { - border-top: 0; -} - -.table-bordered thead:first-child tr:first-child th:first-child, -.table-bordered tbody:first-child tr:first-child td:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} - -.table-bordered thead:first-child tr:first-child th:last-child, -.table-bordered tbody:first-child tr:first-child td:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} - -.table-bordered thead:last-child tr:last-child th:first-child, -.table-bordered tbody:last-child tr:last-child td:first-child, -.table-bordered tfoot:last-child tr:last-child td:first-child { - -webkit-border-radius: 0 0 0 4px; - -moz-border-radius: 0 0 0 4px; - border-radius: 0 0 0 4px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; -} - -.table-bordered thead:last-child tr:last-child th:last-child, -.table-bordered tbody:last-child tr:last-child td:last-child, -.table-bordered tfoot:last-child tr:last-child td:last-child { - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-bottomright: 4px; -} - -.table-bordered caption + thead tr:first-child th:first-child, -.table-bordered caption + tbody tr:first-child td:first-child, -.table-bordered colgroup + thead tr:first-child th:first-child, -.table-bordered colgroup + tbody tr:first-child td:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} - -.table-bordered caption + thead tr:first-child th:last-child, -.table-bordered caption + tbody tr:first-child td:last-child, -.table-bordered colgroup + thead tr:first-child th:last-child, -.table-bordered colgroup + tbody tr:first-child td:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} - -.table-striped tbody tr:nth-child(odd) td, -.table-striped tbody tr:nth-child(odd) th { - background-color: #f9f9f9; -} - -.table-hover tbody tr:hover td, -.table-hover tbody tr:hover th { - background-color: #f5f5f5; -} - -table td[class*="span"], -table th[class*="span"], -.row-fluid table td[class*="span"], -.row-fluid table th[class*="span"] { - display: table-cell; - float: none; - margin-left: 0; -} - -.table td.span1, -.table th.span1 { - float: none; - width: 44px; - margin-left: 0; -} - -.table td.span2, -.table th.span2 { - float: none; - width: 124px; - margin-left: 0; -} - -.table td.span3, -.table th.span3 { - float: none; - width: 204px; - margin-left: 0; -} - -.table td.span4, -.table th.span4 { - float: none; - width: 284px; - margin-left: 0; -} - -.table td.span5, -.table th.span5 { - float: none; - width: 364px; - margin-left: 0; -} - -.table td.span6, -.table th.span6 { - float: none; - width: 444px; - margin-left: 0; -} - -.table td.span7, -.table th.span7 { - float: none; - width: 524px; - margin-left: 0; -} - -.table td.span8, -.table th.span8 { - float: none; - width: 604px; - margin-left: 0; -} - -.table td.span9, -.table th.span9 { - float: none; - width: 684px; - margin-left: 0; -} - -.table td.span10, -.table th.span10 { - float: none; - width: 764px; - margin-left: 0; -} - -.table td.span11, -.table th.span11 { - float: none; - width: 844px; - margin-left: 0; -} - -.table td.span12, -.table th.span12 { - float: none; - width: 924px; - margin-left: 0; -} - -.table tbody tr.success td { - background-color: #dff0d8; -} - -.table tbody tr.error td { - background-color: #f2dede; -} - -.table tbody tr.warning td { - background-color: #fcf8e3; -} - -.table tbody tr.info td { - background-color: #d9edf7; -} - -.table-hover tbody tr.success:hover td { - background-color: #d0e9c6; -} - -.table-hover tbody tr.error:hover td { - background-color: #ebcccc; -} - -.table-hover tbody tr.warning:hover td { - background-color: #faf2cc; -} - -.table-hover tbody tr.info:hover td { - background-color: #c4e3f3; -} - -[class^="icon-"], -[class*=" icon-"] { - display: inline-block; - width: 14px; - height: 14px; - margin-top: 1px; - *margin-right: .3em; - line-height: 14px; - vertical-align: text-top; - background-image: url("../img/glyphicons-halflings.png"); - background-position: 14px 14px; - background-repeat: no-repeat; -} - -/* White icons with optional class, or on hover/active states of certain elements */ - -.icon-white, -.nav-pills > .active > a > [class^="icon-"], -.nav-pills > .active > a > [class*=" icon-"], -.nav-list > .active > a > [class^="icon-"], -.nav-list > .active > a > [class*=" icon-"], -.navbar-inverse .nav > .active > a > [class^="icon-"], -.navbar-inverse .nav > .active > a > [class*=" icon-"], -.dropdown-menu > li > a:hover > [class^="icon-"], -.dropdown-menu > li > a:hover > [class*=" icon-"], -.dropdown-menu > .active > a > [class^="icon-"], -.dropdown-menu > .active > a > [class*=" icon-"], -.dropdown-submenu:hover > a > [class^="icon-"], -.dropdown-submenu:hover > a > [class*=" icon-"] { - background-image: url("../img/glyphicons-halflings-white.png"); -} - -.icon-glass { - background-position: 0 0; -} - -.icon-music { - background-position: -24px 0; -} - -.icon-search { - background-position: -48px 0; -} - -.icon-envelope { - background-position: -72px 0; -} - -.icon-heart { - background-position: -96px 0; -} - -.icon-star { - background-position: -120px 0; -} - -.icon-star-empty { - background-position: -144px 0; -} - -.icon-user { - background-position: -168px 0; -} - -.icon-film { - background-position: -192px 0; -} - -.icon-th-large { - background-position: -216px 0; -} - -.icon-th { - background-position: -240px 0; -} - -.icon-th-list { - background-position: -264px 0; -} - -.icon-ok { - background-position: -288px 0; -} - -.icon-remove { - background-position: -312px 0; -} - -.icon-zoom-in { - background-position: -336px 0; -} - -.icon-zoom-out { - background-position: -360px 0; -} - -.icon-off { - background-position: -384px 0; -} - -.icon-signal { - background-position: -408px 0; -} - -.icon-cog { - background-position: -432px 0; -} - -.icon-trash { - background-position: -456px 0; -} - -.icon-home { - background-position: 0 -24px; -} - -.icon-file { - background-position: -24px -24px; -} - -.icon-time { - background-position: -48px -24px; -} - -.icon-road { - background-position: -72px -24px; -} - -.icon-download-alt { - background-position: -96px -24px; -} - -.icon-download { - background-position: -120px -24px; -} - -.icon-upload { - background-position: -144px -24px; -} - -.icon-inbox { - background-position: -168px -24px; -} - -.icon-play-circle { - background-position: -192px -24px; -} - -.icon-repeat { - background-position: -216px -24px; -} - -.icon-refresh { - background-position: -240px -24px; -} - -.icon-list-alt { - background-position: -264px -24px; -} - -.icon-lock { - background-position: -287px -24px; -} - -.icon-flag { - background-position: -312px -24px; -} - -.icon-headphones { - background-position: -336px -24px; -} - -.icon-volume-off { - background-position: -360px -24px; -} - -.icon-volume-down { - background-position: -384px -24px; -} - -.icon-volume-up { - background-position: -408px -24px; -} - -.icon-qrcode { - background-position: -432px -24px; -} - -.icon-barcode { - background-position: -456px -24px; -} - -.icon-tag { - background-position: 0 -48px; -} - -.icon-tags { - background-position: -25px -48px; -} - -.icon-book { - background-position: -48px -48px; -} - -.icon-bookmark { - background-position: -72px -48px; -} - -.icon-print { - background-position: -96px -48px; -} - -.icon-camera { - background-position: -120px -48px; -} - -.icon-font { - background-position: -144px -48px; -} - -.icon-bold { - background-position: -167px -48px; -} - -.icon-italic { - background-position: -192px -48px; -} - -.icon-text-height { - background-position: -216px -48px; -} - -.icon-text-width { - background-position: -240px -48px; -} - -.icon-align-left { - background-position: -264px -48px; -} - -.icon-align-center { - background-position: -288px -48px; -} - -.icon-align-right { - background-position: -312px -48px; -} - -.icon-align-justify { - background-position: -336px -48px; -} - -.icon-list { - background-position: -360px -48px; -} - -.icon-indent-left { - background-position: -384px -48px; -} - -.icon-indent-right { - background-position: -408px -48px; -} - -.icon-facetime-video { - background-position: -432px -48px; -} - -.icon-picture { - background-position: -456px -48px; -} - -.icon-pencil { - background-position: 0 -72px; -} - -.icon-map-marker { - background-position: -24px -72px; -} - -.icon-adjust { - background-position: -48px -72px; -} - -.icon-tint { - background-position: -72px -72px; -} - -.icon-edit { - background-position: -96px -72px; -} - -.icon-share { - background-position: -120px -72px; -} - -.icon-check { - background-position: -144px -72px; -} - -.icon-move { - background-position: -168px -72px; -} - -.icon-step-backward { - background-position: -192px -72px; -} - -.icon-fast-backward { - background-position: -216px -72px; -} - -.icon-backward { - background-position: -240px -72px; -} - -.icon-play { - background-position: -264px -72px; -} - -.icon-pause { - background-position: -288px -72px; -} - -.icon-stop { - background-position: -312px -72px; -} - -.icon-forward { - background-position: -336px -72px; -} - -.icon-fast-forward { - background-position: -360px -72px; -} - -.icon-step-forward { - background-position: -384px -72px; -} - -.icon-eject { - background-position: -408px -72px; -} - -.icon-chevron-left { - background-position: -432px -72px; -} - -.icon-chevron-right { - background-position: -456px -72px; -} - -.icon-plus-sign { - background-position: 0 -96px; -} - -.icon-minus-sign { - background-position: -24px -96px; -} - -.icon-remove-sign { - background-position: -48px -96px; -} - -.icon-ok-sign { - background-position: -72px -96px; -} - -.icon-question-sign { - background-position: -96px -96px; -} - -.icon-info-sign { - background-position: -120px -96px; -} - -.icon-screenshot { - background-position: -144px -96px; -} - -.icon-remove-circle { - background-position: -168px -96px; -} - -.icon-ok-circle { - background-position: -192px -96px; -} - -.icon-ban-circle { - background-position: -216px -96px; -} - -.icon-arrow-left { - background-position: -240px -96px; -} - -.icon-arrow-right { - background-position: -264px -96px; -} - -.icon-arrow-up { - background-position: -289px -96px; -} - -.icon-arrow-down { - background-position: -312px -96px; -} - -.icon-share-alt { - background-position: -336px -96px; -} - -.icon-resize-full { - background-position: -360px -96px; -} - -.icon-resize-small { - background-position: -384px -96px; -} - -.icon-plus { - background-position: -408px -96px; -} - -.icon-minus { - background-position: -433px -96px; -} - -.icon-asterisk { - background-position: -456px -96px; -} - -.icon-exclamation-sign { - background-position: 0 -120px; -} - -.icon-gift { - background-position: -24px -120px; -} - -.icon-leaf { - background-position: -48px -120px; -} - -.icon-fire { - background-position: -72px -120px; -} - -.icon-eye-open { - background-position: -96px -120px; -} - -.icon-eye-close { - background-position: -120px -120px; -} - -.icon-warning-sign { - background-position: -144px -120px; -} - -.icon-plane { - background-position: -168px -120px; -} - -.icon-calendar { - background-position: -192px -120px; -} - -.icon-random { - width: 16px; - background-position: -216px -120px; -} - -.icon-comment { - background-position: -240px -120px; -} - -.icon-magnet { - background-position: -264px -120px; -} - -.icon-chevron-up { - background-position: -288px -120px; -} - -.icon-chevron-down { - background-position: -313px -119px; -} - -.icon-retweet { - background-position: -336px -120px; -} - -.icon-shopping-cart { - background-position: -360px -120px; -} - -.icon-folder-close { - background-position: -384px -120px; -} - -.icon-folder-open { - width: 16px; - background-position: -408px -120px; -} - -.icon-resize-vertical { - background-position: -432px -119px; -} - -.icon-resize-horizontal { - background-position: -456px -118px; -} - -.icon-hdd { - background-position: 0 -144px; -} - -.icon-bullhorn { - background-position: -24px -144px; -} - -.icon-bell { - background-position: -48px -144px; -} - -.icon-certificate { - background-position: -72px -144px; -} - -.icon-thumbs-up { - background-position: -96px -144px; -} - -.icon-thumbs-down { - background-position: -120px -144px; -} - -.icon-hand-right { - background-position: -144px -144px; -} - -.icon-hand-left { - background-position: -168px -144px; -} - -.icon-hand-up { - background-position: -192px -144px; -} - -.icon-hand-down { - background-position: -216px -144px; -} - -.icon-circle-arrow-right { - background-position: -240px -144px; -} - -.icon-circle-arrow-left { - background-position: -264px -144px; -} - -.icon-circle-arrow-up { - background-position: -288px -144px; -} - -.icon-circle-arrow-down { - background-position: -312px -144px; -} - -.icon-globe { - background-position: -336px -144px; -} - -.icon-wrench { - background-position: -360px -144px; -} - -.icon-tasks { - background-position: -384px -144px; -} - -.icon-filter { - background-position: -408px -144px; -} - -.icon-briefcase { - background-position: -432px -144px; -} - -.icon-fullscreen { - background-position: -456px -144px; -} - -.dropup, -.dropdown { - position: relative; -} - -.dropdown-toggle { - *margin-bottom: -3px; -} - -.dropdown-toggle:active, -.open .dropdown-toggle { - outline: 0; -} - -.caret { - display: inline-block; - width: 0; - height: 0; - vertical-align: top; - border-top: 4px solid #000000; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - content: ""; -} - -.dropdown .caret { - margin-top: 8px; - margin-left: 2px; -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - list-style: none; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - *border-right-width: 2px; - *border-bottom-width: 2px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.dropdown-menu .divider { - *width: 100%; - height: 1px; - margin: 9px 1px; - *margin: -5px 0 5px; - overflow: hidden; - background-color: #e5e5e5; - border-bottom: 1px solid #ffffff; -} - -.dropdown-menu li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 20px; - color: #333333; - white-space: nowrap; -} - -.dropdown-menu li > a:hover, -.dropdown-menu li > a:focus, -.dropdown-submenu:hover > a { - color: #ffffff; - text-decoration: none; - background-color: #0081c2; - background-image: -moz-linear-gradient(top, #0088cc, #0077b3); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); - background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); - background-image: -o-linear-gradient(top, #0088cc, #0077b3); - background-image: linear-gradient(to bottom, #0088cc, #0077b3); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); -} - -.dropdown-menu .active > a, -.dropdown-menu .active > a:hover { - color: #333333; - text-decoration: none; - background-color: #0081c2; - background-image: -moz-linear-gradient(top, #0088cc, #0077b3); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); - background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); - background-image: -o-linear-gradient(top, #0088cc, #0077b3); - background-image: linear-gradient(to bottom, #0088cc, #0077b3); - background-repeat: repeat-x; - outline: 0; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); -} - -.dropdown-menu .disabled > a, -.dropdown-menu .disabled > a:hover { - color: #999999; -} - -.dropdown-menu .disabled > a:hover { - text-decoration: none; - cursor: default; - background-color: transparent; - background-image: none; -} - -.open { - *z-index: 1000; -} - -.open > .dropdown-menu { - display: block; -} - -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} - -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - border-top: 0; - border-bottom: 4px solid #000000; - content: ""; -} - -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 1px; -} - -.dropdown-submenu { - position: relative; -} - -.dropdown-submenu > .dropdown-menu { - top: 0; - left: 100%; - margin-top: -6px; - margin-left: -1px; - -webkit-border-radius: 0 6px 6px 6px; - -moz-border-radius: 0 6px 6px 6px; - border-radius: 0 6px 6px 6px; -} - -.dropdown-submenu:hover > .dropdown-menu { - display: block; -} - -.dropup .dropdown-submenu > .dropdown-menu { - top: auto; - bottom: 0; - margin-top: 0; - margin-bottom: -2px; - -webkit-border-radius: 5px 5px 5px 0; - -moz-border-radius: 5px 5px 5px 0; - border-radius: 5px 5px 5px 0; -} - -.dropdown-submenu > a:after { - display: block; - float: right; - width: 0; - height: 0; - margin-top: 5px; - margin-right: -10px; - border-color: transparent; - border-left-color: #cccccc; - border-style: solid; - border-width: 5px 0 5px 5px; - content: " "; -} - -.dropdown-submenu:hover > a:after { - border-left-color: #ffffff; -} - -.dropdown-submenu.pull-left { - float: none; -} - -.dropdown-submenu.pull-left > .dropdown-menu { - left: -100%; - margin-left: 10px; - -webkit-border-radius: 6px 0 6px 6px; - -moz-border-radius: 6px 0 6px 6px; - border-radius: 6px 0 6px 6px; -} - -.dropdown .dropdown-menu .nav-header { - padding-right: 20px; - padding-left: 20px; -} - -.typeahead { - margin-top: 2px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -} - -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, 0.15); -} - -.well-large { - padding: 24px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.well-small { - padding: 9px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.fade { - opacity: 0; - -webkit-transition: opacity 0.15s linear; - -moz-transition: opacity 0.15s linear; - -o-transition: opacity 0.15s linear; - transition: opacity 0.15s linear; -} - -.fade.in { - opacity: 1; -} - -.collapse { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition: height 0.35s ease; - -moz-transition: height 0.35s ease; - -o-transition: height 0.35s ease; - transition: height 0.35s ease; -} - -.collapse.in { - height: auto; -} - -.close { - float: right; - font-size: 20px; - font-weight: bold; - line-height: 20px; - color: #000000; - text-shadow: 0 1px 0 #ffffff; - opacity: 0.2; - filter: alpha(opacity=20); -} - -.close:hover { - color: #000000; - text-decoration: none; - cursor: pointer; - opacity: 0.4; - filter: alpha(opacity=40); -} - -button.close { - padding: 0; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; -} - -.btn { - display: inline-block; - *display: inline; - padding: 4px 12px; - margin-bottom: 0; - *margin-left: .3em; - font-size: 14px; - line-height: 20px; - *line-height: 20px; - color: #333333; - text-align: center; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - vertical-align: middle; - cursor: pointer; - background-color: #f5f5f5; - *background-color: #e6e6e6; - background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); - background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); - background-repeat: repeat-x; - border: 1px solid #bbbbbb; - *border: 0; - border-color: #e6e6e6 #e6e6e6 #bfbfbf; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - border-bottom-color: #a2a2a2; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - *zoom: 1; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn:hover, -.btn:active, -.btn.active, -.btn.disabled, -.btn[disabled] { - color: #333333; - background-color: #e6e6e6; - *background-color: #d9d9d9; -} - -.btn:active, -.btn.active { - background-color: #cccccc \9; -} - -.btn:first-child { - *margin-left: 0; -} - -.btn:hover { - color: #333333; - text-decoration: none; - background-color: #e6e6e6; - *background-color: #d9d9d9; - /* Buttons in IE7 don't get borders, so darken on hover */ - - background-position: 0 -15px; - -webkit-transition: background-position 0.1s linear; - -moz-transition: background-position 0.1s linear; - -o-transition: background-position 0.1s linear; - transition: background-position 0.1s linear; -} - -.btn:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.btn.active, -.btn:active { - background-color: #e6e6e6; - background-color: #d9d9d9 \9; - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn.disabled, -.btn[disabled] { - cursor: default; - background-color: #e6e6e6; - background-image: none; - opacity: 0.65; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} - -.btn-large { - padding: 11px 19px; - font-size: 17.5px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.btn-large [class^="icon-"], -.btn-large [class*=" icon-"] { - margin-top: 2px; -} - -.btn-small { - padding: 2px 10px; - font-size: 11.9px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.btn-small [class^="icon-"], -.btn-small [class*=" icon-"] { - margin-top: 0; -} - -.btn-mini { - padding: 1px 6px; - font-size: 10.5px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.btn-block { - display: block; - width: 100%; - padding-right: 0; - padding-left: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.btn-block + .btn-block { - margin-top: 5px; -} - -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} - -.btn-primary.active, -.btn-warning.active, -.btn-danger.active, -.btn-success.active, -.btn-info.active, -.btn-inverse.active { - color: rgba(255, 255, 255, 0.75); -} - -.btn { - border-color: #c5c5c5; - border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25); -} - -.btn-primary { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #006dcc; - *background-color: #0044cc; - background-image: -moz-linear-gradient(top, #0088cc, #0044cc); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); - background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); - background-image: -o-linear-gradient(top, #0088cc, #0044cc); - background-image: linear-gradient(to bottom, #0088cc, #0044cc); - background-repeat: repeat-x; - border-color: #0044cc #0044cc #002a80; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-primary:hover, -.btn-primary:active, -.btn-primary.active, -.btn-primary.disabled, -.btn-primary[disabled] { - color: #ffffff; - background-color: #0044cc; - *background-color: #003bb3; -} - -.btn-primary:active, -.btn-primary.active { - background-color: #003399 \9; -} - -.btn-warning { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #faa732; - *background-color: #f89406; - background-image: -moz-linear-gradient(top, #fbb450, #f89406); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); - background-image: -webkit-linear-gradient(top, #fbb450, #f89406); - background-image: -o-linear-gradient(top, #fbb450, #f89406); - background-image: linear-gradient(to bottom, #fbb450, #f89406); - background-repeat: repeat-x; - border-color: #f89406 #f89406 #ad6704; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-warning:hover, -.btn-warning:active, -.btn-warning.active, -.btn-warning.disabled, -.btn-warning[disabled] { - color: #ffffff; - background-color: #f89406; - *background-color: #df8505; -} - -.btn-warning:active, -.btn-warning.active { - background-color: #c67605 \9; -} - -.btn-danger { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #da4f49; - *background-color: #bd362f; - background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); - background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); - background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); - background-image: linear-gradient(to bottom, #ee5f5b, #bd362f); - background-repeat: repeat-x; - border-color: #bd362f #bd362f #802420; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-danger:hover, -.btn-danger:active, -.btn-danger.active, -.btn-danger.disabled, -.btn-danger[disabled] { - color: #ffffff; - background-color: #bd362f; - *background-color: #a9302a; -} - -.btn-danger:active, -.btn-danger.active { - background-color: #942a25 \9; -} - -.btn-success { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #5bb75b; - *background-color: #51a351; - background-image: -moz-linear-gradient(top, #62c462, #51a351); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); - background-image: -webkit-linear-gradient(top, #62c462, #51a351); - background-image: -o-linear-gradient(top, #62c462, #51a351); - background-image: linear-gradient(to bottom, #62c462, #51a351); - background-repeat: repeat-x; - border-color: #51a351 #51a351 #387038; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-success:hover, -.btn-success:active, -.btn-success.active, -.btn-success.disabled, -.btn-success[disabled] { - color: #ffffff; - background-color: #51a351; - *background-color: #499249; -} - -.btn-success:active, -.btn-success.active { - background-color: #408140 \9; -} - -.btn-info { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #49afcd; - *background-color: #2f96b4; - background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); - background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); - background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); - background-image: linear-gradient(to bottom, #5bc0de, #2f96b4); - background-repeat: repeat-x; - border-color: #2f96b4 #2f96b4 #1f6377; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-info:hover, -.btn-info:active, -.btn-info.active, -.btn-info.disabled, -.btn-info[disabled] { - color: #ffffff; - background-color: #2f96b4; - *background-color: #2a85a0; -} - -.btn-info:active, -.btn-info.active { - background-color: #24748c \9; -} - -.btn-inverse { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #363636; - *background-color: #222222; - background-image: -moz-linear-gradient(top, #444444, #222222); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222)); - background-image: -webkit-linear-gradient(top, #444444, #222222); - background-image: -o-linear-gradient(top, #444444, #222222); - background-image: linear-gradient(to bottom, #444444, #222222); - background-repeat: repeat-x; - border-color: #222222 #222222 #000000; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-inverse:hover, -.btn-inverse:active, -.btn-inverse.active, -.btn-inverse.disabled, -.btn-inverse[disabled] { - color: #ffffff; - background-color: #222222; - *background-color: #151515; -} - -.btn-inverse:active, -.btn-inverse.active { - background-color: #080808 \9; -} - -button.btn, -input[type="submit"].btn { - *padding-top: 3px; - *padding-bottom: 3px; -} - -button.btn::-moz-focus-inner, -input[type="submit"].btn::-moz-focus-inner { - padding: 0; - border: 0; -} - -button.btn.btn-large, -input[type="submit"].btn.btn-large { - *padding-top: 7px; - *padding-bottom: 7px; -} - -button.btn.btn-small, -input[type="submit"].btn.btn-small { - *padding-top: 3px; - *padding-bottom: 3px; -} - -button.btn.btn-mini, -input[type="submit"].btn.btn-mini { - *padding-top: 1px; - *padding-bottom: 1px; -} - -.btn-link, -.btn-link:active, -.btn-link[disabled] { - background-color: transparent; - background-image: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} - -.btn-link { - color: #0088cc; - cursor: pointer; - border-color: transparent; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-link:hover { - color: #005580; - text-decoration: underline; - background-color: transparent; -} - -.btn-link[disabled]:hover { - color: #333333; - text-decoration: none; -} - -.btn-group { - position: relative; - display: inline-block; - *display: inline; - *margin-left: .3em; - font-size: 0; - white-space: nowrap; - vertical-align: middle; - *zoom: 1; -} - -.btn-group:first-child { - *margin-left: 0; -} - -.btn-group + .btn-group { - margin-left: 5px; -} - -.btn-toolbar { - margin-top: 10px; - margin-bottom: 10px; - font-size: 0; -} - -.btn-toolbar .btn + .btn, -.btn-toolbar .btn-group + .btn, -.btn-toolbar .btn + .btn-group { - margin-left: 5px; -} - -.btn-group > .btn { - position: relative; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-group > .btn + .btn { - margin-left: -1px; -} - -.btn-group > .btn, -.btn-group > .dropdown-menu { - font-size: 14px; -} - -.btn-group > .btn-mini { - font-size: 11px; -} - -.btn-group > .btn-small { - font-size: 12px; -} - -.btn-group > .btn-large { - font-size: 16px; -} - -.btn-group > .btn:first-child { - margin-left: 0; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 4px; -} - -.btn-group > .btn:last-child, -.btn-group > .dropdown-toggle { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; -} - -.btn-group > .btn.large:first-child { - margin-left: 0; - -webkit-border-bottom-left-radius: 6px; - border-bottom-left-radius: 6px; - -webkit-border-top-left-radius: 6px; - border-top-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - -moz-border-radius-topleft: 6px; -} - -.btn-group > .btn.large:last-child, -.btn-group > .large.dropdown-toggle { - -webkit-border-top-right-radius: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - border-bottom-right-radius: 6px; - -moz-border-radius-topright: 6px; - -moz-border-radius-bottomright: 6px; -} - -.btn-group > .btn:hover, -.btn-group > .btn:focus, -.btn-group > .btn:active, -.btn-group > .btn.active { - z-index: 2; -} - -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} - -.btn-group > .btn + .dropdown-toggle { - *padding-top: 5px; - padding-right: 8px; - *padding-bottom: 5px; - padding-left: 8px; - -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group > .btn-mini + .dropdown-toggle { - *padding-top: 2px; - padding-right: 5px; - *padding-bottom: 2px; - padding-left: 5px; -} - -.btn-group > .btn-small + .dropdown-toggle { - *padding-top: 5px; - *padding-bottom: 4px; -} - -.btn-group > .btn-large + .dropdown-toggle { - *padding-top: 7px; - padding-right: 12px; - *padding-bottom: 7px; - padding-left: 12px; -} - -.btn-group.open .dropdown-toggle { - background-image: none; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group.open .btn.dropdown-toggle { - background-color: #e6e6e6; -} - -.btn-group.open .btn-primary.dropdown-toggle { - background-color: #0044cc; -} - -.btn-group.open .btn-warning.dropdown-toggle { - background-color: #f89406; -} - -.btn-group.open .btn-danger.dropdown-toggle { - background-color: #bd362f; -} - -.btn-group.open .btn-success.dropdown-toggle { - background-color: #51a351; -} - -.btn-group.open .btn-info.dropdown-toggle { - background-color: #2f96b4; -} - -.btn-group.open .btn-inverse.dropdown-toggle { - background-color: #222222; -} - -.btn .caret { - margin-top: 8px; - margin-left: 0; -} - -.btn-mini .caret, -.btn-small .caret, -.btn-large .caret { - margin-top: 6px; -} - -.btn-large .caret { - border-top-width: 5px; - border-right-width: 5px; - border-left-width: 5px; -} - -.dropup .btn-large .caret { - border-bottom-width: 5px; -} - -.btn-primary .caret, -.btn-warning .caret, -.btn-danger .caret, -.btn-info .caret, -.btn-success .caret, -.btn-inverse .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.btn-group-vertical { - display: inline-block; - *display: inline; - /* IE7 inline-block hack */ - - *zoom: 1; -} - -.btn-group-vertical .btn { - display: block; - float: none; - width: 100%; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-group-vertical .btn + .btn { - margin-top: -1px; - margin-left: 0; -} - -.btn-group-vertical .btn:first-child { - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; -} - -.btn-group-vertical .btn:last-child { - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; -} - -.btn-group-vertical .btn-large:first-child { - -webkit-border-radius: 6px 6px 0 0; - -moz-border-radius: 6px 6px 0 0; - border-radius: 6px 6px 0 0; -} - -.btn-group-vertical .btn-large:last-child { - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; -} - -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: 20px; - color: #c09853; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - background-color: #fcf8e3; - border: 1px solid #fbeed5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.alert h4 { - margin: 0; -} - -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: 20px; -} - -.alert-success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.alert-danger, -.alert-error { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -.alert-info { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.alert-block { - padding-top: 14px; - padding-bottom: 14px; -} - -.alert-block > p, -.alert-block > ul { - margin-bottom: 0; -} - -.alert-block p + p { - margin-top: 5px; -} - -.nav { - margin-bottom: 20px; - margin-left: 0; - list-style: none; -} - -.nav > li > a { - display: block; -} - -.nav > li > a:hover { - text-decoration: none; - background-color: #eeeeee; -} - -.nav > .pull-right { - float: right; -} - -.nav-header { - display: block; - padding: 3px 15px; - font-size: 11px; - font-weight: bold; - line-height: 20px; - color: #999999; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - text-transform: uppercase; -} - -.nav li + .nav-header { - margin-top: 9px; -} - -.nav-list { - padding-right: 15px; - padding-left: 15px; - margin-bottom: 0; -} - -.nav-list > li > a, -.nav-list .nav-header { - margin-right: -15px; - margin-left: -15px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} - -.nav-list > li > a { - padding: 3px 15px; -} - -.nav-list > .active > a, -.nav-list > .active > a:hover { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); - background-color: #0088cc; -} - -.nav-list [class^="icon-"], -.nav-list [class*=" icon-"] { - margin-right: 2px; -} - -.nav-list .divider { - *width: 100%; - height: 1px; - margin: 9px 1px; - *margin: -5px 0 5px; - overflow: hidden; - background-color: #e5e5e5; - border-bottom: 1px solid #ffffff; -} - -.nav-tabs, -.nav-pills { - *zoom: 1; -} - -.nav-tabs:before, -.nav-pills:before, -.nav-tabs:after, -.nav-pills:after { - display: table; - line-height: 0; - content: ""; -} - -.nav-tabs:after, -.nav-pills:after { - clear: both; -} - -.nav-tabs > li, -.nav-pills > li { - float: left; -} - -.nav-tabs > li > a, -.nav-pills > li > a { - padding-right: 12px; - padding-left: 12px; - margin-right: 2px; - line-height: 14px; -} - -.nav-tabs { - border-bottom: 1px solid #ddd; -} - -.nav-tabs > li { - margin-bottom: -1px; -} - -.nav-tabs > li > a { - padding-top: 8px; - padding-bottom: 8px; - line-height: 20px; - border: 1px solid transparent; - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; -} - -.nav-tabs > li > a:hover { - border-color: #eeeeee #eeeeee #dddddd; -} - -.nav-tabs > .active > a, -.nav-tabs > .active > a:hover { - color: #555555; - cursor: default; - background-color: #ffffff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} - -.nav-pills > li > a { - padding-top: 8px; - padding-bottom: 8px; - margin-top: 2px; - margin-bottom: 2px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} - -.nav-pills > .active > a, -.nav-pills > .active > a:hover { - color: #ffffff; - background-color: #0088cc; -} - -.nav-stacked > li { - float: none; -} - -.nav-stacked > li > a { - margin-right: 0; -} - -.nav-tabs.nav-stacked { - border-bottom: 0; -} - -.nav-tabs.nav-stacked > li > a { - border: 1px solid #ddd; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.nav-tabs.nav-stacked > li:first-child > a { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-topleft: 4px; -} - -.nav-tabs.nav-stacked > li:last-child > a { - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -moz-border-radius-bottomright: 4px; - -moz-border-radius-bottomleft: 4px; -} - -.nav-tabs.nav-stacked > li > a:hover { - z-index: 2; - border-color: #ddd; -} - -.nav-pills.nav-stacked > li > a { - margin-bottom: 3px; -} - -.nav-pills.nav-stacked > li:last-child > a { - margin-bottom: 1px; -} - -.nav-tabs .dropdown-menu { - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; -} - -.nav-pills .dropdown-menu { - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.nav .dropdown-toggle .caret { - margin-top: 6px; - border-top-color: #0088cc; - border-bottom-color: #0088cc; -} - -.nav .dropdown-toggle:hover .caret { - border-top-color: #005580; - border-bottom-color: #005580; -} - -/* move down carets for tabs */ - -.nav-tabs .dropdown-toggle .caret { - margin-top: 8px; -} - -.nav .active .dropdown-toggle .caret { - border-top-color: #fff; - border-bottom-color: #fff; -} - -.nav-tabs .active .dropdown-toggle .caret { - border-top-color: #555555; - border-bottom-color: #555555; -} - -.nav > .dropdown.active > a:hover { - cursor: pointer; -} - -.nav-tabs .open .dropdown-toggle, -.nav-pills .open .dropdown-toggle, -.nav > li.dropdown.open.active > a:hover { - color: #ffffff; - background-color: #999999; - border-color: #999999; -} - -.nav li.dropdown.open .caret, -.nav li.dropdown.open.active .caret, -.nav li.dropdown.open a:hover .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; - opacity: 1; - filter: alpha(opacity=100); -} - -.tabs-stacked .open > a:hover { - border-color: #999999; -} - -.tabbable { - *zoom: 1; -} - -.tabbable:before, -.tabbable:after { - display: table; - line-height: 0; - content: ""; -} - -.tabbable:after { - clear: both; -} - -.tab-content { - overflow: auto; -} - -.tabs-below > .nav-tabs, -.tabs-right > .nav-tabs, -.tabs-left > .nav-tabs { - border-bottom: 0; -} - -.tab-content > .tab-pane, -.pill-content > .pill-pane { - display: none; -} - -.tab-content > .active, -.pill-content > .active { - display: block; -} - -.tabs-below > .nav-tabs { - border-top: 1px solid #ddd; -} - -.tabs-below > .nav-tabs > li { - margin-top: -1px; - margin-bottom: 0; -} - -.tabs-below > .nav-tabs > li > a { - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; -} - -.tabs-below > .nav-tabs > li > a:hover { - border-top-color: #ddd; - border-bottom-color: transparent; -} - -.tabs-below > .nav-tabs > .active > a, -.tabs-below > .nav-tabs > .active > a:hover { - border-color: transparent #ddd #ddd #ddd; -} - -.tabs-left > .nav-tabs > li, -.tabs-right > .nav-tabs > li { - float: none; -} - -.tabs-left > .nav-tabs > li > a, -.tabs-right > .nav-tabs > li > a { - min-width: 74px; - margin-right: 0; - margin-bottom: 3px; -} - -.tabs-left > .nav-tabs { - float: left; - margin-right: 19px; - border-right: 1px solid #ddd; -} - -.tabs-left > .nav-tabs > li > a { - margin-right: -1px; - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.tabs-left > .nav-tabs > li > a:hover { - border-color: #eeeeee #dddddd #eeeeee #eeeeee; -} - -.tabs-left > .nav-tabs .active > a, -.tabs-left > .nav-tabs .active > a:hover { - border-color: #ddd transparent #ddd #ddd; - *border-right-color: #ffffff; -} - -.tabs-right > .nav-tabs { - float: right; - margin-left: 19px; - border-left: 1px solid #ddd; -} - -.tabs-right > .nav-tabs > li > a { - margin-left: -1px; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.tabs-right > .nav-tabs > li > a:hover { - border-color: #eeeeee #eeeeee #eeeeee #dddddd; -} - -.tabs-right > .nav-tabs .active > a, -.tabs-right > .nav-tabs .active > a:hover { - border-color: #ddd #ddd #ddd transparent; - *border-left-color: #ffffff; -} - -.nav > .disabled > a { - color: #999999; -} - -.nav > .disabled > a:hover { - text-decoration: none; - cursor: default; - background-color: transparent; -} - -.navbar { - *position: relative; - *z-index: 2; - margin-bottom: 20px; - overflow: visible; - color: #777777; -} - -.navbar-inner { - min-height: 40px; - padding-right: 20px; - padding-left: 20px; - background-color: #fafafa; - background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2)); - background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2); - background-image: -o-linear-gradient(top, #ffffff, #f2f2f2); - background-image: linear-gradient(to bottom, #ffffff, #f2f2f2); - background-repeat: repeat-x; - border: 1px solid #d4d4d4; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0); - *zoom: 1; - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); -} - -.navbar-inner:before, -.navbar-inner:after { - display: table; - line-height: 0; - content: ""; -} - -.navbar-inner:after { - clear: both; -} - -.navbar .container { - width: auto; -} - -.nav-collapse.collapse { - height: auto; - overflow: visible; -} - -.navbar .brand { - display: block; - float: left; - padding: 10px 20px 10px; - margin-left: -20px; - font-size: 20px; - font-weight: 200; - color: #777777; - text-shadow: 0 1px 0 #ffffff; -} - -.navbar .brand:hover { - text-decoration: none; -} - -.navbar-text { - margin-bottom: 0; - line-height: 40px; -} - -.navbar-link { - color: #777777; -} - -.navbar-link:hover { - color: #333333; -} - -.navbar .divider-vertical { - height: 40px; - margin: 0 9px; - border-right: 1px solid #ffffff; - border-left: 1px solid #f2f2f2; -} - -.navbar .btn, -.navbar .btn-group { - margin-top: 5px; -} - -.navbar .btn-group .btn, -.navbar .input-prepend .btn, -.navbar .input-append .btn { - margin-top: 0; -} - -.navbar-form { - margin-bottom: 0; - *zoom: 1; -} - -.navbar-form:before, -.navbar-form:after { - display: table; - line-height: 0; - content: ""; -} - -.navbar-form:after { - clear: both; -} - -.navbar-form input, -.navbar-form select, -.navbar-form .radio, -.navbar-form .checkbox { - margin-top: 5px; -} - -.navbar-form input, -.navbar-form select, -.navbar-form .btn { - display: inline-block; - margin-bottom: 0; -} - -.navbar-form input[type="image"], -.navbar-form input[type="checkbox"], -.navbar-form input[type="radio"] { - margin-top: 3px; -} - -.navbar-form .input-append, -.navbar-form .input-prepend { - margin-top: 6px; - white-space: nowrap; -} - -.navbar-form .input-append input, -.navbar-form .input-prepend input { - margin-top: 0; -} - -.navbar-search { - position: relative; - float: left; - margin-top: 5px; - margin-bottom: 0; -} - -.navbar-search .search-query { - padding: 4px 14px; - margin-bottom: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: normal; - line-height: 1; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -.navbar-static-top { - position: static; - margin-bottom: 0; -} - -.navbar-static-top .navbar-inner { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; - margin-bottom: 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-static-top .navbar-inner { - border-width: 0 0 1px; -} - -.navbar-fixed-bottom .navbar-inner { - border-width: 1px 0 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-fixed-bottom .navbar-inner { - padding-right: 0; - padding-left: 0; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.navbar-static-top .container, -.navbar-fixed-top .container, -.navbar-fixed-bottom .container { - width: 940px; -} - -.navbar-fixed-top { - top: 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-static-top .navbar-inner { - -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); -} - -.navbar-fixed-bottom { - bottom: 0; -} - -.navbar-fixed-bottom .navbar-inner { - -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); - box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); -} - -.navbar .nav { - position: relative; - left: 0; - display: block; - float: left; - margin: 0 10px 0 0; -} - -.navbar .nav.pull-right { - float: right; - margin-right: 0; -} - -.navbar .nav > li { - float: left; -} - -.navbar .nav > li > a { - float: none; - padding: 10px 15px 10px; - color: #777777; - text-decoration: none; - text-shadow: 0 1px 0 #ffffff; -} - -.navbar .nav .dropdown-toggle .caret { - margin-top: 8px; -} - -.navbar .nav > li > a:focus, -.navbar .nav > li > a:hover { - color: #333333; - text-decoration: none; - background-color: transparent; -} - -.navbar .nav > .active > a, -.navbar .nav > .active > a:hover, -.navbar .nav > .active > a:focus { - color: #555555; - text-decoration: none; - background-color: #e5e5e5; - -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); - -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); -} - -.navbar .btn-navbar { - display: none; - float: right; - padding: 7px 10px; - margin-right: 5px; - margin-left: 5px; - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #ededed; - *background-color: #e5e5e5; - background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5)); - background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5); - background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5); - background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5); - background-repeat: repeat-x; - border-color: #e5e5e5 #e5e5e5 #bfbfbf; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); -} - -.navbar .btn-navbar:hover, -.navbar .btn-navbar:active, -.navbar .btn-navbar.active, -.navbar .btn-navbar.disabled, -.navbar .btn-navbar[disabled] { - color: #ffffff; - background-color: #e5e5e5; - *background-color: #d9d9d9; -} - -.navbar .btn-navbar:active, -.navbar .btn-navbar.active { - background-color: #cccccc \9; -} - -.navbar .btn-navbar .icon-bar { - display: block; - width: 18px; - height: 2px; - background-color: #f5f5f5; - -webkit-border-radius: 1px; - -moz-border-radius: 1px; - border-radius: 1px; - -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); -} - -.btn-navbar .icon-bar + .icon-bar { - margin-top: 3px; -} - -.navbar .nav > li > .dropdown-menu:before { - position: absolute; - top: -7px; - left: 9px; - display: inline-block; - border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; - border-left: 7px solid transparent; - border-bottom-color: rgba(0, 0, 0, 0.2); - content: ''; -} - -.navbar .nav > li > .dropdown-menu:after { - position: absolute; - top: -6px; - left: 10px; - display: inline-block; - border-right: 6px solid transparent; - border-bottom: 6px solid #ffffff; - border-left: 6px solid transparent; - content: ''; -} - -.navbar-fixed-bottom .nav > li > .dropdown-menu:before { - top: auto; - bottom: -7px; - border-top: 7px solid #ccc; - border-bottom: 0; - border-top-color: rgba(0, 0, 0, 0.2); -} - -.navbar-fixed-bottom .nav > li > .dropdown-menu:after { - top: auto; - bottom: -6px; - border-top: 6px solid #ffffff; - border-bottom: 0; -} - -.navbar .nav li.dropdown.open > .dropdown-toggle, -.navbar .nav li.dropdown.active > .dropdown-toggle, -.navbar .nav li.dropdown.open.active > .dropdown-toggle { - color: #555555; - background-color: #e5e5e5; -} - -.navbar .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: #777777; - border-bottom-color: #777777; -} - -.navbar .nav li.dropdown.open > .dropdown-toggle .caret, -.navbar .nav li.dropdown.active > .dropdown-toggle .caret, -.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: #555555; - border-bottom-color: #555555; -} - -.navbar .pull-right > li > .dropdown-menu, -.navbar .nav > li > .dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu:before, -.navbar .nav > li > .dropdown-menu.pull-right:before { - right: 12px; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu:after, -.navbar .nav > li > .dropdown-menu.pull-right:after { - right: 13px; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu .dropdown-menu, -.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu { - right: 100%; - left: auto; - margin-right: -1px; - margin-left: 0; - -webkit-border-radius: 6px 0 6px 6px; - -moz-border-radius: 6px 0 6px 6px; - border-radius: 6px 0 6px 6px; -} - -.navbar-inverse { - color: #999999; -} - -.navbar-inverse .navbar-inner { - background-color: #1b1b1b; - background-image: -moz-linear-gradient(top, #222222, #111111); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); - background-image: -webkit-linear-gradient(top, #222222, #111111); - background-image: -o-linear-gradient(top, #222222, #111111); - background-image: linear-gradient(to bottom, #222222, #111111); - background-repeat: repeat-x; - border-color: #252525; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); -} - -.navbar-inverse .brand, -.navbar-inverse .nav > li > a { - color: #999999; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} - -.navbar-inverse .brand:hover, -.navbar-inverse .nav > li > a:hover { - color: #ffffff; -} - -.navbar-inverse .nav > li > a:focus, -.navbar-inverse .nav > li > a:hover { - color: #ffffff; - background-color: transparent; -} - -.navbar-inverse .nav .active > a, -.navbar-inverse .nav .active > a:hover, -.navbar-inverse .nav .active > a:focus { - color: #ffffff; - background-color: #111111; -} - -.navbar-inverse .navbar-link { - color: #999999; -} - -.navbar-inverse .navbar-link:hover { - color: #ffffff; -} - -.navbar-inverse .divider-vertical { - border-right-color: #222222; - border-left-color: #111111; -} - -.navbar-inverse .nav li.dropdown.open > .dropdown-toggle, -.navbar-inverse .nav li.dropdown.active > .dropdown-toggle, -.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle { - color: #ffffff; - background-color: #111111; -} - -.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: #999999; - border-bottom-color: #999999; -} - -.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret, -.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret, -.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar-inverse .navbar-search .search-query { - color: #ffffff; - background-color: #515151; - border-color: #111111; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - -webkit-transition: none; - -moz-transition: none; - -o-transition: none; - transition: none; -} - -.navbar-inverse .navbar-search .search-query:-moz-placeholder { - color: #cccccc; -} - -.navbar-inverse .navbar-search .search-query:-ms-input-placeholder { - color: #cccccc; -} - -.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder { - color: #cccccc; -} - -.navbar-inverse .navbar-search .search-query:focus, -.navbar-inverse .navbar-search .search-query.focused { - padding: 5px 15px; - color: #333333; - text-shadow: 0 1px 0 #ffffff; - background-color: #ffffff; - border: 0; - outline: 0; - -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); -} - -.navbar-inverse .btn-navbar { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #0e0e0e; - *background-color: #040404; - background-image: -moz-linear-gradient(top, #151515, #040404); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404)); - background-image: -webkit-linear-gradient(top, #151515, #040404); - background-image: -o-linear-gradient(top, #151515, #040404); - background-image: linear-gradient(to bottom, #151515, #040404); - background-repeat: repeat-x; - border-color: #040404 #040404 #000000; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.navbar-inverse .btn-navbar:hover, -.navbar-inverse .btn-navbar:active, -.navbar-inverse .btn-navbar.active, -.navbar-inverse .btn-navbar.disabled, -.navbar-inverse .btn-navbar[disabled] { - color: #ffffff; - background-color: #040404; - *background-color: #000000; -} - -.navbar-inverse .btn-navbar:active, -.navbar-inverse .btn-navbar.active { - background-color: #000000 \9; -} - -.breadcrumb { - padding: 8px 15px; - margin: 0 0 20px; - list-style: none; - background-color: #f5f5f5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.breadcrumb li { - display: inline-block; - *display: inline; - text-shadow: 0 1px 0 #ffffff; - *zoom: 1; -} - -.breadcrumb .divider { - padding: 0 5px; - color: #ccc; -} - -.breadcrumb .active { - color: #999999; -} - -.pagination { - margin: 20px 0; -} - -.pagination ul { - display: inline-block; - *display: inline; - margin-bottom: 0; - margin-left: 0; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - *zoom: 1; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.pagination ul > li { - display: inline; -} - -.pagination ul > li > a, -.pagination ul > li > span { - float: left; - padding: 4px 12px; - line-height: 20px; - text-decoration: none; - background-color: #ffffff; - border: 1px solid #dddddd; - border-left-width: 0; -} - -.pagination ul > li > a:hover, -.pagination ul > .active > a, -.pagination ul > .active > span { - background-color: #f5f5f5; -} - -.pagination ul > .active > a, -.pagination ul > .active > span { - color: #999999; - cursor: default; -} - -.pagination ul > .disabled > span, -.pagination ul > .disabled > a, -.pagination ul > .disabled > a:hover { - color: #999999; - cursor: default; - background-color: transparent; -} - -.pagination ul > li:first-child > a, -.pagination ul > li:first-child > span { - border-left-width: 1px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 4px; -} - -.pagination ul > li:last-child > a, -.pagination ul > li:last-child > span { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; -} - -.pagination-centered { - text-align: center; -} - -.pagination-right { - text-align: right; -} - -.pagination-large ul > li > a, -.pagination-large ul > li > span { - padding: 11px 19px; - font-size: 17.5px; -} - -.pagination-large ul > li:first-child > a, -.pagination-large ul > li:first-child > span { - -webkit-border-bottom-left-radius: 6px; - border-bottom-left-radius: 6px; - -webkit-border-top-left-radius: 6px; - border-top-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - -moz-border-radius-topleft: 6px; -} - -.pagination-large ul > li:last-child > a, -.pagination-large ul > li:last-child > span { - -webkit-border-top-right-radius: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - border-bottom-right-radius: 6px; - -moz-border-radius-topright: 6px; - -moz-border-radius-bottomright: 6px; -} - -.pagination-mini ul > li:first-child > a, -.pagination-small ul > li:first-child > a, -.pagination-mini ul > li:first-child > span, -.pagination-small ul > li:first-child > span { - -webkit-border-bottom-left-radius: 3px; - border-bottom-left-radius: 3px; - -webkit-border-top-left-radius: 3px; - border-top-left-radius: 3px; - -moz-border-radius-bottomleft: 3px; - -moz-border-radius-topleft: 3px; -} - -.pagination-mini ul > li:last-child > a, -.pagination-small ul > li:last-child > a, -.pagination-mini ul > li:last-child > span, -.pagination-small ul > li:last-child > span { - -webkit-border-top-right-radius: 3px; - border-top-right-radius: 3px; - -webkit-border-bottom-right-radius: 3px; - border-bottom-right-radius: 3px; - -moz-border-radius-topright: 3px; - -moz-border-radius-bottomright: 3px; -} - -.pagination-small ul > li > a, -.pagination-small ul > li > span { - padding: 2px 10px; - font-size: 11.9px; -} - -.pagination-mini ul > li > a, -.pagination-mini ul > li > span { - padding: 1px 6px; - font-size: 10.5px; -} - -.pager { - margin: 20px 0; - text-align: center; - list-style: none; - *zoom: 1; -} - -.pager:before, -.pager:after { - display: table; - line-height: 0; - content: ""; -} - -.pager:after { - clear: both; -} - -.pager li { - display: inline; -} - -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -.pager li > a:hover { - text-decoration: none; - background-color: #f5f5f5; -} - -.pager .next > a, -.pager .next > span { - float: right; -} - -.pager .previous > a, -.pager .previous > span { - float: left; -} - -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > span { - color: #999999; - cursor: default; - background-color: #fff; -} - -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000000; -} - -.modal-backdrop.fade { - opacity: 0; -} - -.modal-backdrop, -.modal-backdrop.fade.in { - opacity: 0.8; - filter: alpha(opacity=80); -} - -.modal { - position: fixed; - top: 50%; - left: 50%; - z-index: 1050; - width: 560px; - margin: -250px 0 0 -280px; - background-color: #ffffff; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, 0.3); - *border: 1px solid #999; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - outline: none; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} - -.modal.fade { - top: -25%; - -webkit-transition: opacity 0.3s linear, top 0.3s ease-out; - -moz-transition: opacity 0.3s linear, top 0.3s ease-out; - -o-transition: opacity 0.3s linear, top 0.3s ease-out; - transition: opacity 0.3s linear, top 0.3s ease-out; -} - -.modal.fade.in { - top: 50%; -} - -.modal-header { - padding: 9px 15px; - border-bottom: 1px solid #eee; -} - -.modal-header .close { - margin-top: 2px; -} - -.modal-header h3 { - margin: 0; - line-height: 30px; -} - -.modal-body { - max-height: 400px; - padding: 15px; - overflow-y: auto; -} - -.modal-form { - margin-bottom: 0; -} - -.modal-footer { - padding: 14px 15px 15px; - margin-bottom: 0; - text-align: right; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; - *zoom: 1; - -webkit-box-shadow: inset 0 1px 0 #ffffff; - -moz-box-shadow: inset 0 1px 0 #ffffff; - box-shadow: inset 0 1px 0 #ffffff; -} - -.modal-footer:before, -.modal-footer:after { - display: table; - line-height: 0; - content: ""; -} - -.modal-footer:after { - clear: both; -} - -.modal-footer .btn + .btn { - margin-bottom: 0; - margin-left: 5px; -} - -.modal-footer .btn-group .btn + .btn { - margin-left: -1px; -} - -.modal-footer .btn-block + .btn-block { - margin-left: 0; -} - -.tooltip { - position: absolute; - z-index: 1030; - display: block; - padding: 5px; - font-size: 11px; - opacity: 0; - filter: alpha(opacity=0); - visibility: visible; -} - -.tooltip.in { - opacity: 0.8; - filter: alpha(opacity=80); -} - -.tooltip.top { - margin-top: -3px; -} - -.tooltip.right { - margin-left: 3px; -} - -.tooltip.bottom { - margin-top: 3px; -} - -.tooltip.left { - margin-left: -3px; -} - -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #ffffff; - text-align: center; - text-decoration: none; - background-color: #000000; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.tooltip-arrow { - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.tooltip.top .tooltip-arrow { - bottom: 0; - left: 50%; - margin-left: -5px; - border-top-color: #000000; - border-width: 5px 5px 0; -} - -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-right-color: #000000; - border-width: 5px 5px 5px 0; -} - -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-left-color: #000000; - border-width: 5px 0 5px 5px; -} - -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-bottom-color: #000000; - border-width: 0 5px 5px; -} - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1010; - display: none; - width: 236px; - padding: 1px; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.popover.top { - margin-top: -10px; -} - -.popover.right { - margin-left: 10px; -} - -.popover.bottom { - margin-top: 10px; -} - -.popover.left { - margin-left: -10px; -} - -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - font-weight: normal; - line-height: 18px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - -webkit-border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - border-radius: 5px 5px 0 0; -} - -.popover-content { - padding: 9px 14px; -} - -.popover-content p, -.popover-content ul, -.popover-content ol { - margin-bottom: 0; -} - -.popover .arrow, -.popover .arrow:after { - position: absolute; - display: inline-block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.popover .arrow:after { - z-index: -1; - content: ""; -} - -.popover.top .arrow { - bottom: -10px; - left: 50%; - margin-left: -10px; - border-top-color: #ffffff; - border-width: 10px 10px 0; -} - -.popover.top .arrow:after { - bottom: -1px; - left: -11px; - border-top-color: rgba(0, 0, 0, 0.25); - border-width: 11px 11px 0; -} - -.popover.right .arrow { - top: 50%; - left: -10px; - margin-top: -10px; - border-right-color: #ffffff; - border-width: 10px 10px 10px 0; -} - -.popover.right .arrow:after { - bottom: -11px; - left: -1px; - border-right-color: rgba(0, 0, 0, 0.25); - border-width: 11px 11px 11px 0; -} - -.popover.bottom .arrow { - top: -10px; - left: 50%; - margin-left: -10px; - border-bottom-color: #ffffff; - border-width: 0 10px 10px; -} - -.popover.bottom .arrow:after { - top: -1px; - left: -11px; - border-bottom-color: rgba(0, 0, 0, 0.25); - border-width: 0 11px 11px; -} - -.popover.left .arrow { - top: 50%; - right: -10px; - margin-top: -10px; - border-left-color: #ffffff; - border-width: 10px 0 10px 10px; -} - -.popover.left .arrow:after { - right: -1px; - bottom: -11px; - border-left-color: rgba(0, 0, 0, 0.25); - border-width: 11px 0 11px 11px; -} - -.thumbnails { - margin-left: -20px; - list-style: none; - *zoom: 1; -} - -.thumbnails:before, -.thumbnails:after { - display: table; - line-height: 0; - content: ""; -} - -.thumbnails:after { - clear: both; -} - -.row-fluid .thumbnails { - margin-left: 0; -} - -.thumbnails > li { - float: left; - margin-bottom: 20px; - margin-left: 20px; -} - -.thumbnail { - display: block; - padding: 4px; - line-height: 20px; - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; -} - -a.thumbnail:hover { - border-color: #0088cc; - -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); -} - -.thumbnail > img { - display: block; - max-width: 100%; - margin-right: auto; - margin-left: auto; -} - -.thumbnail .caption { - padding: 9px; - color: #555555; -} - -.media, -.media-body { - overflow: hidden; - *overflow: visible; - zoom: 1; -} - -.media, -.media .media { - margin-top: 15px; -} - -.media:first-child { - margin-top: 0; -} - -.media-object { - display: block; -} - -.media-heading { - margin: 0 0 5px; -} - -.media .pull-left { - margin-right: 10px; -} - -.media .pull-right { - margin-left: 10px; -} - -.media-list { - margin-left: 0; - list-style: none; -} - -.label, -.badge { - display: inline-block; - padding: 2px 4px; - font-size: 11.844px; - font-weight: bold; - line-height: 14px; - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - white-space: nowrap; - vertical-align: baseline; - background-color: #999999; -} - -.label { - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.badge { - padding-right: 9px; - padding-left: 9px; - -webkit-border-radius: 9px; - -moz-border-radius: 9px; - border-radius: 9px; -} - -a.label:hover, -a.badge:hover { - color: #ffffff; - text-decoration: none; - cursor: pointer; -} - -.label-important, -.badge-important { - background-color: #b94a48; -} - -.label-important[href], -.badge-important[href] { - background-color: #953b39; -} - -.label-warning, -.badge-warning { - background-color: #f89406; -} - -.label-warning[href], -.badge-warning[href] { - background-color: #c67605; -} - -.label-success, -.badge-success { - background-color: #468847; -} - -.label-success[href], -.badge-success[href] { - background-color: #356635; -} - -.label-info, -.badge-info { - background-color: #3a87ad; -} - -.label-info[href], -.badge-info[href] { - background-color: #2d6987; -} - -.label-inverse, -.badge-inverse { - background-color: #333333; -} - -.label-inverse[href], -.badge-inverse[href] { - background-color: #1a1a1a; -} - -.btn .label, -.btn .badge { - position: relative; - top: -1px; -} - -.btn-mini .label, -.btn-mini .badge { - top: 0; -} - -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-moz-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-ms-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-o-keyframes progress-bar-stripes { - from { - background-position: 0 0; - } - to { - background-position: 40px 0; - } -} - -@keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -.progress { - height: 20px; - margin-bottom: 20px; - overflow: hidden; - background-color: #f7f7f7; - background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); - background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9); - background-repeat: repeat-x; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); -} - -.progress .bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - color: #ffffff; - text-align: center; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #0e90d2; - background-image: -moz-linear-gradient(top, #149bdf, #0480be); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); - background-image: -webkit-linear-gradient(top, #149bdf, #0480be); - background-image: -o-linear-gradient(top, #149bdf, #0480be); - background-image: linear-gradient(to bottom, #149bdf, #0480be); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -webkit-transition: width 0.6s ease; - -moz-transition: width 0.6s ease; - -o-transition: width 0.6s ease; - transition: width 0.6s ease; -} - -.progress .bar + .bar { - -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); -} - -.progress-striped .bar { - background-color: #149bdf; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - -moz-background-size: 40px 40px; - -o-background-size: 40px 40px; - background-size: 40px 40px; -} - -.progress.active .bar { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -moz-animation: progress-bar-stripes 2s linear infinite; - -ms-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} - -.progress-danger .bar, -.progress .bar-danger { - background-color: #dd514c; - background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35)); - background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); - background-image: linear-gradient(to bottom, #ee5f5b, #c43c35); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0); -} - -.progress-danger.progress-striped .bar, -.progress-striped .bar-danger { - background-color: #ee5f5b; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-success .bar, -.progress .bar-success { - background-color: #5eb95e; - background-image: -moz-linear-gradient(top, #62c462, #57a957); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957)); - background-image: -webkit-linear-gradient(top, #62c462, #57a957); - background-image: -o-linear-gradient(top, #62c462, #57a957); - background-image: linear-gradient(to bottom, #62c462, #57a957); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0); -} - -.progress-success.progress-striped .bar, -.progress-striped .bar-success { - background-color: #62c462; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-info .bar, -.progress .bar-info { - background-color: #4bb1cf; - background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9)); - background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); - background-image: -o-linear-gradient(top, #5bc0de, #339bb9); - background-image: linear-gradient(to bottom, #5bc0de, #339bb9); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0); -} - -.progress-info.progress-striped .bar, -.progress-striped .bar-info { - background-color: #5bc0de; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-warning .bar, -.progress .bar-warning { - background-color: #faa732; - background-image: -moz-linear-gradient(top, #fbb450, #f89406); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); - background-image: -webkit-linear-gradient(top, #fbb450, #f89406); - background-image: -o-linear-gradient(top, #fbb450, #f89406); - background-image: linear-gradient(to bottom, #fbb450, #f89406); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); -} - -.progress-warning.progress-striped .bar, -.progress-striped .bar-warning { - background-color: #fbb450; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.accordion { - margin-bottom: 20px; -} - -.accordion-group { - margin-bottom: 2px; - border: 1px solid #e5e5e5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.accordion-heading { - border-bottom: 0; -} - -.accordion-heading .accordion-toggle { - display: block; - padding: 8px 15px; -} - -.accordion-toggle { - cursor: pointer; -} - -.accordion-inner { - padding: 9px 15px; - border-top: 1px solid #e5e5e5; -} - -.carousel { - position: relative; - margin-bottom: 20px; - line-height: 1; -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} - -.carousel .item { - position: relative; - display: none; - -webkit-transition: 0.6s ease-in-out left; - -moz-transition: 0.6s ease-in-out left; - -o-transition: 0.6s ease-in-out left; - transition: 0.6s ease-in-out left; -} - -.carousel .item > img { - display: block; - line-height: 1; -} - -.carousel .active, -.carousel .next, -.carousel .prev { - display: block; -} - -.carousel .active { - left: 0; -} - -.carousel .next, -.carousel .prev { - position: absolute; - top: 0; - width: 100%; -} - -.carousel .next { - left: 100%; -} - -.carousel .prev { - left: -100%; -} - -.carousel .next.left, -.carousel .prev.right { - left: 0; -} - -.carousel .active.left { - left: -100%; -} - -.carousel .active.right { - left: 100%; -} - -.carousel-control { - position: absolute; - top: 40%; - left: 15px; - width: 40px; - height: 40px; - margin-top: -20px; - font-size: 60px; - font-weight: 100; - line-height: 30px; - color: #ffffff; - text-align: center; - background: #222222; - border: 3px solid #ffffff; - -webkit-border-radius: 23px; - -moz-border-radius: 23px; - border-radius: 23px; - opacity: 0.5; - filter: alpha(opacity=50); -} - -.carousel-control.right { - right: 15px; - left: auto; -} - -.carousel-control:hover { - color: #ffffff; - text-decoration: none; - opacity: 0.9; - filter: alpha(opacity=90); -} - -.carousel-caption { - position: absolute; - right: 0; - bottom: 0; - left: 0; - padding: 15px; - background: #333333; - background: rgba(0, 0, 0, 0.75); -} - -.carousel-caption h4, -.carousel-caption p { - line-height: 20px; - color: #ffffff; -} - -.carousel-caption h4 { - margin: 0 0 5px; -} - -.carousel-caption p { - margin-bottom: 0; -} - -.hero-unit { - padding: 60px; - margin-bottom: 30px; - font-size: 18px; - font-weight: 200; - line-height: 30px; - color: inherit; - background-color: #eeeeee; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.hero-unit h1 { - margin-bottom: 0; - font-size: 60px; - line-height: 1; - letter-spacing: -1px; - color: inherit; -} - -.hero-unit li { - line-height: 30px; -} - -.pull-right { - float: right; -} - -.pull-left { - float: left; -} - -.hide { - display: none; -} - -.show { - display: block; -} - -.invisible { - visibility: hidden; -} - -.affix { - position: fixed; -} diff --git a/src/site/resources/css/bootstrap.min.css b/src/site/resources/css/bootstrap.min.css deleted file mode 100644 index 43e16d72511..00000000000 --- a/src/site/resources/css/bootstrap.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap v2.2.1 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}.text-warning{color:#c09853}a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:hover{color:#356635}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal;cursor:pointer}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info>label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn,.input-append select+.btn-group .btn,.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child,.table-bordered tfoot:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9}.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success td{background-color:#dff0d8}.table tbody tr.error td{background-color:#f2dede}.table tbody tr.warning td{background-color:#fcf8e3}.table tbody tr.info td{background-color:#d9edf7}.table-hover tbody tr.success:hover td{background-color:#d0e9c6}.table-hover tbody tr.error:hover td{background-color:#ebcccc}.table-hover tbody tr.warning:hover td{background-color:#faf2cc}.table-hover tbody tr.info:hover td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#333;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999}.dropdown-menu .disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent;background-image:none}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:2px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini{padding:1px 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#c5c5c5;border-color:rgba(0,0,0,0.15) rgba(0,0,0,0.15) rgba(0,0,0,0.25)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar .btn+.btn,.btn-toolbar .btn-group+.btn,.btn-toolbar .btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu{font-size:14px}.btn-group>.btn-mini{font-size:11px}.btn-group>.btn-small{font-size:12px}.btn-group>.btn-large{font-size:16px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical .btn{display:block;float:none;width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical .btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical .btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical .btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical .btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical .btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible;color:#777}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse{color:#999}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb .divider{padding:0 5px;color:#ccc}.breadcrumb .active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:1px 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:50%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px}.tooltip.right{margin-left:3px}.tooltip.bottom{margin-top:3px}.tooltip.left{margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0}.popover .arrow,.popover .arrow:after{position:absolute;display:inline-block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow:after{z-index:-1;content:""}.popover.top .arrow{bottom:-10px;left:50%;margin-left:-10px;border-top-color:#fff;border-width:10px 10px 0}.popover.top .arrow:after{bottom:-1px;left:-11px;border-top-color:rgba(0,0,0,0.25);border-width:11px 11px 0}.popover.right .arrow{top:50%;left:-10px;margin-top:-10px;border-right-color:#fff;border-width:10px 10px 10px 0}.popover.right .arrow:after{bottom:-11px;left:-1px;border-right-color:rgba(0,0,0,0.25);border-width:11px 11px 11px 0}.popover.bottom .arrow{top:-10px;left:50%;margin-left:-10px;border-bottom-color:#fff;border-width:0 10px 10px}.popover.bottom .arrow:after{top:-1px;left:-11px;border-bottom-color:rgba(0,0,0,0.25);border-width:0 11px 11px}.popover.left .arrow{top:50%;right:-10px;margin-top:-10px;border-left-color:#fff;border-width:10px 0 10px 10px}.popover.left .arrow:after{right:-1px;bottom:-11px;border-left-color:rgba(0,0,0,0.25);border-width:11px 0 11px 11px}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media .pull-left{margin-right:10px}.media .pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel .item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel .item>img{display:block;line-height:1}.carousel .active,.carousel .next,.carousel .prev{display:block}.carousel .active{left:0}.carousel .next,.carousel .prev{position:absolute;top:0;width:100%}.carousel .next{left:100%}.carousel .prev{left:-100%}.carousel .next.left,.carousel .prev.right{left:0}.carousel .active.left{left:-100%}.carousel .active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css deleted file mode 100644 index fbe21fc9025..00000000000 --- a/src/site/resources/css/site.css +++ /dev/null @@ -1,114 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -div.clear { clear:both; visibility: hidden; } -div.clear hr { display: none; } - -/* Tweaks to the bootstrap theme ---------------------------------- */ -li { line-height: 20px; } -tt { font: 12px Menlo, Monaco, "Courier New", monospace; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; padding: 3px 4px; background-color: #f7f7f9; border: 1px solid #e1e1e8; } -tt, code { color: #4e648e } -dt { margin: 15px 0 5px 0; font-size: 1.2em } - -.big-red { font-weight: bold; color: #D14 } -.big-green { font-weight: bold; color: green } - -.layout-table { width: 100%; } -.sidebar { width: 250px; vertical-align: top; } -.content { padding-left: 20px; vertical-align: top; } - -.sidebar-nav { padding: 9px 0; } - -.logo-left { margin: 10px; float: left } -.logo-right { margin: 5px; float: right; height: 100px } - -.navbar .nav { margin-left: 40px; } - -.nav-list { margin-bottom: 15px; } -.nav-list li { line-height: 16px; } -.nav-list li.nav-header { color: #333; } -.nav-list li.nav-header i { margin-right: 5px; } - -.nav-list li a { background: no-repeat 16px 9px; padding-left: 34px; } -.nav-list li.collapsed > a { background-image: url(../images/collapsed.gif) } -.nav-list li.expanded > a { background-image: url(../images/expanded.gif) } - -.nav-list li.expanded ul { list-style: none; margin-left: 0; } -.nav-list li.expanded li a { display: block; padding: 3px 15px 3px 45px; margin-left: -15px; margin-right: -15px; } -.nav-list li.expanded li a:hover { text-decoration: none; background-color: #eee; } -.nav-list li.expanded li.active a { background-color: #08C; color: white } - -.nav.nav-tabs { margin-bottom: 8px; } - -.content .section { margin-top: 20px; } -.content .section:first-child { margin-top: 0; } -.section h2 { margin-bottom: 10px; } -.section h3 { margin-bottom: 10px; } -.section h4 { margin-bottom: 10px; } - -.footer { background-color: whitesmoke; padding: 15px; margin-top: 15px; text-align: center; border-top: 1px solid #eee; } -.footer p { font-size: 12px; margin: 0 } - -.table-not-wide { width: inherit;} -.alert-heading { display: block; font-size: 14px; margin-bottom: 6px; font-weight: bold; } - -/* Pretty printing styles. Used with prettify.js. ----------------------------------------------------- */ -.com { color: #93a1a1; } -.lit { color: #195f91; } -.pun, .opn, .clo { color: #93a1a1; } -.fun { color: #dc322f; } -.str, .atv { color: #D14; } -.kwd, .linenums .tag { color: #1e347b; } -.typ, .atn, .dec, .var { color: teal; } -.pln { color: #48484c; } -.prettyprint { padding: 8px; background-color: #f7f7f9; border: 1px solid #e1e1e8; } -.prettyprint.linenums { - -webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; - -moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; - box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0; -} -ol.linenums { margin: 0 0 0 33px; } -ol.linenums li { padding-left: 12px; color: #bebec5; line-height: 18px; text-shadow: 0 1px 0 #fff; } - -/* Additional styles. ------------------------*/ - -/* fixed width font links (e.g., to a JavaDoc page) */ -a.javadoc, a.javadoc:hover { - font: 12px Menlo, Monaco, "Courier New", monospace; -} -/* reset code links to same color as normal links */ -a.javadoc, a tt, a code { - color: #0088CC; -} - -/* add film icons to youtube and vimeo links (.icon-film) */ -a[href^='https://www.youtube.com/']::before, -a[href^='https://vimeo.com/']::before { - display: inline-block; - width: 14px; - height: 14px; - margin-top: 1px; - margin-right: .3em; - line-height: 14px; - vertical-align: text-top; - content: ''; - /* .icon-film */ - background: url("../img/glyphicons-halflings.png") no-repeat -192px 0; -} diff --git a/src/site/resources/glyphicons-halflings-2-1.zip b/src/site/resources/glyphicons-halflings-2-1.zip new file mode 100644 index 00000000000..974885b4088 Binary files /dev/null and b/src/site/resources/glyphicons-halflings-2-1.zip differ diff --git a/src/site/resources/images/LocationPerf.png b/src/site/resources/images/LocationPerf.png new file mode 100644 index 00000000000..1a232efe815 Binary files /dev/null and b/src/site/resources/images/LocationPerf.png differ diff --git a/src/site/resources/images/kibana.png b/src/site/resources/images/kibana.png new file mode 100644 index 00000000000..3b1569365b0 Binary files /dev/null and b/src/site/resources/images/kibana.png differ diff --git a/src/site/resources/img/glyphicons/book.png b/src/site/resources/img/glyphicons/book.png new file mode 100644 index 00000000000..5fcb05e43a0 Binary files /dev/null and b/src/site/resources/img/glyphicons/book.png differ diff --git a/src/site/resources/img/glyphicons/cog.png b/src/site/resources/img/glyphicons/cog.png new file mode 100644 index 00000000000..572b300d4f7 Binary files /dev/null and b/src/site/resources/img/glyphicons/cog.png differ diff --git a/src/site/resources/img/glyphicons/home.png b/src/site/resources/img/glyphicons/home.png new file mode 100644 index 00000000000..11f59e93119 Binary files /dev/null and b/src/site/resources/img/glyphicons/home.png differ diff --git a/src/site/resources/img/glyphicons/info.png b/src/site/resources/img/glyphicons/info.png new file mode 100644 index 00000000000..833cdd534d6 Binary files /dev/null and b/src/site/resources/img/glyphicons/info.png differ diff --git a/src/site/resources/img/glyphicons/layers.png b/src/site/resources/img/glyphicons/layers.png new file mode 100644 index 00000000000..d1570492e7e Binary files /dev/null and b/src/site/resources/img/glyphicons/layers.png differ diff --git a/src/site/resources/img/glyphicons/link.png b/src/site/resources/img/glyphicons/link.png new file mode 100644 index 00000000000..18cf1ecc64b Binary files /dev/null and b/src/site/resources/img/glyphicons/link.png differ diff --git a/src/site/resources/img/glyphicons/pencil.png b/src/site/resources/img/glyphicons/pencil.png new file mode 100644 index 00000000000..1f5df55688b Binary files /dev/null and b/src/site/resources/img/glyphicons/pencil.png differ diff --git a/src/site/resources/img/glyphicons/tag.png b/src/site/resources/img/glyphicons/tag.png new file mode 100644 index 00000000000..e189be4db31 Binary files /dev/null and b/src/site/resources/img/glyphicons/tag.png differ diff --git a/src/site/resources/js/bootstrap.js b/src/site/resources/js/bootstrap.js deleted file mode 100644 index 7f303eb88a1..00000000000 --- a/src/site/resources/js/bootstrap.js +++ /dev/null @@ -1,2027 +0,0 @@ -/* =================================================== - * bootstrap-transition.js v2.1.0 - * http://twitter.github.com/bootstrap/javascript.html#transitions - * =================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - $(function () { - - "use strict"; // jshint ;_; - - - /* CSS TRANSITION SUPPORT (http://www.modernizr.com/) - * ======================================================= */ - - $.support.transition = (function () { - - var transitionEnd = (function () { - - var el = document.createElement('bootstrap') - , transEndEventNames = { - 'WebkitTransition' : 'webkitTransitionEnd' - , 'MozTransition' : 'transitionend' - , 'OTransition' : 'oTransitionEnd otransitionend' - , 'transition' : 'transitionend' - } - , name - - for (name in transEndEventNames){ - if (el.style[name] !== undefined) { - return transEndEventNames[name] - } - } - - }()) - - return transitionEnd && { - end: transitionEnd - } - - })() - - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-alert.js v2.1.0 - * http://twitter.github.com/bootstrap/javascript.html#alerts - * ========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* ALERT CLASS DEFINITION - * ====================== */ - - var dismiss = '[data-dismiss="alert"]' - , Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.prototype.close = function (e) { - var $this = $(this) - , selector = $this.attr('data-target') - , $parent - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - $parent = $(selector) - - e && e.preventDefault() - - $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) - - $parent.trigger(e = $.Event('close')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - $parent - .trigger('closed') - .remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent.on($.support.transition.end, removeElement) : - removeElement() - } - - - /* ALERT PLUGIN DEFINITION - * ======================= */ - - $.fn.alert = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('alert') - if (!data) $this.data('alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.alert.Constructor = Alert - - - /* ALERT DATA-API - * ============== */ - - $(function () { - $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) - }) - -}(window.jQuery);/* ============================================================ - * bootstrap-button.js v2.1.0 - * http://twitter.github.com/bootstrap/javascript.html#buttons - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* BUTTON PUBLIC CLASS DEFINITION - * ============================== */ - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.button.defaults, options) - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - , $el = this.$element - , data = $el.data() - , val = $el.is('input') ? 'val' : 'html' - - state = state + 'Text' - data.resetText || $el.data('resetText', $el[val]()) - - $el[val](data[state] || this.options[state]) - - // push to event loop to allow forms to submit - setTimeout(function () { - state == 'loadingText' ? - $el.addClass(d).attr(d, d) : - $el.removeClass(d).removeAttr(d) - }, 0) - } - - Button.prototype.toggle = function () { - var $parent = this.$element.parent('[data-toggle="buttons-radio"]') - - $parent && $parent - .find('.active') - .removeClass('active') - - this.$element.toggleClass('active') - } - - - /* BUTTON PLUGIN DEFINITION - * ======================== */ - - $.fn.button = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('button') - , options = typeof option == 'object' && option - if (!data) $this.data('button', (data = new Button(this, options))) - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - $.fn.button.defaults = { - loadingText: 'loading...' - } - - $.fn.button.Constructor = Button - - - /* BUTTON DATA-API - * =============== */ - - $(function () { - $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - $btn.button('toggle') - }) - }) - -}(window.jQuery);/* ========================================================== - * bootstrap-carousel.js v2.1.0 - * http://twitter.github.com/bootstrap/javascript.html#carousel - * ========================================================== - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================== */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* CAROUSEL CLASS DEFINITION - * ========================= */ - - var Carousel = function (element, options) { - this.$element = $(element) - this.options = options - this.options.slide && this.slide(this.options.slide) - this.options.pause == 'hover' && this.$element - .on('mouseenter', $.proxy(this.pause, this)) - .on('mouseleave', $.proxy(this.cycle, this)) - } - - Carousel.prototype = { - - cycle: function (e) { - if (!e) this.paused = false - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - return this - } - - , to: function (pos) { - var $active = this.$element.find('.item.active') - , children = $active.parent().children() - , activePos = children.index($active) - , that = this - - if (pos > (children.length - 1) || pos < 0) return - - if (this.sliding) { - return this.$element.one('slid', function () { - that.to(pos) - }) - } - - if (activePos == pos) { - return this.pause().cycle() - } - - return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) - } - - , pause: function (e) { - if (!e) this.paused = true - if (this.$element.find('.next, .prev').length && $.support.transition.end) { - this.$element.trigger($.support.transition.end) - this.cycle() - } - clearInterval(this.interval) - this.interval = null - return this - } - - , next: function () { - if (this.sliding) return - return this.slide('next') - } - - , prev: function () { - if (this.sliding) return - return this.slide('prev') - } - - , slide: function (type, next) { - var $active = this.$element.find('.item.active') - , $next = next || $active[type]() - , isCycling = this.interval - , direction = type == 'next' ? 'left' : 'right' - , fallback = type == 'next' ? 'first' : 'last' - , that = this - , e = $.Event('slide', { - relatedTarget: $next[0] - }) - - this.sliding = true - - isCycling && this.pause() - - $next = $next.length ? $next : this.$element.find('.item')[fallback]() - - if ($next.hasClass('active')) return - - if ($.support.transition && this.$element.hasClass('slide')) { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - this.$element.one($.support.transition.end, function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { that.$element.trigger('slid') }, 0) - }) - } else { - this.$element.trigger(e) - if (e.isDefaultPrevented()) return - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger('slid') - } - - isCycling && this.cycle() - - return this - } - - } - - - /* CAROUSEL PLUGIN DEFINITION - * ========================== */ - - $.fn.carousel = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('carousel') - , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option) - , action = typeof option == 'string' ? option : options.slide - if (!data) $this.data('carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.cycle() - }) - } - - $.fn.carousel.defaults = { - interval: 5000 - , pause: 'hover' - } - - $.fn.carousel.Constructor = Carousel - - - /* CAROUSEL DATA-API - * ================= */ - - $(function () { - $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { - var $this = $(this), href - , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 - , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) - $target.carousel(options) - e.preventDefault() - }) - }) - -}(window.jQuery);/* ============================================================= - * bootstrap-collapse.js v2.1.0 - * http://twitter.github.com/bootstrap/javascript.html#collapse - * ============================================================= - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* COLLAPSE PUBLIC CLASS DEFINITION - * ================================ */ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, $.fn.collapse.defaults, options) - - if (this.options.parent) { - this.$parent = $(this.options.parent) - } - - this.options.toggle && this.toggle() - } - - Collapse.prototype = { - - constructor: Collapse - - , dimension: function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - , show: function () { - var dimension - , scroll - , actives - , hasData - - if (this.transitioning) return - - dimension = this.dimension() - scroll = $.camelCase(['scroll', dimension].join('-')) - actives = this.$parent && this.$parent.find('> .accordion-group > .in') - - if (actives && actives.length) { - hasData = actives.data('collapse') - if (hasData && hasData.transitioning) return - actives.collapse('hide') - hasData || actives.data('collapse', null) - } - - this.$element[dimension](0) - this.transition('addClass', $.Event('show'), 'shown') - $.support.transition && this.$element[dimension](this.$element[0][scroll]) - } - - , hide: function () { - var dimension - if (this.transitioning) return - dimension = this.dimension() - this.reset(this.$element[dimension]()) - this.transition('removeClass', $.Event('hide'), 'hidden') - this.$element[dimension](0) - } - - , reset: function (size) { - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - [dimension](size || 'auto') - [0].offsetWidth - - this.$element[size !== null ? 'addClass' : 'removeClass']('collapse') - - return this - } - - , transition: function (method, startEvent, completeEvent) { - var that = this - , complete = function () { - if (startEvent.type == 'show') that.reset() - that.transitioning = 0 - that.$element.trigger(completeEvent) - } - - this.$element.trigger(startEvent) - - if (startEvent.isDefaultPrevented()) return - - this.transitioning = 1 - - this.$element[method]('in') - - $.support.transition && this.$element.hasClass('collapse') ? - this.$element.one($.support.transition.end, complete) : - complete() - } - - , toggle: function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - } - - - /* COLLAPSIBLE PLUGIN DEFINITION - * ============================== */ - - $.fn.collapse = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('collapse') - , options = typeof option == 'object' && option - if (!data) $this.data('collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - $.fn.collapse.defaults = { - toggle: true - } - - $.fn.collapse.Constructor = Collapse - - - /* COLLAPSIBLE DATA-API - * ==================== */ - - $(function () { - $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) { - var $this = $(this), href - , target = $this.attr('data-target') - || e.preventDefault() - || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 - , option = $(target).data('collapse') ? 'toggle' : $this.data() - $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed') - $(target).collapse(option) - }) - }) - -}(window.jQuery);/* ============================================================ - * bootstrap-dropdown.js v2.1.0 - * http://twitter.github.com/bootstrap/javascript.html#dropdowns - * ============================================================ - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ============================================================ */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* DROPDOWN CLASS DEFINITION - * ========================= */ - - var toggle = '[data-toggle=dropdown]' - , Dropdown = function (element) { - var $el = $(element).on('click.dropdown.data-api', this.toggle) - $('html').on('click.dropdown.data-api', function () { - $el.parent().removeClass('open') - }) - } - - Dropdown.prototype = { - - constructor: Dropdown - - , toggle: function (e) { - var $this = $(this) - , $parent - , isActive - - if ($this.is('.disabled, :disabled')) return - - $parent = getParent($this) - - isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - $parent.toggleClass('open') - $this.focus() - } - - return false - } - - , keydown: function (e) { - var $this - , $items - , $active - , $parent - , isActive - , index - - if (!/(38|40|27)/.test(e.keyCode)) return - - $this = $(this) - - e.preventDefault() - e.stopPropagation() - - if ($this.is('.disabled, :disabled')) return - - $parent = getParent($this) - - isActive = $parent.hasClass('open') - - if (!isActive || (isActive && e.keyCode == 27)) return $this.click() - - $items = $('[role=menu] li:not(.divider) a', $parent) - - if (!$items.length) return - - index = $items.index($items.filter(':focus')) - - if (e.keyCode == 38 && index > 0) index-- // up - if (e.keyCode == 40 && index < $items.length - 1) index++ // down - if (!~index) index = 0 - - $items - .eq(index) - .focus() - } - - } - - function clearMenus() { - getParent($(toggle)) - .removeClass('open') - } - - function getParent($this) { - var selector = $this.attr('data-target') - , $parent - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 - } - - $parent = $(selector) - $parent.length || ($parent = $this.parent()) - - return $parent - } - - - /* DROPDOWN PLUGIN DEFINITION - * ========================== */ - - $.fn.dropdown = function (option) { - return this.each(function () { - var $this = $(this) - , data = $this.data('dropdown') - if (!data) $this.data('dropdown', (data = new Dropdown(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - $.fn.dropdown.Constructor = Dropdown - - - /* APPLY TO STANDARD DROPDOWN ELEMENTS - * =================================== */ - - $(function () { - $('html') - .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus) - $('body') - .on('click.dropdown touchstart.dropdown.data-api', '.dropdown', function (e) { e.stopPropagation() }) - .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle) - .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown) - }) - -}(window.jQuery);/* ========================================================= - * bootstrap-modal.js v2.1.0 - * http://twitter.github.com/bootstrap/javascript.html#modals - * ========================================================= - * Copyright 2012 Twitter, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================= */ - - -!function ($) { - - "use strict"; // jshint ;_; - - - /* MODAL CLASS DEFINITION - * ====================== */ - - var Modal = function (element, options) { - this.options = options - this.$element = $(element) - .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) - this.options.remote && this.$element.find('.modal-body').load(this.options.remote) - } - - Modal.prototype = { - - constructor: Modal - - , toggle: function () { - return this[!this.isShown ? 'show' : 'hide']() - } - - , show: function () { - var that = this - , e = $.Event('show') - - this.$element.trigger(e) - - if (this.isShown || e.isDefaultPrevented()) return - - $('body').addClass('modal-open') - - this.isShown = true - - this.escape() - - this.backdrop(function () { - var transition = $.support.transition && that.$element.hasClass('fade') - - if (!that.$element.parent().length) { - that.$element.appendTo(document.body) //don't move modals dom position - } - - that.$element - .show() - - if (transition) { - that.$element[0].offsetWidth // force reflow - } - - that.$element - .addClass('in') - .attr('aria-hidden', false) - .focus() - - that.enforceFocus() - - transition ? - that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : - that.$element.trigger('shown') - - }) - } - - , hide: function (e) { - e && e.preventDefault() - - var that = this - - e = $.Event('hide') - - this.$element.trigger(e) - - if (!this.isShown || e.isDefaultPrevented()) return - - this.isShown = false - - $('body').removeClass('modal-open') - - this.escape() - - $(document).off('focusin.modal') - - this.$element - .removeClass('in') - .attr('aria-hidden', true) - - $.support.transition && this.$element.hasClass('fade') ? - this.hideWithTransition() : - this.hideModal() - } - - , enforceFocus: function () { - var that = this - $(document).on('focusin.modal', function (e) { - if (that.$element[0] !== e.target && !that.$element.has(e.target).length) { - that.$element.focus() - } - }) - } - - , escape: function () { - var that = this - if (this.isShown && this.options.keyboard) { - this.$element.on('keyup.dismiss.modal', function ( e ) { - e.which == 27 && that.hide() - }) - } else if (!this.isShown) { - this.$element.off('keyup.dismiss.modal') - } - } - - , hideWithTransition: function () { - var that = this - , timeout = setTimeout(function () { - that.$element.off($.support.transition.end) - that.hideModal() - }, 500) - - this.$element.one($.support.transition.end, function () { - clearTimeout(timeout) - that.hideModal() - }) - } - - , hideModal: function (that) { - this.$element - .hide() - .trigger('hidden') - - this.backdrop() - } - - , removeBackdrop: function () { - this.$backdrop.remove() - this.$backdrop = null - } - - , backdrop: function (callback) { - var that = this - , animate = this.$element.hasClass('fade') ? 'fade' : '' - - if (this.isShown && this.options.backdrop) { - var doAnimate = $.support.transition && animate - - this.$backdrop = $('